Skip to content

Support programmed Gradle Version Catalogs version upgrades - #8503

Open
BoykoAlex wants to merge 2 commits into
openrewrite:mainfrom
BoykoAlex:catalog-versions-programmed
Open

Support programmed Gradle Version Catalogs version upgrades#8503
BoykoAlex wants to merge 2 commits into
openrewrite:mainfrom
BoykoAlex:catalog-versions-programmed

Conversation

@BoykoAlex

@BoykoAlex BoykoAlex commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What's changed?

Adds Gradle Version Catalog support (dependencyResolutionManagement { versionCatalogs { ... } } }, both Groovy and Kotlin DSL) to UpgradeDependencyVersion, so it can bump versions declared in a catalog instead of only versions declared directly in build.gradle/build.gradle.kts dependency blocks.

Scope for this PR is intentionally limited to library(...) and version(...) declarations. Catalog plugin(...) and bundle(...) entries, and from(...) (TOML-file-backed catalogs), are not supported yet.

This is implemented via three new traits:

  • GradleVersionCatalog — represents a single named catalog (libs { ... } / create("libs") { ... }), exposing its libraries and version declarations, and applying version changes to them.
  • VersionCatalogLibrary — a single library(...) declaration, with its inline version, versionRef(...), or withoutVersion() form.
  • VersionCatalogVersion — a single version(alias, value) declaration.

Several libraries commonly share one version(...) declaration via versionRef(...) (e.g. a dozen spring-boot-starter-* artifacts all pointing at one springBoot version). UpgradeDependencyVersion now handles this correctly:

  • If every library sharing a reference is targeted together, the shared version(...) declaration itself is bumped once.
  • If only some are targeted, the targeted library is detached to its own inline literal, leaving the shared reference and untargeted siblings alone.
  • If, across separate recipe runs, the detached members happen to converge back on the same version, they're re-attached to the shared reference.
  • A library whose version can't be resolved to a literal at all -- withoutVersion(), a rich .version { strictly(...) } } constraint, or an interpolated "${...}" string -- is treated consistently: left unchanged rather than crashing or being forced to a literal, and it doesn't interfere with reconciliation of any group it isn't genuinely part of.

A new GradleVersionCatalogVersionReferences marker snapshots the original versionRef sharing structure the first time a catalog is touched, so the detach/re-attach algorithm above can tell whether two separately-requested bumps actually target the same underlying declaration.

Also adds a few small reusable helpers to GradleTraitMatcher (isTopLevelStatement, asChainedInvocation, literalArgument) used by the new traits.

What's your motivation?

Gradle Version Catalogs are the recommended way to declare dependency versions in modern Gradle projects, but UpgradeDependencyVersion previously had no awareness of them, so catalog-based projects couldn't be upgraded automatically.

Anything in particular you'd like reviewers to focus on?

  • The versionRef detach/re-attach algorithm in GradleVersionCatalog (withVersion, withOriginalVersionReferencesMarker, reconciledAfterDetaching) — this is the trickiest part of the change and is deliberately kept separate from UpgradeDependencyVersion's existing safeUpdatedVersion logic for Gradle variables, since the two cases have different sharing semantics.
  • Whether the memoization in GradleVersionCatalog (AST walk cached per-instance, discarded on any mutation) is an acceptable tradeoff.

Have you considered any alternatives or workarounds?

Considered forcing withoutVersion() libraries onto the target version whenever one is specified, but decided against special-casing it: it's just one of several shapes (alongside rich .version { ... } constraints and interpolated versions) where a library's version can't be resolved to a literal, and all of them are now handled the same way -- left unchanged rather than crashing.

Checklist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants