Always include Gradle API stubs in GradleParser classpath#7232
Always include Gradle API stubs in GradleParser classpath#7232
Conversation
When `settingsClasspath` or `buildscriptClasspath` is explicitly set (even to empty), `GradleParser` previously replaced the default Gradle API stubs entirely. This caused incorrect type attribution on DSL methods like `pluginManagement()`, `repositories()`, and `gradlePluginPortal()`, leading recipes like `AddSettingsPluginRepository` to fail to detect existing entries. Introduce `mergeClasspath()` which always includes the default Gradle API stubs alongside any externally-provided classpath. Add a reproducer test that sets `settingsClasspath` to empty and verifies `AddSettingsPluginRepository` correctly detects an existing `gradlePluginPortal()`.
58b3cb4 to
a007914
Compare
- Add GradleParserTest for settings.gradle.kts and build.gradle.kts type attribution with empty classpaths, verifying pluginManagement() resolves to Settings and repositories() resolves to Project - Narrow TypeValidation in AddSettingsPluginRepositoryTest to only disable methodInvocations instead of disabling all validation - Use explicit java.util imports instead of wildcard in GradleParser
...ite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddSettingsPluginRepositoryTest.java
Outdated
Show resolved
Hide resolved
|
When parsing Gradle scripts in process, the Gradle API will be available, so we wouldn't want to add the Gradle 6 implementation of classes back in that we use when out of process as a workaround. The class value discovered looks like a dynamic proxy which likely extends the target class, so that makes me suspicious that our matcher isn't checking supertypes in |
That's a good point. Of course we also use
That sounds plausible |
Yep. In recipes, we don't configure a |
|
Thanks for the detailed feedback. You raise a good point about the in-process case — The supertype/dynamic proxy angle is interesting too. I'm going to leave this PR as draft — @shanman190, would you be open to picking up the root cause fix? You clearly have better context on the MethodMatcher/FindRepository interaction and the in-process parsing constraints. In the meantime, #7230 (the recipe-level workaround) is merged and handles the immediate symptom. I have a follow-up ready for Shannon's review comment there too (#7245 — gating the name-based fallback on |
Problem
When
settingsClasspathorbuildscriptClasspathis explicitly set onGradleParser.Builder(even to an empty list), the parser replaces the default Gradle API stubs entirely instead of merging them. This causes incorrect type attribution on DSL methods likepluginManagement(),repositories(), andgradlePluginPortal().For example,
pluginManagement()ends up with a garbage declaring type likeorg.gradle.api.initialization.org.gradle.api.initialization.13789550265458Ktinstead of the correctorg.gradle.api.initialization.Settings. Recipes that useMethodMatcher(e.g.FindRepository) then fail to match these methods, causing downstream recipes likeAddSettingsPluginRepositoryto not detect existing entries and add duplicates.This happens in practice when the Gradle plugin extracts the settings buildscript classpath (typically empty when there are no custom settings plugins) and passes it to
GradleParser.Solution
Introduce
mergeClasspath()which always includes the default Gradle API stubs (gradle-core-api, gradle-base-services, etc.) alongside any externally-provided classpath, using aLinkedHashSetto avoid duplicates. All four parser initialization paths (groovy/kotlin x build/settings) now use this method.Includes a reproducer test that sets
settingsClasspathto an empty list and verifiesAddSettingsPluginRepositorycorrectly detects an existinggradlePluginPortal().