Repository: gradle/gradle
Pull request #32823: Prevent selecting the root node as a variant
Author: login: jvandort
Created: 2025-03-24T17:02:58Z | Merged: 2025-03-27T16:50:25Z

The root node represents the configuration being resolved, and therefore it is not intended to be selected as a variant.

### Reviewing cheatsheet

Before merging the PR, comments starting with 
- ❌ ❓**must** be fixed
- 🤔 💅 **should** be fixed
- 💭 **may** be fixed
- 🎉 celebrate happy things



Diff:
diff --git a/platforms/core-configuration/configuration-cache/src/integTest/groovy/org/gradle/internal/cc/impl/fixtures/ToolingApiSpec.groovy b/platforms/core-configuration/configuration-cache/src/integTest/groovy/org/gradle/internal/cc/impl/fixtures/ToolingApiSpec.groovy
index 133656614b3df..4638cbdbf15a8 100644
--- a/platforms/core-configuration/configuration-cache/src/integTest/groovy/org/gradle/internal/cc/impl/fixtures/ToolingApiSpec.groovy
+++ b/platforms/core-configuration/configuration-cache/src/integTest/groovy/org/gradle/internal/cc/impl/fixtures/ToolingApiSpec.groovy
@@ -106,15 +106,23 @@ trait ToolingApiSpec {
         addModelImplementation("buildSrc")
 
         addModelBuilderImplementation("buildSrc", """
-            def message = "project \${project.path} classpath = \${project.configurations.implementation.files.size()}"
+            def message = "project \${project.path} classpath = \${project.configurations.runtimeClasspath.files.size()}"
             return new MyModel(message)
         """)
 
         addBuilderRegisteringPluginImplementation("buildSrc", "MyModelBuilder", """
-            def implementation = project.configurations.create("implementation")
-            implementation.attributes.attribute(${Attribute.name}.of("thing", String), "custom")
-            def artifact = project.layout.buildDirectory.file("out.txt")
-            implementation.outgoing.artifact(artifact)
+            def implementation = project.configurations.dependencyScope("implementation")
+            def runtimeClasspath = project.configurations.resolvable("runtimeClasspath") {
+                extendsFrom(implementation.get())
+                attributes.attribute(${Attribute.name}.of("thing", String), "custom")
+            }
+            def runtimeElements = project.configurations.consumable("runtimeElements") {
+                extendsFrom(implementation.get())
+                attributes.attribute(${Attribute.name}.of("thing", String), "custom")
+
+                def artifact = project.layout.buildDirectory.file("out.txt")
+                outgoing.artifact(artifact)
+            }
         """)
     }
 
diff --git a/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/BuildscriptResolutionIntegrationTest.groovy b/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/BuildscriptResolutionIntegrationTest.groovy
index 94d13799a620e..80e9197fcb87d 100644
--- a/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/BuildscriptResolutionIntegrationTest.groovy
+++ b/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/BuildscriptResolutionIntegrationTest.groovy
@@ -32,9 +32,7 @@ class BuildscriptResolutionIntegrationTest extends AbstractIntegrationSpec {
         """
     }
 
-    // This is not desired behavior. A project dependency should refer to the actual
-    // project, not its buildscript component. This is a bug.
-    def "project buildscript configuration can select itself"() {
+    def "project buildscript configuration cannot select itself"() {
         buildFile << """
             buildscript {
                 configurations {
@@ -61,15 +59,15 @@ class BuildscriptResolutionIntegrationTest extends AbstractIntegrationSpec {
             }
         """
 
-        expect:
-        executer.expectDocumentedDeprecationWarning("While resolving configuration 'conf', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
+        when:
         executer.expectDocumentedDeprecationWarning("Mutating configuration container for buildscript of root project 'root' using create(String, Closure) has been deprecated. This will fail with an error in Gradle 9.0. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#mutating_buildscript_configurations")
-        succeeds("resolve")
+        fails("resolve")
+
+        then:
+        failure.assertHasCause("Cannot select root node 'conf' as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
     }
 
-    // This is not desired behavior. A project dependency should refer to the actual
-    // project, not its buildscript component. This is a bug.
-    def "project buildscript classpath configuration can select itself"() {
+    def "project buildscript classpath configuration cannot select itself"() {
         file("foo.txt") << "foo"
         buildFile << """
             buildscript {
@@ -89,13 +87,12 @@ class BuildscriptResolutionIntegrationTest extends AbstractIntegrationSpec {
             assert buildscript.configurations.classpath.incoming.files*.name == ["foo.txt"]
         """
 
-        expect:
-        2.times {
-            // Once when resolving the classpath normally, once when re-resolving
-            executer.expectDocumentedDeprecationWarning("The classpath configuration has been deprecated for consumption. This will fail with an error in Gradle 9.0. For more information, please refer to https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:deprecated-configurations in the Gradle documentation.")
-            executer.expectDocumentedDeprecationWarning("While resolving configuration 'classpath', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
-        }
-        succeeds("help")
+        when:
+        executer.expectDocumentedDeprecationWarning("The classpath configuration has been deprecated for consumption. This will fail with an error in Gradle 9.0. For more information, please refer to https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:deprecated-configurations in the Gradle documentation.")
+        fails("resolve")
+
+        then:
+        failure.assertHasCause("Cannot select root node 'classpath' as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
     }
 
     def "project buildscript configuration can select another project"() {
diff --git a/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
index af16002d482e4..33d78977db3a8 100644
--- a/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
+++ b/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
@@ -522,8 +522,10 @@ class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec im
         }
     }
 
+    // TODO #9591: This does not reflect desired behavior. The recursive copy is a detached configuration, which
+    // effectively replaces the root component, preventing the consumable configuration from being selected.
     @Issue('GRADLE-3280')
-    def "can resolve recursive copy of configuration with cyclic project dependencies"() {
+    def "cannot resolve recursive copy of configuration with cyclic project dependencies"() {
         given:
         settingsFile << "include 'a', 'b', 'c'"
         def common = """
@@ -583,12 +585,14 @@ class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec im
         """
 
         expect:
-        succeeds ':a:assertCanResolve'
+        succeeds(":a:assertCanResolve")
 
-        and:
+        when:
         executer.expectDocumentedDeprecationWarning("The resCopy configuration has been deprecated for consumption. This will fail with an error in Gradle 9.0. For more information, please refer to https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:deprecated-configurations in the Gradle documentation.")
-        executer.expectDocumentedDeprecationWarning("While resolving configuration 'resCopy', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
-        succeeds ':a:assertCanResolveRecursiveCopy'
+        fails(":a:assertCanResolveRecursiveCopy")
+
+        then:
+        failure.assertHasCause("Cannot select root node 'resCopy' as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
     }
 
     // this test is largely covered by other tests, but does ensure that there is nothing special about
diff --git a/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RootComponentResolutionIntegrationTest.groovy b/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RootComponentResolutionIntegrationTest.groovy
index 014d9020a168c..beb090a10070d 100644
--- a/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RootComponentResolutionIntegrationTest.groovy
+++ b/platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RootComponentResolutionIntegrationTest.groovy
@@ -103,7 +103,7 @@ class RootComponentResolutionIntegrationTest extends AbstractIntegrationSpec {
         succeeds("resolve")
     }
 
-    def "configuration can resolve itself"() {
+    def "configuration cannot resolve itself"() {
         buildFile << """
             configurations {
                 conf {
@@ -128,54 +128,11 @@ class RootComponentResolutionIntegrationTest extends AbstractIntegrationSpec {
             }
         """
 
-        executer.expectDocumentedDeprecationWarning("While resolving configuration 'conf', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
-
-        expect:
-        succeeds("resolve")
-    }
-
-    def "configuration can resolve itself and reselect artifacts"() {
-        buildFile << """
-            configurations {
-                conf {
-                    outgoing {
-                        artifact file("foo.txt")
-                    }
-                    attributes {
-                        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, "foo"))
-                    }
-                }
-                other {
-                    outgoing {
-                        artifact file("bar.txt")
-                    }
-                    attributes {
-                        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, "bar"))
-                    }
-                }
-            }
-
-            dependencies {
-                conf project
-            }
-
-            task resolve {
-                def files = configurations.conf.incoming.artifactView {
-                    withVariantReselection()
-                    attributes {
-                        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, "bar"))
-                    }
-                }.files
-                doLast {
-                    assert files*.name == ["bar.txt"]
-                }
-            }
-        """
-
-        executer.expectDocumentedDeprecationWarning("While resolving configuration 'conf', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
+        when:
+        fails("resolve")
 
-        expect:
-        succeeds("resolve")
+        then:
+        failure.assertHasCause("Cannot select root node 'conf' as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
     }
 
     def "resolvable configuration and consumable configuration from same project live in same resolved component"() {
diff --git a/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultLocalVariantGraphResolveStateBuilder.java b/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultLocalVariantGraphResolveStateBuilder.java
index 7ebf3fae00860..0c459297f0da6 100644
--- a/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultLocalVariantGraphResolveStateBuilder.java
+++ b/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultLocalVariantGraphResolveStateBuilder.java
@@ -56,6 +56,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Set;
@@ -89,50 +90,51 @@ public LocalVariantGraphResolveState createRootVariantState(
         ModelContainer<?> model,
         CalculatedValueContainerFactory calculatedValueContainerFactory
     ) {
-        // Starting in Gradle 9.0, the logic for creating the root variant and for creating a
-        // consumable variant will differ, as the root variant should not have artifacts.
-        // However, until then, since the root variant can still be consumed, it must have artifacts.
-        return doCreateVariantState(
-            configuration,
-            componentId,
+        finalize(configuration, "resolved");
+
+        ImmutableAttributes attributes = configuration.getAttributes().asImmutable();
+        CalculatedValue<DefaultLocalVariantGraphResolveState.VariantDependencyMetadata> dependencies = getConfigurationDependencyState(
+            configuration.asDescribable(),
+            configuration.getHierarchy(),
+            attributes,
             dependencyCache,
             model,
-            calculatedValueContainerFactory,
-            "resolved"
+            calculatedValueContainerFactory
         );
-    }
 
-    @Override
-    public LocalVariantGraphResolveState createConsumableVariantState(
-        ConfigurationInternal configuration,
-        ComponentIdentifier componentId,
-        DependencyCache dependencyCache,
-        ModelContainer<?> model,
-        CalculatedValueContainerFactory calculatedValueContainerFactory
-    ) {
-        return doCreateVariantState(
-            configuration,
+        // TODO: The root node should have no capabilities, as it has no artifacts.
+        // However, changing this prevents conflicts between code being compiled and its
+        // dependencies from being detected during compilation -- though this also
+        // can lead to some false positives.
+        ImmutableCapabilities capabilities = ImmutableCapabilities.of(Configurations.collectCapabilities(configuration, new HashSet<>(), new HashSet<>()));
+        LocalVariantGraphResolveMetadata metadata = new DefaultLocalVariantGraphResolveMetadata(
+            configuration.getName(),
+            configuration.isTransitive(),
+            attributes,
+            capabilities,
+            false
+        );
+
+        return new DefaultLocalVariantGraphResolveState(
+            idGenerator.nextVariantId(),
             componentId,
-            dependencyCache,
-            model,
+            metadata,
+            idGenerator,
             calculatedValueContainerFactory,
-            "consumed as a variant"
+            dependencies,
+            Collections.emptySet()
         );
     }
 
-    private DefaultLocalVariantGraphResolveState doCreateVariantState(
+    @Override
+    public LocalVariantGraphResolveState createConsumableVariantState(
         ConfigurationInternal configuration,
         ComponentIdentifier componentId,
         DependencyCache dependencyCache,
         ModelContainer<?> model,
-        CalculatedValueContainerFactory calculatedValueContainerFactory,
-        String observationReason
+        CalculatedValueContainerFactory calculatedValueContainerFactory
     ) {
-        // Perform any final mutating actions for this configuration and its parents.
-        // Then, lock this configuration and its parents from mutation.
-        // After we observe a configuration (by building its metadata), its state should not change.
-        configuration.runDependencyActions();
-        configuration.markAsObserved(observationReason);
+        finalize(configuration, "consumed as a variant");
 
         String configurationName = configuration.getName();
         ComponentConfigurationIdentifier configurationIdentifier = new ComponentConfigurationIdentifier(componentId, configurationName);
@@ -156,13 +158,14 @@ public void visitChildVariant(String name, DisplayName displayName, ImmutableAtt
             }
         });
 
-        // Collect all dependencies and excludes in hierarchy.
-        // After running the dependency actions and preventing from mutation above, we know the
-        // hierarchy will not change anymore and all configurations in the hierarchy
-        // will no longer be mutated.
-        Set<Configuration> hierarchy = configuration.getHierarchy();
-        CalculatedValue<DefaultLocalVariantGraphResolveState.VariantDependencyMetadata> dependencies =
-            getConfigurationDependencyState(configuration.asDescribable(), hierarchy, attributes, dependencyCache, model, calculatedValueContainerFactory);
+        CalculatedValue<DefaultLocalVariantGraphResolveState.VariantDependencyMetadata> dependencies = getConfigurationDependencyState(
+            configuration.asDescribable(),
+            configuration.getHierarchy(),
+            attributes,
+            dependencyCache,
+            model,
+            calculatedValueContainerFactory
+        );
 
         LocalVariantGraphResolveMetadata metadata = new DefaultLocalVariantGraphResolveMetadata(
             configurationName,
@@ -183,6 +186,19 @@ public void visitChildVariant(String name, DisplayName displayName, ImmutableAtt
         );
     }
 
+    /**
+     * Perform any final mutating actions for this configuration and its parents.
+     * Then, lock this configuration and its parents from mutation.
+     * After we observe a configuration (by building its metadata), its state should not change.
+     */
+    private static void finalize(ConfigurationInternal configuration, String reason) {
+        // Perform any final mutating actions for this configuration and its parents.
+        // Then, lock this configuration and its parents from mutation.
+        // After we observe a configuration (by building its metadata), its state should not change.
+        configuration.runDependencyActions();
+        configuration.markAsObserved(reason);
+    }
+
     private static CalculatedValue<ImmutableList<LocalComponentArtifactMetadata>> getVariantArtifacts(
         DisplayName displayName,
         ComponentIdentifier componentId,
diff --git a/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/DependencyGraphBuilder.java b/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/DependencyGraphBuilder.java
index 0a62636d9b943..d6d7303d7cbe4 100644
--- a/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/DependencyGraphBuilder.java
+++ b/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/DependencyGraphBuilder.java
@@ -56,7 +56,6 @@
 import org.gradle.internal.component.model.VariantGraphResolveMetadata;
 import org.gradle.internal.component.resolution.failure.ResolutionFailureHandler;
 import org.gradle.internal.component.resolution.failure.exception.AbstractResolutionFailureException;
-import org.gradle.internal.deprecation.DeprecationLogger;
 import org.gradle.internal.operations.BuildOperationConstraint;
 import org.gradle.internal.operations.BuildOperationExecutor;
 import org.gradle.internal.resolve.ModuleVersionResolveException;
@@ -436,22 +435,6 @@ private static void validateGraph(
                 attachMultipleForceOnPlatformFailureToEdges(module);
             }
         }
-
-        if (resolveState.getRoot().wasIncomingEdgeAdded()) {
-            String rootNodeName = resolveState.getRoot().getMetadata().getName();
-            DeprecationLogger.deprecate(
-                    String.format(
-                        "While resolving configuration '%s', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. " +
-                            "Depending on the resolved configuration in this manner",
-                        rootNodeName
-                    ))
-                .withProblemIdDisplayName("Configurations should not act as both a resolution root and a variant simultaneously.")
-                .withProblemId("configurations-acting-as-both-root-and-variant")
-                .withAdvice("Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
-                .willBecomeAnErrorInGradle9()
-                .withUpgradeGuideSection(8, "depending_on_root_configuration")
-                .nagUser();
-        }
     }
 
     private static boolean isDynamic(SelectorState selector) {
diff --git a/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/RootNode.java b/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/RootNode.java
index 836efab7ec34c..bf7e9938f9497 100644
--- a/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/RootNode.java
+++ b/platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/builder/RootNode.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder;
 
 import com.google.common.collect.ImmutableList;
+import org.gradle.api.InvalidUserCodeException;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.RootGraphNode;
 import org.gradle.internal.component.local.model.LocalFileDependencyMetadata;
 import org.gradle.internal.component.local.model.LocalVariantGraphResolveMetadata;
@@ -31,8 +32,6 @@ class RootNode extends NodeState implements RootGraphNode {
     private final ResolveOptimizations resolveOptimizations;
     private final List<? extends DependencyMetadata> syntheticDependencies;
 
-    boolean incomingEdgeWasAdded = false;
-
     RootNode(long resultId, ComponentState moduleRevision, ResolveState resolveState, List<? extends DependencyMetadata> syntheticDependencies, VariantGraphResolveState root) {
         super(resultId, moduleRevision, resolveState, root, false);
         this.resolveOptimizations = resolveState.getResolveOptimizations();
@@ -51,17 +50,11 @@ public Set<? extends LocalFileDependencyMetadata> getOutgoingFileEdges() {
 
     @Override
     void addIncomingEdge(EdgeState dependencyEdge) {
-        super.addIncomingEdge(dependencyEdge);
-        incomingEdgeWasAdded = true;
-
-        // TODO: We read `incomingEdgeWasAdded` at the end of graph resolution.
-        // If this method is ever called, we trigger a deprecation warning.
-        // In Gradle 9.0, we should fail here immediately if someone tries to
-        // add an incoming edge to a root node.
-    }
-
-    public boolean wasIncomingEdgeAdded() {
-        return incomingEdgeWasAdded;
+        throw new InvalidUserCodeException(
+            "Cannot select root node '" + getMetadata().getName() + "' as a variant. " +
+                "Configurations should not act as both a resolution root and a variant simultaneously. " +
+                "Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them."
+        );
     }
 
     @Override
diff --git a/testing/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/problems/KnownProblemIds.groovy b/testing/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/problems/KnownProblemIds.groovy
index 3243a7929f616..01721e40306ff 100644
--- a/testing/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/problems/KnownProblemIds.groovy
+++ b/testing/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/problems/KnownProblemIds.groovy
@@ -103,7 +103,6 @@ class KnownProblemIds {
         'deprecation:plugin': ['Plugin has been deprecated.'],
         'deprecation:plugin-script': ['Plugin script has been deprecated.'],
         'deprecation:the-detachedconfiguration-configuration-has-been-deprecated-for-consumption': ['The detachedConfiguration1 configuration has been deprecated for consumption.'],
-        'deprecation:configurations-acting-as-both-root-and-variant': ['Configurations should not act as both a resolution root and a variant simultaneously.'],
         'deprecation:properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl': ['Properties should be assigned using the \'propName = value\' syntax. Setting a property via the Gradle-generated \'propName value\' or \'propName(value)\' syntax in Groovy DSL has been deprecated.'],
         'deprecation:repository-jcenter': ['The RepositoryHandler.jcenter() method has been deprecated.'],
         'task-selection:no-matches': ['No matches', 'cannot locate task'],


Reviews:
user: login: tresat
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-26T14:43:20Z
user: login: jvandort
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:49:21Z
user: login: jvandort
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T15:04:46Z
user: login: jvandort
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T15:06:34Z

Review comments:
diff_hunk: @@ -128,13 +128,14 @@ class RootComponentResolutionIntegrationTest extends AbstractIntegrationSpec {
             }
         """
 
-        executer.expectDocumentedDeprecationWarning("While resolving configuration 'conf', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
+        when:
+        fails("resolve")
 
-        expect:
-        succeeds("resolve")
+        then:
+        failure.assertHasCause("Cannot select root node 'conf' as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
     }
 
-    def "configuration can resolve itself and reselect artifacts"() {
+    def "configuration cannot resolve itself and reselect artifacts"() {
path: platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RootComponentResolutionIntegrationTest.groovy
user: login: tresat
body: ❓ Is this really a different case from the one above?  Doesn't the resolution happen first, so the reselection is irrelevant as that code never even runs due to the earlier failure?
created_at: 2025-03-26T14:40:57Z
author_association: MEMBER
diff_hunk: @@ -32,9 +32,7 @@ class BuildscriptResolutionIntegrationTest extends AbstractIntegrationSpec {
         """
     }
 
-    // This is not desired behavior. A project dependency should refer to the actual
-    // project, not its buildscript component. This is a bug.
-    def "project buildscript configuration can select itself"() {
+    def "project buildscript configuration cannot select itself"() {
path: platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/BuildscriptResolutionIntegrationTest.groovy
user: login: tresat
body: 🤔 Is the foo attribute necessary here?  If not, this and the test below could be combined into parameterized test.
created_at: 2025-03-26T14:42:54Z
author_association: MEMBER
diff_hunk: @@ -522,8 +522,10 @@ class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec im
         }
     }
 
+    // TODO #9591: This does not reflect desired behavior. The recursive is a detached configuration, which
path: platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
user: login: jvandort
body: ```suggestion
    // TODO #9591: This does not reflect desired behavior. The recursive copy is a detached configuration, which
```
created_at: 2025-03-26T14:49:21Z
author_association: MEMBER
diff_hunk: @@ -128,13 +128,14 @@ class RootComponentResolutionIntegrationTest extends AbstractIntegrationSpec {
             }
         """
 
-        executer.expectDocumentedDeprecationWarning("While resolving configuration 'conf', it was also selected as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Depending on the resolved configuration in this manner has been deprecated. This will fail with an error in Gradle 9.0. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#depending_on_root_configuration")
+        when:
+        fails("resolve")
 
-        expect:
-        succeeds("resolve")
+        then:
+        failure.assertHasCause("Cannot select root node 'conf' as a variant. Configurations should not act as both a resolution root and a variant simultaneously. Be sure to mark configurations meant for resolution as canBeConsumed=false or use the 'resolvable(String)' configuration factory method to create them.")
     }
 
-    def "configuration can resolve itself and reselect artifacts"() {
+    def "configuration cannot resolve itself and reselect artifacts"() {
path: platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RootComponentResolutionIntegrationTest.groovy
user: login: jvandort
body: Yeah the error happens at graph resolution. Removed this test.
created_at: 2025-03-27T15:04:46Z
author_association: MEMBER
diff_hunk: @@ -32,9 +32,7 @@ class BuildscriptResolutionIntegrationTest extends AbstractIntegrationSpec {
         """
     }
 
-    // This is not desired behavior. A project dependency should refer to the actual
-    // project, not its buildscript component. This is a bug.
-    def "project buildscript configuration can select itself"() {
+    def "project buildscript configuration cannot select itself"() {
path: platforms/software/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/BuildscriptResolutionIntegrationTest.groovy
user: login: jvandort
body: `foo` is necessary to cause the configuration to be treated as a variant, as configurations without attributes are not variants. The second test works since the `classpath` configuration already has attributes. 
created_at: 2025-03-27T15:06:34Z
author_association: MEMBER

PR comments:
user: login: jvandort
created_at: 2025-03-25T15:18:33Z
body: @bot-gradle test this 
author_association: MEMBER
user: login: bot-gradle
created_at: 2025-03-25T15:18:37Z
body: <!-- {"replyTargetCommentId":2751626221,"triggeredTeamCityBuilds":[{"buildConfigurationId":"Gradle_Master_Check_Stage_PullRequestFeedback_Trigger","buildId":"96266344","headRef":"3c1800f7791a23ff24601bc2d509fb7354ede2f8"}]} -->
I've triggered the following builds:
- [Pull Request Feedback (Trigger) (Check)](https://builds.gradle.org/build/96266344)
  - [Build Scan](https://ge.gradle.org/scans?search.tags=CI&amp;search.names=gitCommitId,PartOf&amp;search.values=3c1800f7791a23ff24601bc2d509fb7354ede2f8,PullRequestFeedback&amp;)

author_association: COLLABORATOR
user: login: bot-gradle
created_at: 2025-03-25T16:41:20Z
body: The following builds have passed:
- [Pull Request Feedback (Trigger) (Check)](https://builds.gradle.org/buildConfiguration/Gradle_Master_Check_Stage_PullRequestFeedback_Trigger/96266344)
  - [Build Scan](https://ge.gradle.org/scans?search.tags=CI&amp;search.names=gitCommitId,PartOf&amp;search.values=3c1800f7791a23ff24601bc2d509fb7354ede2f8,PullRequestFeedback&amp;)

author_association: COLLABORATOR
user: login: jvandort
created_at: 2025-03-27T15:07:59Z
body: @bot-gradle test this
author_association: MEMBER
user: login: bot-gradle
created_at: 2025-03-27T15:08:06Z
body: <!-- {"replyTargetCommentId":2758410879,"triggeredTeamCityBuilds":[{"buildConfigurationId":"Gradle_Master_Check_Stage_PullRequestFeedback_Trigger","buildId":"96407680","headRef":"8421dfacc80eb15feb3dc627c989d22485f7f850"}]} -->
I've triggered the following builds:
- [Pull Request Feedback (Trigger) (Check)](https://builds.gradle.org/build/96407680)
  - [Build Scan](https://ge.gradle.org/scans?search.tags=CI&amp;search.names=gitCommitId,PartOf&amp;search.values=8421dfacc80eb15feb3dc627c989d22485f7f850,PullRequestFeedback&amp;)

author_association: COLLABORATOR
user: login: bot-gradle
created_at: 2025-03-27T16:15:19Z
body: The following builds have failed:
- [Pull Request Feedback (Trigger) (Check)](https://builds.gradle.org/buildConfiguration/Gradle_Master_Check_Stage_PullRequestFeedback_Trigger/96407680)
  - [Build Scan](https://ge.gradle.org/scans?search.buildOutcome=failure&amp;search.tags=CI&amp;search.names=gitCommitId&amp;search.values=8421dfacc80eb15feb3dc627c989d22485f7f850&amp;)

author_association: COLLABORATOR