diff --git a/src/main/java/com/palantir/gradle/versions/VersionsPropsPlugin.java b/src/main/java/com/palantir/gradle/versions/VersionsPropsPlugin.java index c2358aa99..e688d931e 100644 --- a/src/main/java/com/palantir/gradle/versions/VersionsPropsPlugin.java +++ b/src/main/java/com/palantir/gradle/versions/VersionsPropsPlugin.java @@ -145,6 +145,7 @@ private static void applyToRootProject(Project project) { project.subprojects(subproject -> subproject.getPluginManager().apply(VersionsPropsPlugin.class)); } + @SuppressWarnings("checkstyle:CyclomaticComplexity") private static void setupConfiguration( Project subproject, VersionRecommendationsExtension extension, @@ -173,6 +174,19 @@ private static void setupConfiguration( return; } + // Consumable-only configurations are outgoing variants — they expose artifacts to consumers + // but never resolve dependencies themselves, so version constraints inherited from + // rootConfiguration have no effect on them. + // Additionally, extending rootConfiguration on a consumable-only config creates a variant + // model cycle on Gradle 9.4.0+ when that configuration's outgoing artifacts resolve a configuration (e.g. + // compileClasspath) that also extends rootConfiguration. rootConfiguration ends up reachable from both sides — + // Gradle includes it in the variant model through the consumable config, and also needs to resolve it through + // compileClasspath. + if (conf.isCanBeConsumed() && !conf.isCanBeResolved()) { + log.debug("Not configuring consumable-only configuration: {}", conf); + return; + } + // Must do all this in a withDependencies block so that it's run lazily, so that // `extension.shouldExcludeConfiguration` isn't queried too early (before the user had the change to configure). // However, we must not make this lazy using an afterEvaluate. diff --git a/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationTest.java b/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationTest.java index af00be22f..08d533027 100644 --- a/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationTest.java +++ b/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationTest.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.palantir.gradle.testing.execution.GradleInvoker; import com.palantir.gradle.testing.execution.InvocationResult; +import com.palantir.gradle.testing.junit.AdditionallyRunWithGradle; import com.palantir.gradle.testing.junit.DisabledConfigurationCache; import com.palantir.gradle.testing.junit.GradlePluginTests; import com.palantir.gradle.testing.maven.MavenArtifact; @@ -81,6 +82,8 @@ void setup(MavenRepo repo, RootProject rootProject) { } } + println("file:///%s") + allprojects { repositories { maven { url "file:///%s" } @@ -101,7 +104,7 @@ void setup(MavenRepo repo, RootProject rootProject) { } } } - """, repo.path()); + """, repo.path(), repo.path()); } @SuppressWarnings("for-rollout:deprecation") @@ -298,6 +301,30 @@ void creates_rootconfiguration_even_if_versions_props_file_missing(GradleInvoker gradle.withArgs().buildsSuccessfully(); } + @Test + @AdditionallyRunWithGradle("9.4.0") + void consumable_only_configuration_on_root_project_does_not_cause_variant_model_cycle( + GradleInvoker gradle, RootProject rootProject) { + rootProject.propertiesFile("versions.props").setProperty("org.slf4j:*", "1.7.24"); + + rootProject.buildGradle().plugins().add("java"); + + rootProject.buildGradle().append(""" + configurations.consumable('customOutgoing') { + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'custom-usage')) + } + outgoing.artifacts(provider { + configurations.compileClasspath.incoming.artifactView { + attributes { attribute(Attribute.of('artifactType', String), 'jar') } + }.files.files + }) + } + """); + + gradle.withArgs("resolveConfigurations").buildsSuccessfully(); + } + @Test void build_succeeds_without_versions_props_or_versions_lock(GradleInvoker gradle, SubProject foo) { foo.buildGradle().plugins().add("java");