Skip to content

Fall back to the wrapper's distributionUrl when recipes find no BuildTool - #8433

Merged
timtebeek merged 4 commits into
mainfrom
tim/brazzaville-v1
Aug 10, 2026
Merged

Fall back to the wrapper's distributionUrl when recipes find no BuildTool#8433
timtebeek merged 4 commits into
mainfrom
tim/brazzaville-v1

Conversation

@timtebeek

@timtebeek timtebeek commented Aug 8, 2026

Copy link
Copy Markdown
Member

UpdateGradleWrapper, UpdateMavenWrapper and AddDevelocityGradlePlugin all gated on a BuildTool marker of the matching type, which only the OpenRewrite build-tool plugins attach — so on an LST from any other parser they silently did nothing, indistinguishable from "already up to date" (this has broken three times in the Moderne CLI alone: moderneinc/moderne-cli#1005, moderneinc/moderne-cli#4414, moderneinc/moderne-cli#4472). The marker is also a worse source of truth than the file itself: the plugins set it from whichever tool ran the build, while distributionUrl is the wrapper's version by definition. All three scanners now prefer the marker and otherwise derive the version from distributionUrl, still bailing out when neither yields one, and only bumping the marker when one was present to begin with. The shared extraction is GradleWrapper.versionFromDistributionUrl, which GradleWrapper.create(URI) also now uses so a pre-release wrapperUri yields 8.5-rc-1 rather than 8.5 — that value picks the gradlew script template, and versions.csv carries templates for pre-releases.

Only the scanners changed; which files the visitors rewrite is untouched. AddDevelocityGradlePlugin becomes a ScanningRecipe that keys each wrapper's version by the directory it governs and resolves each script against the nearest enclosing wrapper, so a nested build with its own wrapper is never judged by the root's. Its GradleSettings/GradleProject requirement is deliberately unchanged, so it helps LSTs that carry Gradle project provenance but no BuildTool, not ones missing both.

New tests across all three recipes cover no marker, a uniform BuildTool(ModerneCli, …) LST, wrappers in subdirectories, marker-wins-on-disagreement, the downgrade guard, and a distributionUrl with no extractable version; AddDevelocityGradlePluginTest also covers a nested build resolving against its own wrapper. The pre-existing updateMultipleWrappers tests were fixed to stop sharing SourceSpec instances across dir(...) calls, which Dir.iterator() mutates in place. Verified against the moderne-cli#4472 reproduction: rewrite-github-actions' wrapper on 9.5.1 with no BuildTool anywhere now updates all four files to 9.7.0.

…dTool`

`UpdateGradleWrapper` and `UpdateMavenWrapper` gated on a `BuildTool` marker
of the matching type being present on the wrapper properties file. Only the
OpenRewrite build-tool plugins attach that marker, so any other parser
pipeline produced a silent no-op with no way to tell "up to date" apart from
"provenance missing". It has broken three times in the Moderne CLI alone
(moderneinc/moderne-cli#1005, #4414, #4472).

The marker is also a worse source of truth than the file itself: the plugins
set it from the tool that happened to run the build, while `distributionUrl`
is the wrapper's version by definition, and both recipes already parse it.

Both scanners now prefer the marker and fall back to the version in
`distributionUrl`, bailing out as before when neither yields one. The marker
is only updated when one was present to begin with. The `getVisitor()` guard
that skipped any file carrying a non-matching `BuildTool` now only rejects
non-wrapper paths, so a `gradlew`/`mvnw` stamped with another build tool is
still updated once its properties file was accepted.
…tionUrl`

Both `visitCompilationUnit` overloads bailed when the script they visited
carried no `BuildTool` marker of type `Gradle`, the same invisible provenance
contract the wrapper recipes just shed. The marker feeds a single decision —
whether the build is Gradle 6 or newer, and so whether the plugin belongs in
the settings script or the root build script — which `gradle-wrapper.properties`
answers by definition.

The recipe is now a `ScanningRecipe`. The scanner records each wrapper's
version keyed by the directory that wrapper governs, and scripts resolve
against the nearest enclosing wrapper, so a nested build with its own wrapper
is never judged by the root's version. The marker still wins when present; with
neither the recipe bails as before. The duplicated guard is now one helper so
the Groovy and Kotlin paths cannot drift.

`versionFromDistributionUrl` moves out of `UpdateGradleWrapper` into
`org.openrewrite.gradle.internal.GradleWrapperProperties` so both recipes share
one copy.

This does not relax the `GradleSettings`/`GradleProject` requirement that
follows, so it helps LSTs carrying Gradle project provenance but no
`BuildTool`, not ones missing both.
@timtebeek timtebeek changed the title Fall back to distributionUrl when the wrapper recipes find no BuildTool Fall back to the wrapper's distributionUrl when recipes find no BuildTool Aug 9, 2026
The `wrapperUri` option's version was read with `GRADLE_VERSION_PATTERN`, whose
`gradle-([0-9.]+)` stops at the first hyphen, so a pre-release distribution such
as `gradle-8.5-rc-1-bin.zip` yielded `8.5`. That version is both written into
the `gradleVersion` DSL, where it disagreed with the `distributionUrl` beside
it, and used to pick the `gradlew` script template.

Try `GradleWrapperProperties.versionFromDistributionUrl` first and fall back to
the loose pattern only when it finds nothing, so URLs it does not recognize
behave exactly as before. `versions.csv` carries script templates for
pre-release versions, so `findNearest` now resolves an RC to that RC's own
scripts rather than the release's.
- Move `versionFromDistributionUrl` from the new
  `internal.GradleWrapperProperties` onto `util.GradleWrapper`, beside the
  `GRADLE_VERSION_PATTERN` it complements, and drop the internal class. This
  makes the method public API rather than internal, so the two distribution URL
  parsers now sit together where the next reader will find them.
- Revert the `getVisitor()` guards in both wrapper recipes to their original
  form and delete `isWrapperPath`. The guard only ever relaxed a case where the
  properties file carries a matching `BuildTool` while a sibling wrapper file
  carries a different one, an LST shape that does not occur, so the tests
  covering it go too.
- Express `AddDevelocityGradlePlugin`'s version resolution as
  `filter`/`map`/`orElseGet`, matching the scanners in the wrapper recipes.
@timtebeek
timtebeek merged commit 6d58e37 into main Aug 10, 2026
1 check passed
@timtebeek
timtebeek deleted the tim/brazzaville-v1 branch August 10, 2026 09:44
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant