Skip to content

Fix Gradle 9.4 variant resolution cycle with gradle-consistent-versions#155

Open
FinlayRJW wants to merge 10 commits intodevelopfrom
finlayw/fix-gradle9-variant-resolution
Open

Fix Gradle 9.4 variant resolution cycle with gradle-consistent-versions#155
FinlayRJW wants to merge 10 commits intodevelopfrom
finlayw/fix-gradle9-variant-resolution

Conversation

@FinlayRJW
Copy link
Contributor

@FinlayRJW FinlayRJW commented Mar 13, 2026

Before this PR

On Gradle 9.4.0, applying gradle-idea-language-injector alongside gradle-consistent-versions (GCV) fails with:

 > Could not resolve root project :.
   Required by:
       root project 'root'
    > Could not resolve all files for configuration ':compileClasspath'.
       > Could not resolve root project :.
         Required by:
             root project 'root'
          > Value for variants of root project : has not been calculated yet.
       > Could not resolve root project :.
         Required by:
             root project 'root'
          > Value for variants of root project : has not been calculated yet.

Root cause

gradle/gradle#36245 changed extendsFrom from eagerly storing parent configs in a Set<Configuration> to lazily storing them as Provider<Configuration>. This means that during variant model computation, evaluating a consumable config's state can now trigger dependency graph traversal as a side effect — previously iterating extendsFrom was just reading a pre-populated set with no side effects.

The old IdeaLanguageInjectorProjectPlugin published resolved JAR files as artifacts of the consumable configuration via a provider:

outgoing.getOutgoing().artifacts(project.provider(() -> sourceSetArtifacts(project)));

where sourceSetArtifacts resolved each source set's compileClasspath and collected the resulting File objects. When Gradle evaluates this artifact provider during variant model computation, it triggers compileClasspath resolution. GCV makes all configs extend rootConfiguration, which has a ProjectDependency on the root project. Resolving that dependency requires the root project's variant model — which is already being computed. This creates the cycle:

ROOT PROJECT variant model computation starts
│
├── Iterates consumable configs to build variant model
│   │
│   └── "idea-language-injector-outgoing" (consumable)
│       │
│       └── artifacts(provider(() -> sourceSetArtifacts()))
│           │
│           └── Provider evaluated during variant model computation
│               │
│               └── sourceSetArtifacts() calls compileClasspath
│                   .getIncoming().artifactView(...).getFiles().getFiles()
│                   │
│                   └── Forces resolution of compileClasspath
│                       │
│                       ├── compileClasspath extendsFrom rootConfiguration (via GCV)
│                       │
│                       └── rootConfiguration has ProjectDependency(:)
│                           │
│                           └── Resolving ProjectDependency(:) needs
│                               root project's variant model
│                               │
│                               └── ❌ CYCLE: variant model already being computed

This was architecturally wrong: a consumable configuration was eagerly resolving another configuration (compileClasspath) inside an artifact provider.

Approaches explored

Three fixes were explored:

  1. Fix Gradle 9.4.0 variant resolution failure with gradle-consistent-versions #149: Call VersionRecommendationsExtension.excludeConfigurations(...) to opt our config out of GCV. Works, but requires a compile-time dependency on GCV and treats the symptom rather than the cause.

  2. GCV-side fix: Skip extendsFrom for consumable-only configs in GCV. This changes how GCV works and the real issue is that idea-language-injector shouldn't be resolving a configuration inside an artifact provider.

  3. Fix Gradle 9.4 variant model cycle without GCV workaround #150: Stop applying the project plugin to the root project (allprojectssubprojects) and scan root-project dependencies via an artifact view directly. Avoids the cycle since the root no longer has a consumable config. However this special-cases the root project and introduces two code paths.

After this PR

Takes a different approach: instead of having the consumable config publish pre-resolved JAR files, it now participates properly in Gradle's dependency graph.

Each subproject's consumable config extends from the same declarable configurations that compileClasspath extends from (api, implementation, compileOnly, etc.). This means the consumable config carries dependency metadata, not resolved files — so evaluating the extends chain during variant model computation has no side effects.

Since external libraries only publish standard variants (java-api, java-runtime) and don't know about our custom Usage attribute (idea-language-injector-outgoing), an AttributeCompatibilityRule is registered to tell Gradle that java-api variants are compatible when the consumer requests idea-language-injector-outgoing. This lets transitive dependencies resolve through their java-api variant.

Testing

  • All existing tests pass
  • New compatible_with_consistent_versions test verifies compileJava succeeds with GCV applied

@changelog-app
Copy link

changelog-app bot commented Mar 13, 2026

Generate changelog in changelog/@unreleased

Type (Select exactly one)

  • Feature (Adding new functionality)
  • Improvement (Improving existing functionality)
  • Fix (Fixing an issue with existing functionality)
  • Break (Creating a new major version by breaking public APIs)
  • Deprecation (Removing functionality in a non-breaking way)
  • Migration (Automatically moving data/functionality to a new system)

Description

Fix Gradle 9.4 variant resolution cycle with gradle-consistent-versions

Check the box to generate changelog(s)

  • Generate changelog entry

@changelog-app
Copy link

changelog-app bot commented Mar 13, 2026

Successfully generated changelog entry!

Need to regenerate?

Simply interact with the changelog bot comment again to regenerate these entries.

🔄 Changelog entries were re-generated at Fri, 13 Mar 2026 13:25:25 UTC!


📋Changelog Preview

💡 Improvements

  • Fix Gradle 9.4 variant resolution cycle with gradle-consistent-versions (#155)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant