Merge pull request #17819 Don't start another daemon process from launcher when the first one is not accessible
diff --git a/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt b/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt
index c34cdbf..f06158f 100644
--- a/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt
+++ b/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt
@@ -70,15 +70,15 @@
     }
 }
 
-fun BuildFeatures.triggeredOnPullRequests() {
+fun BuildFeatures.triggeredOnPullRequests(model: CIBuildModel) {
     pullRequests {
-        vcsRootExtId = "GradleMaster"
+        vcsRootExtId = "Gradle_Branches_GradlePersonalBranches"
         provider = github {
             authType = token {
                 token = "%github.bot-gradle.token%"
             }
             filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER
-            filterTargetBranch = branchesFilterExcluding()
+            filterTargetBranch = model.branch.branchFilter()
         }
     }
 }
diff --git a/.teamcity/src/main/kotlin/configurations/SanityCheck.kt b/.teamcity/src/main/kotlin/configurations/SanityCheck.kt
index 6dd855e..4ed6e07 100644
--- a/.teamcity/src/main/kotlin/configurations/SanityCheck.kt
+++ b/.teamcity/src/main/kotlin/configurations/SanityCheck.kt
@@ -10,7 +10,7 @@
 
     features {
         publishBuildStatusToGithub(model)
-        triggeredOnPullRequests()
+        triggeredOnPullRequests(model)
     }
 
     applyDefaults(
diff --git a/.teamcity/src/main/kotlin/promotion/PromotionProject.kt b/.teamcity/src/main/kotlin/promotion/PromotionProject.kt
index d84ea88..6a205d4 100644
--- a/.teamcity/src/main/kotlin/promotion/PromotionProject.kt
+++ b/.teamcity/src/main/kotlin/promotion/PromotionProject.kt
@@ -26,13 +26,13 @@
 
     params {
         password("env.ORG_GRADLE_PROJECT_gradleS3SecretKey", "%gradleS3SecretKey%")
-        password("env.ORG_GRADLE_PROJECT_artifactoryUserPassword", "%artifactoryUserPassword%")
+        password("env.ORG_GRADLE_PROJECT_artifactoryUserPassword", "%gradle.internal.repository.build-tool.publish.password%")
         param("env.ORG_GRADLE_PROJECT_gradleS3AccessKey", "AKIAQBZWBNAJCJGCAMFL")
         password("env.DOTCOM_DEV_DOCS_AWS_SECRET_KEY", "%dotcomDevDocsAwsSecretKey%")
         param("env.DOTCOM_DEV_DOCS_AWS_ACCESS_KEY", "AKIAX5VJCER2X7DPYFXF")
         password("env.ORG_GRADLE_PROJECT_sdkmanToken", "%sdkmanToken%")
         param("env.JAVA_HOME", javaHome(BuildToolBuildJvm, Os.LINUX))
-        param("env.ORG_GRADLE_PROJECT_artifactoryUserName", "bot-build-tool")
+        param("env.ORG_GRADLE_PROJECT_artifactoryUserName", "%gradle.internal.repository.build-tool.publish.username%")
         password("env.ORG_GRADLE_PROJECT_infrastructureEmailPwd", "%infrastructureEmailPwd%")
         param("env.ORG_GRADLE_PROJECT_sdkmanKey", "8ed1a771bc236c287ad93c699bfdd2d7")
     }
diff --git a/build-logic-commons/gradle-plugin/src/main/kotlin/gradlebuild.build-logic.groovy-dsl-gradle-plugin.gradle.kts b/build-logic-commons/gradle-plugin/src/main/kotlin/gradlebuild.build-logic.groovy-dsl-gradle-plugin.gradle.kts
index e16e3f8..91a8304 100644
--- a/build-logic-commons/gradle-plugin/src/main/kotlin/gradlebuild.build-logic.groovy-dsl-gradle-plugin.gradle.kts
+++ b/build-logic-commons/gradle-plugin/src/main/kotlin/gradlebuild.build-logic.groovy-dsl-gradle-plugin.gradle.kts
@@ -31,7 +31,6 @@
 
     implementation(localGroovy())
     testImplementation("org.spockframework:spock-core")
-    testImplementation("org.spockframework:spock-junit4")
     testImplementation("net.bytebuddy:byte-buddy")
     testImplementation("org.objenesis:objenesis")
 }
diff --git a/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/AcceptedApiChangesJsonFileManagerTest.groovy b/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/AcceptedApiChangesJsonFileManagerTest.groovy
index b69cba9..b0cde03 100644
--- a/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/AcceptedApiChangesJsonFileManagerTest.groovy
+++ b/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/AcceptedApiChangesJsonFileManagerTest.groovy
@@ -16,22 +16,21 @@
 
 package gradlebuild.binarycompatibility
 
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
 import spock.lang.Subject
+import spock.lang.TempDir
 
 class AcceptedApiChangesJsonFileManagerTest extends Specification {
 
-    @Rule
-    TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    File temporaryFolder
 
     @Subject AcceptedApiChangesJsonFileManager jsonFileManager = new AcceptedApiChangesJsonFileManager()
 
     def jsonFile
 
     def setup() {
-        jsonFile = temporaryFolder.newFile('acceptedChanges.json')
+        jsonFile = new File(temporaryFolder, 'acceptedChanges.json')
     }
 
     def "can clean existing API changes"() {
diff --git a/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/PublicAPIRulesTest.groovy b/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/PublicAPIRulesTest.groovy
index d1afeb6..99be800 100644
--- a/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/PublicAPIRulesTest.groovy
+++ b/build-logic/binary-compatibility/src/test/groovy/gradlebuild/binarycompatibility/PublicAPIRulesTest.groovy
@@ -30,10 +30,9 @@
 import me.champeau.gradle.japicmp.report.Severity
 import me.champeau.gradle.japicmp.report.ViolationCheckContext
 import org.gradle.api.Incubating
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
 import spock.lang.Unroll
+import spock.lang.TempDir
 
 import javax.inject.Inject
 
@@ -41,8 +40,8 @@
     private final static String TEST_INTERFACE_NAME = 'org.gradle.api.ApiTest'
     private final static String TEST_INTERFACE_SIMPLE_NAME = 'ApiTest'
 
-    @Rule
-    TemporaryFolder tmp = new TemporaryFolder()
+    @TempDir
+    File tmp
     File sourceFile
 
     BinaryCompatibilityRepository repository
@@ -57,8 +56,8 @@
     def injectAnnotation = Stub(JApiAnnotation)
 
     def setup() {
-        new File(tmp.root, "org/gradle/api").mkdirs()
-        sourceFile = tmp.newFile("${TEST_INTERFACE_NAME.replace('.', '/')}.java")
+        new File(tmp, "org/gradle/api").mkdirs()
+        sourceFile = new File(tmp, "${TEST_INTERFACE_NAME.replace('.', '/')}.java").tap { text = "" }
 
         jApiClassifier.fullyQualifiedName >> TEST_INTERFACE_NAME
         jApiField.name >> 'field'
@@ -73,7 +72,7 @@
         overrideAnnotation.fullyQualifiedName >> Override.name
         injectAnnotation.fullyQualifiedName >> Inject.name
 
-        repository = BinaryCompatibilityRepository.openRepositoryFor([new File(tmp.root.absolutePath)], [])
+        repository = BinaryCompatibilityRepository.openRepositoryFor([new File(tmp.absolutePath)], [])
     }
 
     def cleanup() {
diff --git a/build-logic/build-update-utils/src/main/kotlin/gradlebuild/buildutils/tasks/UpdateInitPluginTemplateVersionFile.kt b/build-logic/build-update-utils/src/main/kotlin/gradlebuild/buildutils/tasks/UpdateInitPluginTemplateVersionFile.kt
index 9d0a7a6..4c6c4d3 100644
--- a/build-logic/build-update-utils/src/main/kotlin/gradlebuild/buildutils/tasks/UpdateInitPluginTemplateVersionFile.kt
+++ b/build-logic/build-update-utils/src/main/kotlin/gradlebuild/buildutils/tasks/UpdateInitPluginTemplateVersionFile.kt
@@ -25,7 +25,7 @@
 import org.gradle.api.tasks.Internal
 import org.gradle.api.tasks.TaskAction
 import org.gradle.internal.util.PropertiesUtils
-import org.gradle.util.VersionNumber
+import org.gradle.util.internal.VersionNumber
 import org.gradle.work.DisableCachingByDefault
 import java.util.Properties
 
diff --git a/build-logic/build-update-utils/src/test/groovy/gradlebuild/buildutils/tasks/UpdateReleasedVersionsIntegrationTest.groovy b/build-logic/build-update-utils/src/test/groovy/gradlebuild/buildutils/tasks/UpdateReleasedVersionsIntegrationTest.groovy
index 58b0d20..79394d8 100644
--- a/build-logic/build-update-utils/src/test/groovy/gradlebuild/buildutils/tasks/UpdateReleasedVersionsIntegrationTest.groovy
+++ b/build-logic/build-update-utils/src/test/groovy/gradlebuild/buildutils/tasks/UpdateReleasedVersionsIntegrationTest.groovy
@@ -17,9 +17,8 @@
 package gradlebuild.buildutils.tasks
 
 import gradlebuild.buildutils.model.ReleasedVersion
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import java.text.SimpleDateFormat
 
@@ -27,8 +26,8 @@
 
     def format = new SimpleDateFormat('yyyyMMddHHmmssZ')
 
-    @Rule
-    TemporaryFolder tmpDir
+    @TempDir
+    File tmpDir
 
     def setup() {
         format.timeZone = TimeZone.getTimeZone("UTC")
@@ -36,7 +35,7 @@
 
     def "updated released version file has expected format"() {
         given:
-        def releasedVersionsFile = tmpDir.newFile("released-versions.json")
+        def releasedVersionsFile = new File(tmpDir, "released-versions.json")
         releasedVersionsFile << '''{
   "latestReleaseSnapshot": {
     "version": "6.6-20200702230251+0000",
diff --git a/build-logic/documentation/src/test/groovy/gradlebuild/docs/model/SimpleClassMetaDataRepositoryTest.groovy b/build-logic/documentation/src/test/groovy/gradlebuild/docs/model/SimpleClassMetaDataRepositoryTest.groovy
index f276527..9681644 100644
--- a/build-logic/documentation/src/test/groovy/gradlebuild/docs/model/SimpleClassMetaDataRepositoryTest.groovy
+++ b/build-logic/documentation/src/test/groovy/gradlebuild/docs/model/SimpleClassMetaDataRepositoryTest.groovy
@@ -15,15 +15,14 @@
  */
 package gradlebuild.docs.model
 
-
+import java.nio.file.Files
 import org.gradle.api.Action
 import org.gradle.api.UnknownDomainObjectException
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class SimpleClassMetaDataRepositoryTest extends Specification {
-    @Rule TemporaryFolder tmpDir
+    @TempDir File tmpDir
     final SimpleClassMetaDataRepository<TestDomainObject> repository = new SimpleClassMetaDataRepository<TestDomainObject>()
 
     def canAddMetaData() {
@@ -100,7 +99,7 @@
 
     def canPersistMetaData() {
         TestDomainObject value = new TestDomainObject('a')
-        File file = tmpDir.newFile()
+        File file = Files.createTempFile(tmpDir.toPath(), null, null).toFile()
         repository.put('class', value)
 
         when:
diff --git a/build-logic/jvm/src/main/kotlin/gradlebuild/jvm/argumentproviders/CiEnvironmentProvider.kt b/build-logic/jvm/src/main/kotlin/gradlebuild/jvm/argumentproviders/CiEnvironmentProvider.kt
index 5260630..6c1628e 100644
--- a/build-logic/jvm/src/main/kotlin/gradlebuild/jvm/argumentproviders/CiEnvironmentProvider.kt
+++ b/build-logic/jvm/src/main/kotlin/gradlebuild/jvm/argumentproviders/CiEnvironmentProvider.kt
@@ -49,7 +49,7 @@
     private
     fun collectMirrorUrls(): Map<String, String> =
         // expected env var format: repo1_id:repo1_url,repo2_id:repo2_url,...
-        System.getenv("REPO_MIRROR_GRDEV_URLS")?.ifBlank { null }?.split(',')?.associate { nameToUrl ->
+        System.getenv("REPO_MIRROR_URLS")?.ifBlank { null }?.split(',')?.associate { nameToUrl ->
             val (name, url) = nameToUrl.split(':', limit = 2)
             name to url
         } ?: emptyMap()
diff --git a/build-logic/jvm/src/main/kotlin/gradlebuild/propagated-env-variables.kt b/build-logic/jvm/src/main/kotlin/gradlebuild/propagated-env-variables.kt
index 0e58c56..75be39f 100644
--- a/build-logic/jvm/src/main/kotlin/gradlebuild/propagated-env-variables.kt
+++ b/build-logic/jvm/src/main/kotlin/gradlebuild/propagated-env-variables.kt
@@ -44,7 +44,7 @@
     "LOCALAPPDATA",
 
     // Used by Gradle test infrastructure
-    "REPO_MIRROR_URL",
+    "REPO_MIRROR_URLS",
 
     // Used to find local java installations
     "SDKMAN_CANDIDATES_DIR",
diff --git a/gradle/shared-with-buildSrc/mirrors.settings.gradle.kts b/gradle/shared-with-buildSrc/mirrors.settings.gradle.kts
index 395dde2..48a05bc 100644
--- a/gradle/shared-with-buildSrc/mirrors.settings.gradle.kts
+++ b/gradle/shared-with-buildSrc/mirrors.settings.gradle.kts
@@ -25,14 +25,14 @@
     "mavencentral" to "https://repo.maven.apache.org/maven2/",
     "google" to "https://dl.google.com/dl/android/maven2/",
     "gradle" to "https://repo.gradle.org/gradle/repo",
-    "gradleplugins" to "https://plugins.gradle.org/m2",
+    "gradle-prod-plugins" to "https://plugins.gradle.org/m2",
     "gradlejavascript" to "https://repo.gradle.org/gradle/javascript-public",
     "gradle-public" to "https://repo.gradle.org/gradle/public",
-    "gradle-enterprise-plugin-rc" to "https://repo.gradle.org/gradle/enterprise-libs-release-candidates"
+    "gradle-enterprise-rc" to "https://repo.gradle.org/gradle/enterprise-libs-release-candidates"
 )
 
 val mirrorUrls: Map<String, String> =
-    providers.environmentVariable("REPO_MIRROR_GRDEV_URLS").forUseAtConfigurationTime().orNull
+    providers.environmentVariable("REPO_MIRROR_URLS").forUseAtConfigurationTime().orNull
         ?.ifBlank { null }
         ?.split(',')
         ?.associate { nameToUrl ->
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e92f59c..ffed3a2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-rc-3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/released-versions.json b/released-versions.json
index a4d09c2..ad5f67a6 100644
--- a/released-versions.json
+++ b/released-versions.json
@@ -1,7 +1,7 @@
 {
   "latestReleaseSnapshot": {
-    "version": "7.2-20210816012935+0000",
-    "buildTime": "20210816012935+0000"
+    "version": "7.2-20210818010647+0000",
+    "buildTime": "20210818010647+0000"
   },
   "latestRc": {
     "version": "7.2-rc-3",
@@ -9,6 +9,10 @@
   },
   "finalReleases": [
     {
+      "version": "7.2",
+      "buildTime": "20210817095903+0000"
+    },
+    {
       "version": "7.1.1",
       "buildTime": "20210702121643+0000"
     },
diff --git a/subprojects/build-events/src/main/java/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistry.java b/subprojects/build-events/src/main/java/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistry.java
index f4930e2..370ed09 100644
--- a/subprojects/build-events/src/main/java/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistry.java
+++ b/subprojects/build-events/src/main/java/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistry.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import org.gradle.BuildAdapter;
 import org.gradle.BuildResult;
+import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.provider.ProviderInternal;
 import org.gradle.api.internal.provider.Providers;
 import org.gradle.api.provider.Provider;
@@ -68,8 +69,10 @@ public class DefaultBuildEventsListenerRegistry implements BuildEventsListenerRe
     private final List<Object> listeners = new ArrayList<>();
     private final ExecutorFactory executorFactory;
 
-    public DefaultBuildEventsListenerRegistry(BuildEventListenerFactory factory, ListenerManager listenerManager,
-                                              BuildOperationListenerManager buildOperationListenerManager, ExecutorFactory executorFactory) {
+    public DefaultBuildEventsListenerRegistry(
+        BuildEventListenerFactory factory, ListenerManager listenerManager,
+        BuildOperationListenerManager buildOperationListenerManager, ExecutorFactory executorFactory
+    ) {
         this.factory = factory;
         this.listenerManager = listenerManager;
         this.buildOperationListenerManager = buildOperationListenerManager;
@@ -237,7 +240,7 @@ private class ListenerCleanup extends BuildAdapter {
         @Override
         public void buildFinished(BuildResult result) {
             // TODO - maybe make the registry a build scoped service
-            if (result.getGradle().getParent() != null) {
+            if (!((GradleInternal) result.getGradle()).isRootBuild()) {
                 // Stop only when the root build completes
                 return;
             }
diff --git a/subprojects/build-events/src/test/groovy/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistryTest.groovy b/subprojects/build-events/src/test/groovy/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistryTest.groovy
index 3e30127..335d414 100644
--- a/subprojects/build-events/src/test/groovy/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistryTest.groovy
+++ b/subprojects/build-events/src/test/groovy/org/gradle/internal/build/event/DefaultBuildEventsListenerRegistryTest.groovy
@@ -46,7 +46,10 @@
     def factory = new MockBuildEventListenerFactory()
     def listenerManager = new DefaultListenerManager(Scopes.Build)
     def buildOperationListenerManager = new DefaultBuildOperationListenerManager()
-    def buildResult = new BuildResult(Mock(GradleInternal), null)
+    def gradle = Stub(GradleInternal) {
+        isRootBuild() >> true
+    }
+    def buildResult = new BuildResult(gradle, null)
     def registry = new DefaultBuildEventsListenerRegistry(factory, listenerManager, buildOperationListenerManager, executorFactory)
 
     def cleanup() {
@@ -120,6 +123,9 @@
 
         then:
         registry.subscriptions.size() == 1
+
+        cleanup:
+        signalBuildFinished()
     }
 
     def "listeners receive events concurrently"() {
diff --git a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy
index 6f69c55..3845904 100644
--- a/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy
+++ b/subprojects/build-init/src/test/groovy/org/gradle/buildinit/plugins/internal/SimpleTemplateOperationSpec.groovy
@@ -16,13 +16,12 @@
 
 package org.gradle.buildinit.plugins.internal
 
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class SimpleTemplateOperationSpec extends Specification {
-    @Rule
-    TemporaryFolder temporaryFolder
+    @TempDir
+    File temporaryFolder
 
     def "Template URL must not be null"() {
         when:
@@ -42,7 +41,7 @@
 
     def "writes file from template with binding support"() {
         setup:
-        def targetFile = temporaryFolder.newFile("test.out")
+        def targetFile = new File(temporaryFolder, "test.out")
         def templateURL = getClass().getResource("SimpleTemplateOperationSpec-binding.template")
         def templateOperation = new SimpleTemplateOperation(templateURL, targetFile, [someBindedValue: new TemplateValue("someTemplateValue")])
 
diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/BuildStateFactory.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/BuildStateFactory.java
index 7688f09..8f8b1af 100644
--- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/BuildStateFactory.java
+++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/BuildStateFactory.java
@@ -16,12 +16,16 @@
 
 package org.gradle.composite.internal;
 
+import org.gradle.StartParameter;
 import org.gradle.api.artifacts.component.BuildIdentifier;
 import org.gradle.api.internal.BuildDefinition;
+import org.gradle.api.internal.StartParameterInternal;
 import org.gradle.api.internal.project.ProjectStateRegistry;
 import org.gradle.initialization.BuildCancellationToken;
+import org.gradle.internal.Actions;
 import org.gradle.internal.build.BuildLifecycleControllerFactory;
 import org.gradle.internal.build.BuildState;
+import org.gradle.internal.build.PublicBuildPath;
 import org.gradle.internal.build.RootBuildState;
 import org.gradle.internal.build.StandAloneNestedBuild;
 import org.gradle.internal.buildtree.BuildTreeState;
@@ -31,8 +35,13 @@
 import org.gradle.internal.service.scopes.Scopes;
 import org.gradle.internal.service.scopes.ServiceScope;
 import org.gradle.internal.session.CrossBuildSessionState;
+import org.gradle.plugin.management.internal.PluginRequests;
 import org.gradle.util.Path;
 
+import java.io.File;
+
+import static org.gradle.api.internal.SettingsInternal.BUILD_SRC;
+
 @ServiceScope(Scopes.BuildTree.class)
 public class BuildStateFactory {
     private final BuildTreeState buildTreeState;
@@ -43,13 +52,15 @@ public class BuildStateFactory {
     private final BuildCancellationToken buildCancellationToken;
     private final ProjectStateRegistry projectStateRegistry;
 
-    public BuildStateFactory(BuildTreeState buildTreeState,
-                             BuildLifecycleControllerFactory buildLifecycleControllerFactory,
-                             ListenerManager listenerManager,
-                             GradleUserHomeScopeServiceRegistry userHomeDirServiceRegistry,
-                             CrossBuildSessionState crossBuildSessionState,
-                             BuildCancellationToken buildCancellationToken,
-                             ProjectStateRegistry projectStateRegistry) {
+    public BuildStateFactory(
+        BuildTreeState buildTreeState,
+        BuildLifecycleControllerFactory buildLifecycleControllerFactory,
+        ListenerManager listenerManager,
+        GradleUserHomeScopeServiceRegistry userHomeDirServiceRegistry,
+        CrossBuildSessionState crossBuildSessionState,
+        BuildCancellationToken buildCancellationToken,
+        ProjectStateRegistry projectStateRegistry
+    ) {
         this.buildTreeState = buildTreeState;
         this.buildLifecycleControllerFactory = buildLifecycleControllerFactory;
         this.listenerManager = listenerManager;
@@ -64,13 +75,45 @@ public RootBuildState createRootBuild(BuildDefinition buildDefinition) {
     }
 
     public StandAloneNestedBuild createNestedBuild(BuildIdentifier buildIdentifier, Path identityPath, BuildDefinition buildDefinition, BuildState owner) {
-        return new DefaultNestedBuild(buildIdentifier, identityPath, buildDefinition, owner, buildTreeState, buildLifecycleControllerFactory, projectStateRegistry);
+        DefaultNestedBuild build = new DefaultNestedBuild(buildIdentifier, identityPath, buildDefinition, owner, buildTreeState, buildLifecycleControllerFactory, projectStateRegistry);
+        // Expose any contributions from the parent's settings
+        build.getMutableModel().setClassLoaderScope(() -> owner.getMutableModel().getSettings().getClassLoaderScope());
+        return build;
     }
 
-    public NestedBuildTree createNestedTree(BuildDefinition buildDefinition,
-                                            BuildIdentifier buildIdentifier,
-                                            Path identityPath,
-                                            BuildState owner) {
+    public NestedBuildTree createNestedTree(
+        BuildDefinition buildDefinition,
+        BuildIdentifier buildIdentifier,
+        Path identityPath,
+        BuildState owner
+    ) {
         return new DefaultNestedBuildTree(buildDefinition, buildIdentifier, identityPath, owner, userHomeDirServiceRegistry, crossBuildSessionState, buildCancellationToken);
     }
+
+    public BuildDefinition buildDefinitionFor(File buildSrcDir, BuildState owner) {
+        PublicBuildPath publicBuildPath = owner.getMutableModel().getServices().get(PublicBuildPath.class);
+        StartParameterInternal buildSrcStartParameter = buildSrcStartParameterFor(buildSrcDir, owner.getMutableModel().getStartParameter());
+        BuildDefinition buildDefinition = BuildDefinition.fromStartParameterForBuild(
+            buildSrcStartParameter,
+            BUILD_SRC,
+            buildSrcDir,
+            PluginRequests.EMPTY,
+            Actions.doNothing(),
+            publicBuildPath,
+            true
+        );
+        @SuppressWarnings("deprecation")
+        File customBuildFile = buildSrcStartParameter.getBuildFile();
+        assert customBuildFile == null;
+        return buildDefinition;
+    }
+
+    private StartParameterInternal buildSrcStartParameterFor(File buildSrcDir, StartParameter containingBuildParameters) {
+        final StartParameterInternal buildSrcStartParameter = (StartParameterInternal) containingBuildParameters.newBuild();
+        buildSrcStartParameter.setCurrentDir(buildSrcDir);
+        buildSrcStartParameter.setProjectProperties(containingBuildParameters.getProjectProperties());
+        buildSrcStartParameter.doNotSearchUpwards();
+        buildSrcStartParameter.setProfile(containingBuildParameters.isProfile());
+        return buildSrcStartParameter;
+    }
 }
diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuild.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuild.java
index d736026..496faf6 100644
--- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuild.java
+++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuild.java
@@ -23,7 +23,6 @@
 import org.gradle.api.artifacts.component.BuildIdentifier;
 import org.gradle.api.internal.BuildDefinition;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.ProjectStateRegistry;
 import org.gradle.api.tasks.TaskReference;
 import org.gradle.initialization.IncludedBuildSpec;
@@ -152,7 +151,7 @@ public Path getCurrentPrefixForProjectsInChildBuilds() {
     }
 
     @Override
-    public Path getIdentityPathForProject(Path projectPath) {
+    public Path calculateIdentityPathForProject(Path projectPath) {
         return getIdentityPath().append(projectPath);
     }
 
@@ -162,21 +161,6 @@ public Action<? super DependencySubstitutions> getRegisteredDependencySubstituti
     }
 
     @Override
-    public boolean hasInjectedSettingsPlugins() {
-        return !buildDefinition.getInjectedPluginRequests().isEmpty();
-    }
-
-    @Override
-    public SettingsInternal loadSettings() {
-        return buildLifecycleController.getLoadedSettings();
-    }
-
-    @Override
-    public SettingsInternal getLoadedSettings() {
-        return getGradle().getSettings();
-    }
-
-    @Override
     public GradleInternal getConfiguredBuild() {
         return buildLifecycleController.getConfiguredBuild();
     }
diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildRegistry.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildRegistry.java
index 9d9be6d..8028478 100644
--- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildRegistry.java
+++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildRegistry.java
@@ -23,6 +23,7 @@
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.artifacts.DefaultBuildIdentifier;
+import org.gradle.initialization.buildsrc.BuildSrcDetector;
 import org.gradle.internal.build.BuildAddedListener;
 import org.gradle.internal.build.BuildState;
 import org.gradle.internal.build.BuildStateRegistry;
@@ -60,6 +61,7 @@ public class DefaultIncludedBuildRegistry implements BuildStateRegistry, Stoppab
     // TODO: Locking around this state
     private RootBuildState rootBuild;
     private final Map<BuildIdentifier, BuildState> buildsByIdentifier = new HashMap<>();
+    private final Map<BuildState, StandAloneNestedBuild> buildSrcBuildsByOwner = new HashMap<>();
     private final Map<File, IncludedBuildState> includedBuildsByRootDir = new LinkedHashMap<>();
     private final Map<Path, File> includedBuildDirectoriesByPath = new LinkedHashMap<>();
     private final Deque<IncludedBuildState> pendingIncludedBuilds = new ArrayDeque<>();
@@ -108,6 +110,7 @@ private void addBuild(BuildState build) {
             throw new IllegalArgumentException("Build is already registered: " + build.getBuildIdentifier());
         }
         buildAddedBroadcaster.buildAdded(build);
+        maybeAddBuildSrcBuild(build);
     }
 
     @Override
@@ -182,15 +185,22 @@ private void registerSubstitutions() {
     }
 
     @Override
-    public StandAloneNestedBuild addBuildSrcNestedBuild(BuildDefinition buildDefinition, BuildState owner) {
-        if (!SettingsInternal.BUILD_SRC.equals(buildDefinition.getName())) {
-            throw new IllegalStateException("Expected buildSrc build, got: " + buildDefinition.getName());
+    public StandAloneNestedBuild getBuildSrcNestedBuild(BuildState owner) {
+        return buildSrcBuildsByOwner.get(owner);
+    }
+
+    private void maybeAddBuildSrcBuild(BuildState owner) {
+        File buildSrcDir = new File(owner.getBuildRootDir(), SettingsInternal.BUILD_SRC);
+        if (!BuildSrcDetector.isValidBuildSrcBuild(buildSrcDir)) {
+            return;
         }
+
+        BuildDefinition buildDefinition = buildStateFactory.buildDefinitionFor(buildSrcDir, owner);
         Path identityPath = assignPath(owner, buildDefinition.getName(), buildDefinition.getBuildRootDir());
         BuildIdentifier buildIdentifier = idFor(buildDefinition.getName());
         StandAloneNestedBuild build = buildStateFactory.createNestedBuild(buildIdentifier, identityPath, buildDefinition, owner);
+        buildSrcBuildsByOwner.put(owner, build);
         addBuild(build);
-        return build;
     }
 
     @Override
@@ -313,7 +323,7 @@ public void stop() {
     }
 
     private void assertNameDoesNotClashWithRootSubproject(IncludedBuildState includedBuild) {
-        if (rootBuild.getLoadedSettings().findProject(":" + includedBuild.getName()) != null) {
+        if (rootBuild.getProjects().findProject(includedBuild.getIdentityPath()) != null) {
             throw new GradleException("Included build in " + includedBuild.getBuildRootDir() + " has name '" + includedBuild.getName() + "' which is the same as a project of the main build.");
         }
     }
diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultNestedBuild.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultNestedBuild.java
index 1a691fa..156402d 100644
--- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultNestedBuild.java
+++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultNestedBuild.java
@@ -19,7 +19,6 @@
 import org.gradle.api.artifacts.component.BuildIdentifier;
 import org.gradle.api.internal.BuildDefinition;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.ProjectStateRegistry;
 import org.gradle.initialization.exception.ExceptionAnalyser;
 import org.gradle.internal.build.AbstractBuildState;
@@ -52,7 +51,6 @@ class DefaultNestedBuild extends AbstractBuildState implements StandAloneNestedB
     private final BuildDefinition buildDefinition;
     private final BuildLifecycleController buildLifecycleController;
     private final BuildTreeLifecycleController buildTreeLifecycleController;
-    private final BuildScopeServices buildScopeServices;
 
     DefaultNestedBuild(
         BuildIdentifier buildIdentifier,
@@ -69,7 +67,7 @@ class DefaultNestedBuild extends AbstractBuildState implements StandAloneNestedB
         this.owner = owner;
         this.projectStateRegistry = projectStateRegistry;
 
-        buildScopeServices = new BuildScopeServices(buildTree.getServices());
+        BuildScopeServices buildScopeServices = new BuildScopeServices(buildTree.getServices());
         this.buildLifecycleController = buildLifecycleControllerFactory.newInstance(buildDefinition, this, owner, buildScopeServices);
 
         IncludedBuildTaskGraph taskGraph = buildScopeServices.get(IncludedBuildTaskGraph.class);
@@ -131,17 +129,12 @@ public <T> T run(Function<? super BuildTreeLifecycleController, T> buildAction)
     }
 
     @Override
-    public SettingsInternal getLoadedSettings() {
-        return buildLifecycleController.getGradle().getSettings();
-    }
-
-    @Override
     public Path getCurrentPrefixForProjectsInChildBuilds() {
         return owner.getCurrentPrefixForProjectsInChildBuilds().child(buildDefinition.getName());
     }
 
     @Override
-    public Path getIdentityPathForProject(Path projectPath) {
+    public Path calculateIdentityPathForProject(Path projectPath) {
         return buildLifecycleController.getGradle().getIdentityPath().append(projectPath);
     }
 
diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultRootBuildState.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultRootBuildState.java
index b81a875..6bca348 100644
--- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultRootBuildState.java
+++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultRootBuildState.java
@@ -20,7 +20,6 @@
 import org.gradle.api.artifacts.component.BuildIdentifier;
 import org.gradle.api.internal.BuildDefinition;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.StartParameterInternal;
 import org.gradle.api.internal.artifacts.DefaultBuildIdentifier;
 import org.gradle.api.internal.project.ProjectStateRegistry;
@@ -168,17 +167,12 @@ public StartParameterInternal getStartParameter() {
     }
 
     @Override
-    public SettingsInternal getLoadedSettings() {
-        return buildLifecycleController.getGradle().getSettings();
-    }
-
-    @Override
     public Path getCurrentPrefixForProjectsInChildBuilds() {
         return Path.ROOT;
     }
 
     @Override
-    public Path getIdentityPathForProject(Path path) {
+    public Path calculateIdentityPathForProject(Path path) {
         return path;
     }
 
diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/RootOfNestedBuildTree.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/RootOfNestedBuildTree.java
index 46f0f7c..062ae22 100644
--- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/RootOfNestedBuildTree.java
+++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/RootOfNestedBuildTree.java
@@ -22,7 +22,6 @@
 import org.gradle.api.initialization.Settings;
 import org.gradle.api.internal.BuildDefinition;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.StartParameterInternal;
 import org.gradle.api.internal.project.ProjectStateRegistry;
 import org.gradle.initialization.RunNestedBuildBuildOperationType;
@@ -141,17 +140,12 @@ public boolean isImplicitBuild() {
     }
 
     @Override
-    public SettingsInternal getLoadedSettings() {
-        return buildLifecycleController.getGradle().getSettings();
-    }
-
-    @Override
     public Path getCurrentPrefixForProjectsInChildBuilds() {
         return owner.getCurrentPrefixForProjectsInChildBuilds().child(buildName);
     }
 
     @Override
-    public Path getIdentityPathForProject(Path projectPath) {
+    public Path calculateIdentityPathForProject(Path projectPath) {
         return buildLifecycleController.getGradle().getIdentityPath().append(projectPath);
     }
 
diff --git a/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/DefaultIncludedBuildRegistryTest.groovy b/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/DefaultIncludedBuildRegistryTest.groovy
index 3f332fe..eac7ea9c7 100644
--- a/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/DefaultIncludedBuildRegistryTest.groovy
+++ b/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/DefaultIncludedBuildRegistryTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.composite.internal
 
-import org.gradle.api.GradleException
+
 import org.gradle.api.initialization.ProjectDescriptor
 import org.gradle.api.internal.BuildDefinition
 import org.gradle.api.internal.GradleInternal
@@ -26,6 +26,7 @@
 import org.gradle.api.internal.project.ProjectStateRegistry
 import org.gradle.initialization.BuildCancellationToken
 import org.gradle.initialization.exception.ExceptionAnalyser
+import org.gradle.initialization.layout.BuildLayout
 import org.gradle.internal.Actions
 import org.gradle.internal.build.BuildAddedListener
 import org.gradle.internal.build.BuildLifecycleController
@@ -34,6 +35,7 @@
 import org.gradle.internal.build.BuildStateRegistry
 import org.gradle.internal.build.IncludedBuildFactory
 import org.gradle.internal.build.IncludedBuildState
+import org.gradle.internal.build.PublicBuildPath
 import org.gradle.internal.build.RootBuildState
 import org.gradle.internal.buildtree.BuildModelParameters
 import org.gradle.internal.buildtree.BuildTreeLifecycleControllerFactory
@@ -41,10 +43,12 @@
 import org.gradle.internal.event.ListenerManager
 import org.gradle.internal.operations.BuildOperationExecutor
 import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.internal.service.scopes.GradleUserHomeScopeServiceRegistry
 import org.gradle.internal.session.CrossBuildSessionState
 import org.gradle.internal.work.WorkerLeaseService
 import org.gradle.plugin.management.internal.PluginRequests
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Path
 import org.junit.Rule
@@ -90,15 +94,13 @@
     def "can add a root build"() {
         def notifiedBuild
         def buildDefinition = Stub(BuildDefinition)
-        def gradleLauncher = Stub(BuildLifecycleController)
-        def gradle = Stub(GradleInternal)
+        def buildController = buildController()
 
         when:
         def rootBuild = registry.createRootBuild(buildDefinition)
 
         then:
-        _ * gradleLauncher.gradle >> gradle
-        1 * gradleLauncherFactory.newInstance(buildDefinition, _, null, _) >> gradleLauncher
+        1 * gradleLauncherFactory.newInstance(buildDefinition, _, null, _) >> buildController
         1 * buildAddedListener.buildAdded(_) >> { BuildState addedBuild ->
             notifiedBuild = addedBuild
         }
@@ -118,6 +120,7 @@
         def buildIdentifier = new DefaultBuildIdentifier("b1")
         def idPath = Path.path(":b1")
         includedBuild.buildIdentifier >> buildIdentifier
+        includedBuild.buildRootDir >> dir
 
         given:
         registry.attachRootBuild(rootBuild())
@@ -138,22 +141,18 @@
     }
 
     def "can add multiple included builds"() {
-        def dir1 = tmpDir.createDir("b1")
-        def dir2 = tmpDir.createDir("b2")
-        def buildDefinition1 = build(dir1)
-        def buildDefinition2 = build(dir2)
-        def includedBuild1 = Stub(IncludedBuildState)
-        def includedBuild2 = Stub(IncludedBuildState)
+        def buildDefinition1 = build("b1")
+        def buildDefinition2 = build("b2")
+        def includedBuild1 = expectIncludedBuildAdded("b1", buildDefinition1)
+        def includedBuild2 = expectIncludedBuildAdded("b2", buildDefinition2)
 
         given:
         registry.attachRootBuild(rootBuild())
-        includedBuildFactory.createBuild(new DefaultBuildIdentifier("b1"), Path.path(":b1"), buildDefinition1, false, _) >> includedBuild1
-        includedBuildFactory.createBuild(new DefaultBuildIdentifier("b2"), Path.path(":b2"), buildDefinition2, false, _) >> includedBuild2
 
-        expect:
         registry.addIncludedBuild(buildDefinition1)
         registry.addIncludedBuild(buildDefinition2)
 
+        expect:
         registry.includedBuilds as List == [includedBuild1, includedBuild2]
     }
 
@@ -165,23 +164,12 @@
         def buildDefinition2 = build(dir2, "b2")
         def buildDefinition3 = build(dir3, "b3")
         def id1 = new DefaultBuildIdentifier("b1")
-        def id2 = new DefaultBuildIdentifier("b2")
-        def id3 = new DefaultBuildIdentifier("b3")
-        def idPath1 = Path.path(":b1")
-        def idPath2 = Path.path(":b2")
-        def idPath3 = Path.path(":b3")
-        def includedBuild1 = Stub(IncludedBuildState) { getBuildIdentifier() >> id1 }
-        def includedBuild2 = Stub(IncludedBuildState) { getBuildIdentifier() >> id2 }
-        def includedBuild3 = Stub(IncludedBuildState) { getBuildIdentifier() >> id3 }
-        includedBuild1.identityPath >> idPath1
-        includedBuild2.identityPath >> idPath2
-        includedBuild3.identityPath >> idPath3
+        def includedBuild1 = expectIncludedBuildAdded("b1", buildDefinition1)
+        def includedBuild2 = expectIncludedBuildAdded("b2", buildDefinition2)
+        def includedBuild3 = expectIncludedBuildAdded("b3", buildDefinition3)
 
         given:
         registry.attachRootBuild(rootBuild())
-        includedBuildFactory.createBuild(id1, idPath1, buildDefinition1, false, _) >> includedBuild1
-        includedBuildFactory.createBuild(id2, idPath2, buildDefinition2, false, _) >> includedBuild2
-        includedBuildFactory.createBuild(id3, idPath3, buildDefinition3, false, _) >> includedBuild3
 
         expect:
         registry.addIncludedBuild(buildDefinition1)
@@ -197,10 +185,11 @@
         def dir = tmpDir.createDir("b1")
         def buildDefinition1 = build(dir)
         def buildDefinition2 = build(dir)
+        def includedBuild = expectIncludedBuildAdded("b1", buildDefinition1)
 
         given:
         registry.attachRootBuild(rootBuild())
-        def includedBuild = registry.addIncludedBuild(buildDefinition1)
+        registry.addIncludedBuild(buildDefinition1)
 
         expect:
         registry.addIncludedBuild(buildDefinition2) is includedBuild
@@ -210,6 +199,7 @@
         def dir = tmpDir.createDir("b1")
         def buildDefinition = build(dir)
         def includedBuild = Stub(IncludedBuildState)
+        includedBuild.buildRootDir >> dir
 
         given:
         registry.attachRootBuild(rootBuild())
@@ -226,70 +216,65 @@
         registry.includedBuilds as List == [includedBuild]
     }
 
-    def "can add a buildSrc nested build"() {
+    def "add buildSrc nested build when owner is registered"() {
         given:
-        def buildDefinition = Stub(BuildDefinition)
-        buildDefinition.name >> "buildSrc"
-        registry.attachRootBuild(rootBuild())
-        def owner = Stub(BuildState) { getIdentityPath() >> Path.ROOT }
-        def notifiedBuild
+        def rootDir = tmpDir.createDir("root")
+        rootDir.file("buildSrc/build.gradle").createFile()
+
+        def rootBuild = rootBuild(rootDir)
+        def notifiedBuilds = []
 
         when:
-        def nestedBuild = registry.addBuildSrcNestedBuild(buildDefinition, owner)
+        registry.attachRootBuild(rootBuild)
+
         then:
-        1 * buildAddedListener.buildAdded(_) >> { BuildState addedBuild ->
-            notifiedBuild = addedBuild
+        2 * buildAddedListener.buildAdded(_) >> { BuildState addedBuild ->
+            notifiedBuilds << addedBuild
         }
 
+        and:
+        def nestedBuild = registry.getBuildSrcNestedBuild(rootBuild)
+        nestedBuild != null
         nestedBuild.implicitBuild
         nestedBuild.buildIdentifier == new DefaultBuildIdentifier("buildSrc")
         nestedBuild.identityPath == Path.path(":buildSrc")
-        notifiedBuild.is(nestedBuild)
 
+        and:
+        notifiedBuilds == [rootBuild, nestedBuild]
+
+        and:
         registry.getBuild(nestedBuild.buildIdentifier).is(nestedBuild)
     }
 
-    def "cannot add multiple buildSrc nested builds with same name"() {
+    def "can add multiple buildSrc builds with different levels of nesting"() {
         given:
-        def buildDefinition = Stub(BuildDefinition)
-        buildDefinition.name >> "buildSrc"
-        registry.attachRootBuild(rootBuild())
-        def owner = Stub(BuildState) { getIdentityPath() >> Path.ROOT }
+        def rootDir = tmpDir.createDir("root")
+        rootDir.file("buildSrc/build.gradle").createFile()
 
-        when:
-        registry.addBuildSrcNestedBuild(buildDefinition, owner)
-        registry.addBuildSrcNestedBuild(buildDefinition, owner)
-
-        then:
-        thrown GradleException
-    }
-
-    def "can add multiple buildSrc nested builds with same name and different levels of nesting"() {
-        given:
-        def rootBuild = rootBuild()
+        def rootBuild = rootBuild(rootDir)
         registry.attachRootBuild(rootBuild)
 
-        def parent1Definition = Stub(BuildDefinition)
-        parent1Definition.name >> "parent"
-        def parent1 = registry.addIncludedBuild(parent1Definition)
+        def parentDir = rootDir.file("parent").createDir()
+        parentDir.file("buildSrc/build.gradle").createFile()
 
-        def buildDefinition = Stub(BuildDefinition)
-        buildDefinition.name >> "buildSrc"
-        buildDefinition.buildRootDir >> new File("d")
+        def parentDefinition = build(parentDir, "parent")
+        def parent = expectIncludedBuildAdded("parent", parentDefinition)
+
+        registry.addIncludedBuild(parentDefinition)
 
         expect:
-        def nestedBuild1 = registry.addBuildSrcNestedBuild(buildDefinition, rootBuild)
+        def nestedBuild1 = registry.getBuildSrcNestedBuild(rootBuild)
         nestedBuild1.buildIdentifier == new DefaultBuildIdentifier("buildSrc")
         nestedBuild1.identityPath == Path.path(":buildSrc")
 
-        def nestedBuild2 = registry.addBuildSrcNestedBuild(buildDefinition, parent1)
+        def nestedBuild2 = registry.getBuildSrcNestedBuild(parent)
         // Shows current behaviour, not necessarily desired behaviour
         nestedBuild2.buildIdentifier == new DefaultBuildIdentifier("buildSrc:1")
         nestedBuild2.identityPath == Path.path(":parent:buildSrc")
+    }
 
-        def nestedBuild3 = registry.addBuildSrcNestedBuild(buildDefinition, nestedBuild1)
-        nestedBuild3.buildIdentifier == new DefaultBuildIdentifier("buildSrc:2")
-        nestedBuild3.identityPath == Path.path(":buildSrc:buildSrc")
+    def build(String name) {
+        return build(tmpDir.createDir(name), name)
     }
 
     def build(File rootDir, String name = rootDir.name) {
@@ -304,16 +289,35 @@
         )
     }
 
-    RootBuildState rootBuild(String... projects) {
-        def gradleLauncher = Stub(BuildLifecycleController)
-        def parentGradle = Stub(GradleInternal)
+    IncludedBuildState expectIncludedBuildAdded(String name, BuildDefinition buildDefinition) {
+        def idPath = Path.path(":$name")
+        def buildIdentifier = new DefaultBuildIdentifier(name)
+
         def gradle = Stub(GradleInternal)
+        def services = Stub(ServiceRegistry)
+
+        def includedBuild = Stub(IncludedBuildState)
+        includedBuild.buildRootDir >> buildDefinition.buildRootDir
+        includedBuild.identityPath >> idPath
+        includedBuild.buildIdentifier >> buildIdentifier
+        includedBuild.mutableModel >> gradle
+
+        gradle.services >> services
+
+        services.get(PublicBuildPath) >> Stub(PublicBuildPath)
+
+        includedBuildFactory.createBuild(buildIdentifier, idPath, buildDefinition, false, _) >> includedBuild
+
+        return includedBuild
+    }
+
+    RootBuildState rootBuild(TestFile rootDir = tmpDir.createDir("root-dir")) {
         def settings = Stub(SettingsInternal)
+        def gradle = Stub(GradleInternal)
+        def buildController = buildController(settings, gradle)
         def build = Stub(RootBuildState)
 
-        gradleLauncherFactory.newInstance(_, _, _, _) >> gradleLauncher
-        gradleLauncher.gradle >> gradle
-        gradle.settings >> settings
+        gradleLauncherFactory.newInstance(_, _, _, _) >> buildController
         settings.rootProject >> Stub(ProjectDescriptor) {
             getName() >> "root"
         }
@@ -324,7 +328,32 @@
         build.buildIdentifier >> DefaultBuildIdentifier.ROOT
         build.identityPath >> Path.ROOT
         build.loadedSettings >> settings
-        build.mutableModel >> parentGradle
+        build.mutableModel >> gradle
+        build.buildRootDir >> rootDir
         return build
     }
+
+    private BuildLifecycleController buildController() {
+        def settings = Stub(SettingsInternal)
+        def gradle = Stub(GradleInternal)
+        return buildController(settings, gradle)
+    }
+
+    private BuildLifecycleController buildController(SettingsInternal settings, GradleInternal gradle) {
+        def buildController = Stub(BuildLifecycleController)
+        def services = Stub(ServiceRegistry)
+        def buildLayout = Stub(BuildLayout)
+
+        _ * buildController.gradle >> gradle
+        if (settings != null) {
+            _ * gradle.settings >> settings
+        }
+        _ * gradle.services >> services
+        _ * services.get(BuildLayout) >> buildLayout
+        _ * buildLayout.rootDirectory >> tmpDir.file("root-dir")
+        _ * services.get(PublicBuildPath) >> Stub(PublicBuildPath)
+
+        return buildController
+    }
+
 }
diff --git a/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/TestBuildTreeLifecycleControllerFactory.groovy b/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/TestBuildTreeLifecycleControllerFactory.groovy
index 45962a9..a5351e9 100644
--- a/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/TestBuildTreeLifecycleControllerFactory.groovy
+++ b/subprojects/composite-builds/src/test/groovy/org/gradle/composite/internal/TestBuildTreeLifecycleControllerFactory.groovy
@@ -18,7 +18,9 @@
 
 import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.SettingsInternal
+import org.gradle.api.internal.project.ProjectState
 import org.gradle.internal.build.BuildLifecycleController
+import org.gradle.internal.build.BuildState
 import org.gradle.internal.build.BuildToolingModelAction
 import org.gradle.internal.build.BuildToolingModelController
 import org.gradle.internal.buildtree.BuildTreeFinishExecutor
@@ -26,6 +28,8 @@
 import org.gradle.internal.buildtree.BuildTreeLifecycleControllerFactory
 import org.gradle.internal.buildtree.BuildTreeWorkExecutor
 import org.gradle.internal.operations.RunnableBuildOperation
+import org.gradle.tooling.provider.model.UnknownModelException
+import org.gradle.tooling.provider.model.internal.ToolingModelBuilderLookup
 
 import java.util.function.Function
 
@@ -43,13 +47,23 @@
         }
 
         @Override
-        GradleInternal getMutableModel() {
-            return targetBuild.gradle
+        GradleInternal getConfiguredModel() {
+            return targetBuild.configuredBuild
         }
 
         @Override
-        GradleInternal getConfiguredModel() {
-            return targetBuild.configuredBuild
+        ToolingModelBuilderLookup.Builder locateBuilderForDefaultTarget(String modelName, boolean param) throws UnknownModelException {
+            throw new UnsupportedOperationException()
+        }
+
+        @Override
+        ToolingModelBuilderLookup.Builder locateBuilderForTarget(BuildState target, String modelName, boolean param) throws UnknownModelException {
+            throw new UnsupportedOperationException()
+        }
+
+        @Override
+        ToolingModelBuilderLookup.Builder locateBuilderForTarget(ProjectState target, String modelName, boolean param) throws UnknownModelException {
+            throw new UnsupportedOperationException()
         }
 
         @Override
diff --git a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDebugLogIntegrationTest.groovy b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDebugLogIntegrationTest.groovy
index 619fff1..1583279 100644
--- a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDebugLogIntegrationTest.groovy
+++ b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDebugLogIntegrationTest.groovy
@@ -45,21 +45,21 @@
         events.contains([profile: "fingerprint", type: "C", "frame": GradleEnvironment.name])
 
         and: "Gradle and Work Graph events are logged"
-        events.contains([profile: "root state", type: "O", frame: "Gradle"])
-        events.contains([profile: "root state", type: "O", frame: "Work Graph"])
+        events.contains([profile: "build ':' state", type: "O", frame: "Gradle"])
+        events.contains([profile: "build ':' state", type: "O", frame: "Work Graph"])
 
         and: "state frame events are logged"
-        events.contains([profile: "root state", type: "O", frame: ":ok"])
-        events.contains([profile: "root state", type: "C", frame: ":ok"])
-        events.contains([profile: "root state", type: "O", frame: ":sub:ok"])
-        events.contains([profile: "root state", type: "C", frame: ":sub:ok"])
+        events.contains([profile: "build ':' state", type: "O", frame: ":ok"])
+        events.contains([profile: "build ':' state", type: "C", frame: ":ok"])
+        events.contains([profile: "build ':' state", type: "O", frame: ":sub:ok"])
+        events.contains([profile: "build ':' state", type: "C", frame: ":sub:ok"])
 
         and: "task type frame follows task path frame follows LocalTaskNode frame"
         def firstTaskNodeIndex = events.findIndexOf { it.frame == LocalTaskNode.name }
         firstTaskNodeIndex > 0
-        events[firstTaskNodeIndex] == [profile: "root state", type: "O", frame: LocalTaskNode.name]
-        events[firstTaskNodeIndex + 1] == [profile: "root state", type: "O", frame: ":ok"]
-        events[firstTaskNodeIndex + 2] == [profile: "root state", type: "O", frame: DefaultTask.name]
+        events[firstTaskNodeIndex] == [profile: "build ':' state", type: "O", frame: LocalTaskNode.name]
+        events[firstTaskNodeIndex + 1] == [profile: "build ':' state", type: "O", frame: ":ok"]
+        events[firstTaskNodeIndex + 2] == [profile: "build ':' state", type: "O", frame: DefaultTask.name]
     }
 
     private Collection<Map<String, Object>> collectOutputEvents() {
diff --git a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDependencyResolutionFeaturesIntegrationTest.groovy b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDependencyResolutionFeaturesIntegrationTest.groovy
index 70fd223..197ad2d 100644
--- a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDependencyResolutionFeaturesIntegrationTest.groovy
+++ b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheDependencyResolutionFeaturesIntegrationTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.configurationcache
 
+import groovy.transform.Canonical
 import org.gradle.api.tasks.TasksWithInputsAndOutputs
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.server.http.HttpServer
@@ -36,8 +37,24 @@
         executer.requireOwnGradleUserHomeDir()
     }
 
-    def "does not invalidate configuration cache entry when dynamic version information has not expired"() {
+    @Canonical
+    class RepoFixture {
+        MavenHttpRepository repository
+        Closure<Void> cleanup
+
+        URI getUri() {
+            repository.uri
+        }
+    }
+
+    @Unroll
+    def "does not invalidate configuration cache entry when dynamic version information has not expired (#scenario)"() {
         given:
+        RepoFixture defaultRepo = new RepoFixture(remoteRepo)
+        List<RepoFixture> repos = scenario == DynamicVersionScenario.SINGLE_REPO
+            ? [defaultRepo]
+            : [defaultRepo, repoWithout('thing', 'lib')]
+
         server.start()
 
         remoteRepo.module("thing", "lib", "1.2").publish()
@@ -49,7 +66,7 @@
                 implementation
             }
 
-            repositories { maven { url = '${remoteRepo.uri}' } }
+            ${repositoriesBlockFor(repos)}
 
             dependencies {
                 implementation 'thing:lib:1.+'
@@ -95,10 +112,22 @@
         then:
         configurationCache.assertStateLoaded()
         outputContains("result = [lib-1.3.jar]")
+
+        cleanup:
+        cleanUpAll repos
+
+        where:
+        scenario << DynamicVersionScenario.values()
     }
 
-    def "invalidates configuration cache entry when dynamic version information has expired"() {
+    @Unroll
+    def "invalidates configuration cache entry when dynamic version information has expired (#scenario)"() {
         given:
+        RepoFixture defaultRepo = new RepoFixture(remoteRepo)
+        List<RepoFixture> repos = scenario == DynamicVersionScenario.SINGLE_REPO
+            ? [defaultRepo]
+            : [defaultRepo, repoWithout('thing', 'lib')]
+
         server.start()
 
         remoteRepo.module("thing", "lib", "1.2").publish()
@@ -112,7 +141,7 @@
                 }
             }
 
-            repositories { maven { url = '${remoteRepo.uri}' } }
+            ${repositoriesBlockFor(repos)}
 
             dependencies {
                 implementation 'thing:lib:1.+'
@@ -156,12 +185,63 @@
         outputContains("result = [lib-1.3.jar]")
 
         when:
+        configurationCacheRun("resolve1", "-Dorg.gradle.internal.test.clockoffset=${clockOffset}")
+
+        then:
+        configurationCache.assertStateLoaded()
+        outputContains("result = [lib-1.3.jar]")
+
+        when:
         configurationCacheRun("resolve2", "-Dorg.gradle.internal.test.clockoffset=${clockOffset}")
 
         then:
         configurationCache.assertStateStored()
         outputContains("Calculating task graph as configuration cache cannot be reused because cached version information for thing:lib:1.+ has expired.")
         outputContains("result = [lib-1.3.jar]")
+
+        when:
+        configurationCacheRun("resolve2", "-Dorg.gradle.internal.test.clockoffset=${clockOffset}")
+
+        then:
+        configurationCache.assertStateLoaded()
+        outputContains("result = [lib-1.3.jar]")
+
+        cleanup:
+        cleanUpAll repos
+
+        where:
+        scenario << DynamicVersionScenario.values()
+    }
+
+    private RepoFixture repoWithout(String group, String artifact) {
+        HttpServer server = new HttpServer()
+        MavenHttpRepository repo = new MavenHttpRepository(server, '/empty', maven(file('empty')))
+        server.start()
+        repo.getModuleMetaData(group, artifact).expectGetMissing()
+        new RepoFixture(repo, { server.stop() })
+    }
+
+    enum DynamicVersionScenario {
+        SINGLE_REPO, MATCHING_REPO_PLUS_404
+
+        @Override
+        String toString() {
+            super.toString().split('_').collect { it.toLowerCase() }.join(' ')
+        }
+    }
+
+    private static String repositoriesBlockFor(List<RepoFixture> fixtures) {
+        """
+            repositories {
+                ${fixtures.collect { "maven { url = '${it.uri}' }" }.join('\n')}
+            }
+        """
+    }
+
+    private cleanUpAll(List<RepoFixture> fixtures) {
+        fixtures.forEach {
+            it.cleanup?.call()
+        }
     }
 
     def "does not invalidate configuration cache entry when changing artifact information has not expired"() {
diff --git a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheIntegrationTest.groovy b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheIntegrationTest.groovy
index d2ead65..920cd04 100644
--- a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheIntegrationTest.groovy
+++ b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/ConfigurationCacheIntegrationTest.groovy
@@ -16,12 +16,59 @@
 
 package org.gradle.configurationcache
 
+import org.gradle.api.Plugin
+import org.gradle.api.Project
 import org.gradle.initialization.LoadProjectsBuildOperationType
 import org.gradle.initialization.StartParameterBuildOptions.ConfigurationCacheRecreateOption
 import org.gradle.integtests.fixtures.BuildOperationsFixture
+import spock.lang.Issue
 
 class ConfigurationCacheIntegrationTest extends AbstractConfigurationCacheIntegrationTest {
 
+    @Issue("https://github.com/gradle/gradle/issues/18064")
+    def "can build plugin with project dependencies"() {
+        given:
+        settingsFile << """
+            include 'my-lib'
+            include 'my-plugin'
+        """
+        file('my-lib/build.gradle') << """
+            plugins { id 'java' }
+        """
+        file('my-plugin/build.gradle') << """
+            plugins { id 'java-gradle-plugin' }
+
+            dependencies {
+              implementation project(":my-lib")
+            }
+
+            gradlePlugin {
+              plugins {
+                myPlugin {
+                  id = 'com.example.my-plugin'
+                  implementationClass = 'com.example.MyPlugin'
+                }
+              }
+            }
+        """
+        file('src/main/java/com/example/MyPlugin.java') << """
+            package com.example;
+            public class MyPlugin implements $Plugin.name<$Project.name> {
+              @Override
+              public void apply($Project.name project) {
+              }
+            }
+        """
+        def configurationCache = newConfigurationCacheFixture()
+
+        when:
+        configurationCacheRun "build"
+        configurationCacheRun "build"
+
+        then:
+        configurationCache.assertStateLoaded()
+    }
+
     def "can copy zipTree"() {
         given:
         def configurationCache = newConfigurationCacheFixture()
diff --git a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/isolated/IsolatedProjectsToolingApiIModelQueryIntegrationTest.groovy b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/isolated/IsolatedProjectsToolingApiIModelQueryIntegrationTest.groovy
index 5421570..6fc389e 100644
--- a/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/isolated/IsolatedProjectsToolingApiIModelQueryIntegrationTest.groovy
+++ b/subprojects/configuration-cache/src/integTest/groovy/org/gradle/configurationcache/isolated/IsolatedProjectsToolingApiIModelQueryIntegrationTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.configurationcache.isolated
 
 import org.gradle.configurationcache.fixtures.SomeToolingModel
+import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.gradle.GradleBuild
 
 class IsolatedProjectsToolingApiIModelQueryIntegrationTest extends AbstractIsolatedProjectsToolingApiIntegrationTest {
@@ -119,6 +120,9 @@
             include("b")
             println("configuring build")
         """
+        buildFile << """
+            throw new RuntimeException("should not be called")
+        """
 
         when:
         executer.withArguments(ENABLE_CLI)
@@ -189,11 +193,10 @@
 
         when:
         executer.withArguments(ENABLE_CLI)
-        def model3 = fetchModel(GradleBuild)
+        def model3 = fetchModel(GradleProject)
 
         then:
-        model3 instanceof GradleBuild
-        model3.projects.size() == 1
+        model3 instanceof GradleProject
 
         and:
         outputContains("Creating tooling model as no configuration cache is available for the requested model")
@@ -203,11 +206,10 @@
 
         when:
         executer.withArguments(ENABLE_CLI)
-        def model4 = fetchModel(GradleBuild)
+        def model4 = fetchModel(GradleProject)
 
         then:
-        model4 instanceof GradleBuild
-        model4.projects.size() == 1
+        model4 instanceof GradleProject
 
         and:
         outputContains("Reusing configuration cache.")
diff --git a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheHost.kt b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheHost.kt
index 757588a..09857e2 100644
--- a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheHost.kt
+++ b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheHost.kt
@@ -106,7 +106,7 @@
         override fun registerProjects() {
             // Ensure projects are registered for look up e.g. by dependency resolution
             val projectRegistry = service<ProjectStateRegistry>()
-            projectRegistry.registerProjects(service<BuildState>())
+            projectRegistry.registerProjects(state, projectDescriptorRegistry)
             createRootProject()
         }
 
diff --git a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheIO.kt b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheIO.kt
index e2ed699..3147ca4 100644
--- a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheIO.kt
+++ b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/ConfigurationCacheIO.kt
@@ -108,7 +108,7 @@
         action: suspend DefaultWriteContext.(ConfigurationCacheState) -> T
     ): T {
         val build = host.currentBuild
-        val (context, codecs) = writerContextFor(stateFile.outputStream(), build.gradle.rootProject.name + " state")
+        val (context, codecs) = writerContextFor(stateFile.outputStream(), build.gradle.owner.displayName.displayName + " state")
         return context.useToRun {
             runWriteOperation {
                 action(ConfigurationCacheState(codecs, stateFile))
diff --git a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/DefaultBuildTreeLifecycleControllerFactory.kt b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/DefaultBuildTreeLifecycleControllerFactory.kt
index a9c46a4..b9750cf 100644
--- a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/DefaultBuildTreeLifecycleControllerFactory.kt
+++ b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/DefaultBuildTreeLifecycleControllerFactory.kt
@@ -28,13 +28,15 @@
 import org.gradle.internal.buildtree.DefaultBuildTreeModelCreator
 import org.gradle.internal.buildtree.DefaultBuildTreeWorkPreparer
 import org.gradle.internal.operations.BuildOperationExecutor
+import org.gradle.internal.resources.ProjectLeaseRegistry
 
 
 class DefaultBuildTreeLifecycleControllerFactory(
     private val startParameter: ConfigurationCacheStartParameter,
     private val cache: BuildTreeConfigurationCache,
     private val taskGraph: IncludedBuildTaskGraph,
-    private val buildOperationExecutor: BuildOperationExecutor
+    private val buildOperationExecutor: BuildOperationExecutor,
+    private val projectLeaseRegistry: ProjectLeaseRegistry
 ) : BuildTreeLifecycleControllerFactory {
     override fun createController(targetBuild: BuildLifecycleController, workExecutor: BuildTreeWorkExecutor, finishExecutor: BuildTreeFinishExecutor): BuildTreeLifecycleController {
         // Currently, apply the decoration only to the root build, as the cache implementation is still scoped to the root build
@@ -48,7 +50,7 @@
             defaultWorkPreparer
         }
 
-        val defaultModelCreator = DefaultBuildTreeModelCreator(targetBuild, buildOperationExecutor)
+        val defaultModelCreator = DefaultBuildTreeModelCreator(targetBuild, buildOperationExecutor, projectLeaseRegistry)
         val modelCreator = if (startParameter.isEnabled && rootBuild) {
             ConfigurationCacheAwareBuildTreeModelCreator(defaultModelCreator, cache)
         } else {
diff --git a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/fingerprint/ConfigurationCacheFingerprintWriter.kt b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/fingerprint/ConfigurationCacheFingerprintWriter.kt
index 375fd0d..125c8d8 100644
--- a/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/fingerprint/ConfigurationCacheFingerprintWriter.kt
+++ b/subprojects/configuration-cache/src/main/kotlin/org/gradle/configurationcache/fingerprint/ConfigurationCacheFingerprintWriter.kt
@@ -17,6 +17,7 @@
 package org.gradle.configurationcache.fingerprint
 
 import com.google.common.collect.Sets.newConcurrentHashSet
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
 import org.gradle.api.artifacts.component.ModuleComponentSelector
 import org.gradle.api.execution.internal.TaskInputsListener
@@ -120,7 +121,11 @@
         ignoreValueSources = true
     }
 
-    override fun onDynamicVersionSelection(requested: ModuleComponentSelector, expiry: Expiry) {
+    override fun onDynamicVersionSelection(requested: ModuleComponentSelector, expiry: Expiry, versions: Set<ModuleVersionIdentifier>) {
+        // Only consider repositories serving at least one version of the requested module.
+        // This is meant to avoid repetitively expiring cache entries due to a 404 response for the requested module metadata
+        // from one of the configured repositories.
+        if (versions.isEmpty()) return
         val expireAt = host.buildStartTime + expiry.keepFor.toMillis()
         onChangingValue(ConfigurationCacheFingerprint.DynamicDependencyVersion(requested.displayName, expireAt))
     }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/process/internal/worker/DefaultWorkerProcessBuilderTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/worker/DefaultWorkerProcessBuilderTest.groovy
new file mode 100644
index 0000000..c20a774
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/worker/DefaultWorkerProcessBuilderTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.process.internal.worker
+
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class DefaultWorkerProcessBuilderTest extends AbstractIntegrationSpec {
+
+    def "test classpath does not contain nonexistent entries"() {
+        given:
+        file("src/test/java/ClasspathTest.java") << '''
+        import org.junit.Test;
+
+        import static org.junit.Assert.assertTrue;
+
+        public class ClasspathTest {
+            @Test
+            public void test() {
+                String runtimeClasspath = System.getProperty("java.class.path");
+                System.out.println(runtimeClasspath);
+                assertTrue(runtimeClasspath.contains(System.getProperty("user.home")));
+                assertTrue(!runtimeClasspath.contains("Non exist path"));
+            }
+        }
+
+        '''
+        buildFile << """
+        plugins {
+            id "java-library"
+        }
+
+        repositories {
+            mavenCentral()
+        }
+
+        dependencies {
+            testImplementation 'junit:junit:4.13'
+        }
+
+        tasks.test {
+            doFirst {
+                classpath += files(System.getProperty("user.home"),
+                        System.getProperty("user.home") + File.separator + "*",
+                        System.getProperty("user.home") + File.separator + "Non exist path",
+                        System.getProperty("user.home") + File.separator + "Non exist path" + File.separator + "*")
+            }
+        }
+        """
+
+        expect:
+        succeeds("test")
+    }
+
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderEndUserIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderEndUserIntegrationTest.groovy
index cd5b883..42c1e88 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderEndUserIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderEndUserIntegrationTest.groovy
@@ -35,8 +35,6 @@
             implementation gradleApi()
             testImplementation(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
             testImplementation("org.spockframework:spock-core")
-            testImplementation("org.spockframework:spock-junit4")
-            testImplementation("junit:junit:4.13.1")
         }
 
         ${mavenCentralRepository()}
diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/GradleInternal.java b/subprojects/core/src/main/java/org/gradle/api/internal/GradleInternal.java
index 0e2b773..0538988 100644
--- a/subprojects/core/src/main/java/org/gradle/api/internal/GradleInternal.java
+++ b/subprojects/core/src/main/java/org/gradle/api/internal/GradleInternal.java
@@ -36,6 +36,7 @@
 import javax.annotation.Nullable;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * An internal interface for Gradle that exposed objects and concepts that are not intended for public
@@ -121,7 +122,7 @@ public interface GradleInternal extends Gradle, PluginAwareInternal {
 
     ServiceRegistryFactory getServiceRegistryFactory();
 
-    void setClassLoaderScope(ClassLoaderScope classLoaderScope);
+    void setClassLoaderScope(Supplier<? extends ClassLoaderScope> classLoaderScope);
 
     ClassLoaderScope getClassLoaderScope();
 
diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/SettingsInternal.java b/subprojects/core/src/main/java/org/gradle/api/internal/SettingsInternal.java
index 34aab03..c1d9705 100644
--- a/subprojects/core/src/main/java/org/gradle/api/internal/SettingsInternal.java
+++ b/subprojects/core/src/main/java/org/gradle/api/internal/SettingsInternal.java
@@ -28,7 +28,6 @@
 import org.gradle.internal.management.DependencyResolutionManagementInternal;
 import org.gradle.internal.service.ServiceRegistry;
 
-import java.io.File;
 import java.util.List;
 
 public interface SettingsInternal extends Settings, PluginAwareInternal {
@@ -65,8 +64,6 @@ public interface SettingsInternal extends Settings, PluginAwareInternal {
      */
     ClassLoaderScope getClassLoaderScope();
 
-    File getBuildSrcDir();
-
     ServiceRegistry getServices();
 
     @Override
diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/DefaultProjectStateRegistry.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/DefaultProjectStateRegistry.java
index 900b5a2..91ed15f 100644
--- a/subprojects/core/src/main/java/org/gradle/api/internal/project/DefaultProjectStateRegistry.java
+++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/DefaultProjectStateRegistry.java
@@ -20,8 +20,12 @@
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.component.BuildIdentifier;
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier;
 import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.initialization.DefaultProjectDescriptor;
+import org.gradle.internal.Describables;
+import org.gradle.internal.DisplayName;
 import org.gradle.internal.Factories;
 import org.gradle.internal.Factory;
 import org.gradle.internal.build.BuildProjectRegistry;
@@ -34,6 +38,7 @@
 import org.gradle.util.Path;
 
 import javax.annotation.Nullable;
+import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -59,12 +64,12 @@ public DefaultProjectStateRegistry(WorkerLeaseService workerLeaseService) {
     }
 
     @Override
-    public void registerProjects(BuildState owner) {
-        Set<DefaultProjectDescriptor> allProjects = owner.getLoadedSettings().getProjectRegistry().getAllProjects();
+    public void registerProjects(BuildState owner, ProjectRegistry<DefaultProjectDescriptor> projectRegistry) {
+        Set<DefaultProjectDescriptor> allProjects = projectRegistry.getAllProjects();
         synchronized (lock) {
             DefaultBuildProjectRegistry buildProjectRegistry = getBuildProjectRegistry(owner);
             if (!buildProjectRegistry.projectsByPath.isEmpty()) {
-                throw new IllegalStateException("Projects for build " + owner.getBuildIdentifier() + " have already been registered.");
+                throw new IllegalStateException("Projects for " + owner.getDisplayName() + " have already been registered.");
             }
             for (DefaultProjectDescriptor descriptor : allProjects) {
                 addProject(owner, buildProjectRegistry, descriptor);
@@ -75,7 +80,7 @@ public void registerProjects(BuildState owner) {
     private DefaultBuildProjectRegistry getBuildProjectRegistry(BuildState owner) {
         DefaultBuildProjectRegistry buildProjectRegistry = projectsByBuild.get(owner.getBuildIdentifier());
         if (buildProjectRegistry == null) {
-            buildProjectRegistry = new DefaultBuildProjectRegistry(owner.getBuildIdentifier());
+            buildProjectRegistry = new DefaultBuildProjectRegistry(owner);
             projectsByBuild.put(owner.getBuildIdentifier(), buildProjectRegistry);
         }
         return buildProjectRegistry;
@@ -91,8 +96,9 @@ public ProjectState registerProject(BuildState owner, DefaultProjectDescriptor p
 
     private ProjectState addProject(BuildState owner, DefaultBuildProjectRegistry projectRegistry, DefaultProjectDescriptor descriptor) {
         Path projectPath = descriptor.path();
-        Path identityPath = owner.getIdentityPathForProject(projectPath);
-        ProjectComponentIdentifier projectIdentifier = owner.getIdentifierForProject(projectPath);
+        Path identityPath = owner.calculateIdentityPathForProject(projectPath);
+        String name = descriptor.getName();
+        ProjectComponentIdentifier projectIdentifier = new DefaultProjectComponentIdentifier(owner.getBuildIdentifier(), identityPath, projectPath, name);
         IProjectFactory projectFactory = owner.getMutableModel().getServices().get(IProjectFactory.class);
         ProjectStateImpl projectState = new ProjectStateImpl(owner, identityPath, projectPath, descriptor.getName(), projectIdentifier, descriptor, projectFactory);
         projectsByPath.put(identityPath, projectState);
@@ -108,16 +114,10 @@ public Collection<ProjectStateImpl> getAllProjects() {
         }
     }
 
-    // TODO - can kill this method, as the caller can use ProjectInternal.getMutationState() instead
+    // TODO - can kill this method, as the caller can use ProjectInternal.getOwner() instead
     @Override
     public ProjectState stateFor(Project project) {
-        synchronized (lock) {
-            ProjectStateImpl projectState = projectsByPath.get(((ProjectInternal) project).getIdentityPath());
-            if (projectState == null) {
-                throw new IllegalArgumentException("Could not find state for " + project);
-            }
-            return projectState;
-        }
+        return ((ProjectInternal) project).getOwner();
     }
 
     @Override
@@ -184,11 +184,11 @@ public <T> T allowUncontrolledAccessToAnyProject(Factory<T> factory) {
     }
 
     private static class DefaultBuildProjectRegistry implements BuildProjectRegistry {
-        private final BuildIdentifier buildIdentifier;
+        private final BuildState owner;
         private final Map<Path, ProjectStateImpl> projectsByPath = Maps.newLinkedHashMap();
 
-        public DefaultBuildProjectRegistry(BuildIdentifier buildIdentifier) {
-            this.buildIdentifier = buildIdentifier;
+        public DefaultBuildProjectRegistry(BuildState owner) {
+            this.owner = owner;
         }
 
         public void add(Path projectPath, ProjectStateImpl projectState) {
@@ -196,17 +196,28 @@ public void add(Path projectPath, ProjectStateImpl projectState) {
         }
 
         @Override
+        public ProjectState getRootProject() {
+            return getProject(Path.ROOT);
+        }
+
+        @Override
         public ProjectState getProject(Path projectPath) {
             ProjectStateImpl projectState = projectsByPath.get(projectPath);
             if (projectState == null) {
-                throw new IllegalArgumentException("Project " + projectPath + " not found in build " + buildIdentifier);
+                throw new IllegalArgumentException("Project with path '" + projectPath + "' not found in " + owner.getDisplayName() + ".");
             }
             return projectState;
         }
 
+        @Nullable
+        @Override
+        public ProjectState findProject(Path projectPath) {
+            return projectsByPath.get(projectPath);
+        }
+
         @Override
         public Set<? extends ProjectState> getAllProjects() {
-            TreeSet<ProjectState> projects = new TreeSet<>(Comparator.comparing(ProjectState::getMutableModel));
+            TreeSet<ProjectState> projects = new TreeSet<>(Comparator.comparing(ProjectState::getIdentityPath));
             projects.addAll(projectsByPath.values());
             return projects;
         }
@@ -236,8 +247,16 @@ private class ProjectStateImpl implements ProjectState {
         }
 
         @Override
+        public DisplayName getDisplayName() {
+            if (projectPath.equals(Path.ROOT)) {
+                return Describables.quoted("root project", projectName);
+            }
+            return Describables.of(identifier);
+        }
+
+        @Override
         public String toString() {
-            return identifier.getDisplayName();
+            return getDisplayName().getDisplayName();
         }
 
         @Override
@@ -252,6 +271,15 @@ public ProjectState getParent() {
         }
 
         @Override
+        public Set<ProjectState> getChildProjects() {
+            Set<ProjectState> children = new TreeSet<>(Comparator.comparing(ProjectState::getIdentityPath));
+            for (ProjectDescriptor child : descriptor.getChildren()) {
+                children.add(projectsByPath.get(identityPath.child(child.getName())));
+            }
+            return children;
+        }
+
+        @Override
         public String getName() {
             return projectName;
         }
@@ -267,6 +295,11 @@ public Path getProjectPath() {
         }
 
         @Override
+        public File getProjectDir() {
+            return descriptor.getProjectDir();
+        }
+
+        @Override
         public void createMutableModel(ClassLoaderScope selfClassLoaderScope, ClassLoaderScope baseClassLoaderScope) {
             synchronized (this) {
                 if (this.project != null) {
diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectState.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectState.java
index 3eb2ee9..806d92c 100644
--- a/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectState.java
+++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectState.java
@@ -19,6 +19,7 @@
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
 import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.internal.DisplayName;
 import org.gradle.internal.build.BuildState;
 import org.gradle.internal.model.ModelContainer;
 import org.gradle.internal.resources.ResourceLock;
@@ -26,6 +27,8 @@
 
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
+import java.io.File;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -34,6 +37,8 @@
  */
 @ThreadSafe
 public interface ProjectState extends ModelContainer<ProjectInternal> {
+    DisplayName getDisplayName();
+
     /**
      * Returns the containing build of this project.
      */
@@ -46,6 +51,11 @@ public interface ProjectState extends ModelContainer<ProjectInternal> {
     ProjectState getParent();
 
     /**
+     * Returns the direct children of this project, in public iteration order.
+     */
+    Set<ProjectState> getChildProjects();
+
+    /**
      * Returns the name of this project (which may not necessarily be unique).
      */
     String getName();
@@ -61,6 +71,11 @@ public interface ProjectState extends ModelContainer<ProjectInternal> {
     Path getProjectPath();
 
     /**
+     * Returns the project directory.
+     */
+    File getProjectDir();
+
+    /**
      * Returns the identifier of the default component produced by this project.
      */
     ProjectComponentIdentifier getComponentIdentifier();
diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectStateRegistry.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectStateRegistry.java
index c09630a..b49c836 100644
--- a/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectStateRegistry.java
+++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/ProjectStateRegistry.java
@@ -58,7 +58,7 @@ public interface ProjectStateRegistry {
     /**
      * Registers the projects of a build.
      */
-    void registerProjects(BuildState build);
+    void registerProjects(BuildState build, ProjectRegistry<DefaultProjectDescriptor> projectRegistry);
 
     /**
      * Registers a single project.
diff --git a/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java b/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java
index 098930b..68b6c64 100644
--- a/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java
+++ b/subprojects/core/src/main/java/org/gradle/initialization/DefaultSettings.java
@@ -281,11 +281,6 @@ public ClassLoaderScope getClassLoaderScope() {
     }
 
     @Override
-    public File getBuildSrcDir() {
-        return new File(getSettingsDir(), BUILD_SRC);
-    }
-
-    @Override
     public ServiceRegistry getServices() {
         return services;
     }
diff --git a/subprojects/core/src/main/java/org/gradle/initialization/SettingsAttachingSettingsLoader.java b/subprojects/core/src/main/java/org/gradle/initialization/SettingsAttachingSettingsLoader.java
index 634bb86..03a1f18 100644
--- a/subprojects/core/src/main/java/org/gradle/initialization/SettingsAttachingSettingsLoader.java
+++ b/subprojects/core/src/main/java/org/gradle/initialization/SettingsAttachingSettingsLoader.java
@@ -19,7 +19,6 @@
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.ProjectStateRegistry;
-import org.gradle.internal.build.BuildState;
 
 class SettingsAttachingSettingsLoader implements SettingsLoader {
     private final SettingsLoader delegate;
@@ -34,7 +33,7 @@ class SettingsAttachingSettingsLoader implements SettingsLoader {
     public SettingsInternal findAndLoadSettings(GradleInternal gradle) {
         SettingsInternal settings = delegate.findAndLoadSettings(gradle);
         gradle.setSettings(settings);
-        projectRegistry.registerProjects(gradle.getServices().get(BuildState.class));
+        projectRegistry.registerProjects(gradle.getOwner(), settings.getProjectRegistry());
         return settings;
     }
 }
diff --git a/subprojects/core/src/main/java/org/gradle/initialization/buildsrc/BuildSourceBuilder.java b/subprojects/core/src/main/java/org/gradle/initialization/buildsrc/BuildSourceBuilder.java
index b92f3d4..e463d98 100644
--- a/subprojects/core/src/main/java/org/gradle/initialization/buildsrc/BuildSourceBuilder.java
+++ b/subprojects/core/src/main/java/org/gradle/initialization/buildsrc/BuildSourceBuilder.java
@@ -16,16 +16,10 @@
 
 package org.gradle.initialization.buildsrc;
 
-import org.gradle.StartParameter;
-import org.gradle.api.internal.BuildDefinition;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
-import org.gradle.api.internal.StartParameterInternal;
-import org.gradle.api.internal.initialization.ClassLoaderScope;
 import org.gradle.cache.FileLock;
 import org.gradle.cache.FileLockManager;
 import org.gradle.cache.LockOptions;
-import org.gradle.internal.Actions;
 import org.gradle.internal.build.BuildState;
 import org.gradle.internal.build.BuildStateRegistry;
 import org.gradle.internal.build.PublicBuildPath;
@@ -38,11 +32,9 @@
 import org.gradle.internal.operations.CallableBuildOperation;
 import org.gradle.internal.service.scopes.Scopes;
 import org.gradle.internal.service.scopes.ServiceScope;
-import org.gradle.plugin.management.internal.PluginRequests;
 
 import java.io.File;
 
-import static org.gradle.api.internal.SettingsInternal.BUILD_SRC;
 import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode;
 
 @ServiceScope(Scopes.Build.class)
@@ -69,36 +61,19 @@ public BuildSourceBuilder(BuildState currentBuild, FileLockManager fileLockManag
     }
 
     public ClassPath buildAndGetClassPath(GradleInternal gradle) {
-        SettingsInternal settings = gradle.getSettings();
-        File buildSrcDir = settings.getBuildSrcDir();
-        ClassLoaderScope parentClassLoaderScope = settings.getClassLoaderScope();
-
-        return createBuildSourceClasspath(buildSrcDir, gradle.getStartParameter(), parentClassLoaderScope);
+        return createBuildSourceClasspath();
     }
 
-    private ClassPath createBuildSourceClasspath(File buildSrcDir, final StartParameter containingBuildParameters, ClassLoaderScope parentClassLoaderScope) {
-        if (!BuildSrcDetector.isValidBuildSrcBuild(buildSrcDir)) {
+    private ClassPath createBuildSourceClasspath() {
+        StandAloneNestedBuild buildSrcBuild = buildRegistry.getBuildSrcNestedBuild(currentBuild);
+        if (buildSrcBuild == null) {
             return ClassPath.EMPTY;
         }
 
-        final StartParameterInternal buildSrcStartParameter = buildSrcStartParameterFor(buildSrcDir, containingBuildParameters);
-        final BuildDefinition buildDefinition = BuildDefinition.fromStartParameterForBuild(
-            buildSrcStartParameter,
-            BUILD_SRC,
-            buildSrcDir,
-            PluginRequests.EMPTY,
-            Actions.doNothing(),
-            publicBuildPath,
-            true
-        );
-        @SuppressWarnings("deprecation")
-        File customBuildFile = buildSrcStartParameter.getBuildFile();
-        assert customBuildFile == null;
-
         return buildOperationExecutor.call(new CallableBuildOperation<ClassPath>() {
             @Override
             public ClassPath call(BuildOperationContext context) {
-                ClassPath classPath = buildBuildSrc(buildDefinition, parentClassLoaderScope);
+                ClassPath classPath = buildBuildSrc(buildSrcBuild);
                 context.setResult(BUILD_BUILDSRC_RESULT);
                 return classPath;
             }
@@ -120,31 +95,18 @@ public String getBuildPath() {
         });
     }
 
-    private StartParameterInternal buildSrcStartParameterFor(File buildSrcDir, StartParameter containingBuildParameters) {
-        final StartParameterInternal buildSrcStartParameter = (StartParameterInternal) containingBuildParameters.newBuild();
-        buildSrcStartParameter.setCurrentDir(buildSrcDir);
-        buildSrcStartParameter.setProjectProperties(containingBuildParameters.getProjectProperties());
-        buildSrcStartParameter.doNotSearchUpwards();
-        buildSrcStartParameter.setProfile(containingBuildParameters.isProfile());
-        return buildSrcStartParameter;
-    }
-
     @SuppressWarnings("try")
-    private ClassPath buildBuildSrc(final BuildDefinition buildDefinition, ClassLoaderScope parentClassLoaderScope) {
-        StandAloneNestedBuild nestedBuild = buildRegistry.addBuildSrcNestedBuild(buildDefinition, currentBuild);
-        return nestedBuild.run(buildController -> {
-            // Expose any contributions from the parent's settings
-            buildController.getGradle().setClassLoaderScope(parentClassLoaderScope);
-
-            try (FileLock ignored = buildSrcBuildLockFor(buildDefinition)) {
+    private ClassPath buildBuildSrc(StandAloneNestedBuild buildSrcBuild) {
+        return buildSrcBuild.run(buildController -> {
+            try (FileLock ignored = buildSrcBuildLockFor(buildSrcBuild)) {
                 return new BuildSrcUpdateFactory(buildController, buildSrcBuildListenerFactory, cachedClasspathTransformer).create();
             }
         });
     }
 
-    private FileLock buildSrcBuildLockFor(BuildDefinition buildDefinition) {
+    private FileLock buildSrcBuildLockFor(StandAloneNestedBuild build) {
         return fileLockManager.lock(
-            new File(buildDefinition.getBuildRootDir(), ".gradle/noVersion/buildSrc"),
+            new File(build.getBuildRootDir(), ".gradle/noVersion/buildSrc"),
             LOCK_OPTIONS,
             "buildSrc build lock"
         );
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/AbstractBuildState.java b/subprojects/core/src/main/java/org/gradle/internal/build/AbstractBuildState.java
index fe0038f..95580f7 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/AbstractBuildState.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/AbstractBuildState.java
@@ -16,21 +16,24 @@
 
 package org.gradle.internal.build;
 
-import org.gradle.api.artifacts.component.BuildIdentifier;
-import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
-import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier;
+import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.ProjectStateRegistry;
 import org.gradle.execution.taskgraph.TaskExecutionGraphInternal;
-import org.gradle.initialization.DefaultProjectDescriptor;
 import org.gradle.initialization.IncludedBuildSpec;
-import org.gradle.util.Path;
+import org.gradle.internal.Describables;
+import org.gradle.internal.DisplayName;
 
 import java.util.function.Consumer;
 
 public abstract class AbstractBuildState implements BuildState {
     @Override
+    public DisplayName getDisplayName() {
+        return Describables.of(getBuildIdentifier());
+    }
+
+    @Override
     public String toString() {
-        return getBuildIdentifier().toString();
+        return getDisplayName().getDisplayName();
     }
 
     @Override
@@ -50,19 +53,22 @@ public BuildProjectRegistry getProjects() {
         return getProjectStateRegistry().projectsFor(getBuildIdentifier());
     }
 
+    protected abstract BuildLifecycleController getBuildController();
+
     @Override
-    public ProjectComponentIdentifier getIdentifierForProject(Path projectPath) {
-        BuildIdentifier buildIdentifier = getBuildIdentifier();
-        Path identityPath = getIdentityPathForProject(projectPath);
-        DefaultProjectDescriptor project = getLoadedSettings().getProjectRegistry().getProject(projectPath.getPath());
-        if (project == null) {
-            throw new IllegalArgumentException("Project " + projectPath + " not found.");
-        }
-        String name = project.getName();
-        return new DefaultProjectComponentIdentifier(buildIdentifier, identityPath, projectPath, name);
+    public void ensureProjectsLoaded() {
+        getBuildController().getLoadedSettings();
     }
 
-    protected abstract BuildLifecycleController getBuildController();
+    @Override
+    public void ensureProjectsConfigured() {
+        getBuildController().getConfiguredBuild();
+    }
+
+    @Override
+    public SettingsInternal getLoadedSettings() throws IllegalStateException {
+        return getBuildController().getLoadedSettings();
+    }
 
     @Override
     public void populateWorkGraph(Consumer<? super TaskExecutionGraphInternal> action) {
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/BuildProjectRegistry.java b/subprojects/core/src/main/java/org/gradle/internal/build/BuildProjectRegistry.java
index ef5bf1d..e2b7306 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/BuildProjectRegistry.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/BuildProjectRegistry.java
@@ -19,18 +19,32 @@
 import org.gradle.api.internal.project.ProjectState;
 import org.gradle.util.Path;
 
+import javax.annotation.Nullable;
 import java.util.Set;
 
 public interface BuildProjectRegistry {
     /**
+     * Returns the root project of this build.
+     */
+    ProjectState getRootProject();
+
+    /**
      * Returns all projects in this build, in public iteration order.
      */
     Set<? extends ProjectState> getAllProjects();
 
     /**
-     * Locates a project of this build.
+     * Locates a project of this build, failing if the project is not present.
      *
      * @param projectPath The path relative to the root project of this build.
      */
     ProjectState getProject(Path projectPath);
+
+    /**
+     * Locates a project of this build, returning null if the project is not present.
+     *
+     * @param projectPath The path relative to the root project of this build.
+     */
+    @Nullable
+    ProjectState findProject(Path projectPath);
 }
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/BuildState.java b/subprojects/core/src/main/java/org/gradle/internal/build/BuildState.java
index 42e25bb..d52c691 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/BuildState.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/BuildState.java
@@ -17,11 +17,11 @@
 package org.gradle.internal.build;
 
 import org.gradle.api.artifacts.component.BuildIdentifier;
-import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
 import org.gradle.execution.taskgraph.TaskExecutionGraphInternal;
 import org.gradle.initialization.IncludedBuildSpec;
+import org.gradle.internal.DisplayName;
 import org.gradle.util.Path;
 
 import java.io.File;
@@ -33,6 +33,8 @@
  * Implementations are not yet entirely thread-safe, but should be.
  */
 public interface BuildState {
+    DisplayName getDisplayName();
+
     /**
      * Returns the identifier for this build. The identifier is fixed for the lifetime of the build.
      */
@@ -70,15 +72,21 @@ public interface BuildState {
     /**
      * Calculates the identity path for a project in this build.
      */
-    Path getIdentityPathForProject(Path projectPath) throws IllegalStateException;
+    Path calculateIdentityPathForProject(Path projectPath) throws IllegalStateException;
 
     /**
-     * Calculates the identifier for a project in this build.
+     * Loads the projects for this build so that {@link #getProjects()} can be used, if not already done.
+     * This includes running the settings script for the build.
      */
-    ProjectComponentIdentifier getIdentifierForProject(Path projectPath) throws IllegalStateException;
+    void ensureProjectsLoaded();
 
     /**
-     * Returns the projects of this build.
+     * Ensures all projects in this build are configured, if not already done.
+     */
+    void ensureProjectsConfigured();
+
+    /**
+     * Returns the projects of this build. Fails if the projects are not yet loaded for this build.
      */
     BuildProjectRegistry getProjects();
 
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/BuildStateRegistry.java b/subprojects/core/src/main/java/org/gradle/internal/build/BuildStateRegistry.java
index d3594a6..a1c1cf9 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/BuildStateRegistry.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/BuildStateRegistry.java
@@ -99,9 +99,10 @@ public interface BuildStateRegistry {
     IncludedBuildState addImplicitIncludedBuild(BuildDefinition buildDefinition);
 
     /**
-     * Creates a standalone nested build.
+     * Locates the buildSrc build for the given build, if present. Returns null if the given build does not have an associated buildSrc build.
      */
-    StandAloneNestedBuild addBuildSrcNestedBuild(BuildDefinition buildDefinition, BuildState owner);
+    @Nullable
+    StandAloneNestedBuild getBuildSrcNestedBuild(BuildState owner);
 
     /**
      * Creates a new standalone nested build tree.
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/BuildToolingModelController.java b/subprojects/core/src/main/java/org/gradle/internal/build/BuildToolingModelController.java
index 3664f2c..f558c26 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/BuildToolingModelController.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/BuildToolingModelController.java
@@ -17,21 +17,28 @@
 package org.gradle.internal.build;
 
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectState;
 import org.gradle.internal.operations.RunnableBuildOperation;
+import org.gradle.tooling.provider.model.UnknownModelException;
+import org.gradle.tooling.provider.model.internal.ToolingModelBuilderLookup;
 
 import java.util.Collection;
 
+/**
+ * Coordinates the building of tooling models.
+ */
 public interface BuildToolingModelController {
     /**
-     * Returns the current state of the mutable model.
-     */
-    GradleInternal getMutableModel();
-
-    /**
      * Returns the mutable model, configuring if necessary.
      */
     GradleInternal getConfiguredModel();
 
+    ToolingModelBuilderLookup.Builder locateBuilderForDefaultTarget(String modelName, boolean param) throws UnknownModelException;
+
+    ToolingModelBuilderLookup.Builder locateBuilderForTarget(BuildState target, String modelName, boolean param) throws UnknownModelException;
+
+    ToolingModelBuilderLookup.Builder locateBuilderForTarget(ProjectState target, String modelName, boolean param) throws UnknownModelException;
+
     boolean queryModelActionsRunInParallel();
 
     /**
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildFactory.java b/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildFactory.java
index a3d3978..2afc671 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildFactory.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildFactory.java
@@ -31,6 +31,6 @@ public interface IncludedBuildFactory {
      * This ensures that the required classloaders are created in the right order which is required for configuration cache
      */
     default void prepareBuild(IncludedBuildState includedBuild) {
-        includedBuild.loadSettings();
+        includedBuild.getLoadedSettings();
     }
 }
diff --git a/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildState.java b/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildState.java
index fb56833..a367d90 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildState.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/build/IncludedBuildState.java
@@ -20,7 +20,6 @@
 import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.DependencySubstitutions;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
 
 import java.io.File;
 
@@ -36,10 +35,6 @@ public interface IncludedBuildState extends NestedBuildState, CompositeBuildPart
 
     Action<? super DependencySubstitutions> getRegisteredDependencySubstitutions();
 
-    boolean hasInjectedSettingsPlugins();
-
-    SettingsInternal loadSettings();
-
     GradleInternal getConfiguredBuild();
 
     <T> T withState(Transformer<T, ? super GradleInternal> action);
diff --git a/subprojects/core/src/main/java/org/gradle/internal/buildtree/DefaultBuildTreeModelCreator.java b/subprojects/core/src/main/java/org/gradle/internal/buildtree/DefaultBuildTreeModelCreator.java
index 8d02a39..3dac5a5 100644
--- a/subprojects/core/src/main/java/org/gradle/internal/buildtree/DefaultBuildTreeModelCreator.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/buildtree/DefaultBuildTreeModelCreator.java
@@ -17,24 +17,38 @@
 package org.gradle.internal.buildtree;
 
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectState;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.build.BuildLifecycleController;
+import org.gradle.internal.build.BuildState;
 import org.gradle.internal.build.BuildToolingModelAction;
 import org.gradle.internal.build.BuildToolingModelController;
 import org.gradle.internal.operations.BuildOperationExecutor;
 import org.gradle.internal.operations.RunnableBuildOperation;
+import org.gradle.internal.resources.ProjectLeaseRegistry;
+import org.gradle.tooling.provider.model.UnknownModelException;
+import org.gradle.tooling.provider.model.internal.ToolingModelBuilderLookup;
 
 import java.util.Collection;
 
 public class DefaultBuildTreeModelCreator implements BuildTreeModelCreator {
     private final BuildLifecycleController buildController;
     private final BuildOperationExecutor buildOperationExecutor;
+    private final ProjectLeaseRegistry projectLeaseRegistry;
     private final boolean parallelActions;
+    // Apply some locking around configuration and tooling model lookup. This should move deeper into the build tree and build state controllers
+    private final Object treeMutableStateLock = new Object();
 
-    public DefaultBuildTreeModelCreator(BuildLifecycleController buildLifecycleController, BuildOperationExecutor buildOperationExecutor) {
+    public DefaultBuildTreeModelCreator(
+        BuildLifecycleController buildLifecycleController,
+        BuildOperationExecutor buildOperationExecutor,
+        ProjectLeaseRegistry projectLeaseRegistry
+    ) {
         this.buildController = buildLifecycleController;
         this.buildOperationExecutor = buildOperationExecutor;
         parallelActions = !"false".equalsIgnoreCase(buildLifecycleController.getGradle().getStartParameter().getSystemPropertiesArgs().get("org.gradle.internal.tooling.parallel"));
+        this.projectLeaseRegistry = projectLeaseRegistry;
     }
 
     @Override
@@ -54,13 +68,46 @@ public GradleInternal getConfiguredModel() {
         }
 
         @Override
-        public GradleInternal getMutableModel() {
-            return buildController.getGradle();
+        public ToolingModelBuilderLookup.Builder locateBuilderForDefaultTarget(String modelName, boolean param) {
+            synchronized (treeMutableStateLock) {
+                // Look for a build scoped builder
+                ToolingModelBuilderLookup lookup = buildController.getGradle().getServices().get(ToolingModelBuilderLookup.class);
+                ToolingModelBuilderLookup.Builder builder = lookup.maybeLocateForBuildScope(modelName, param, buildController.getGradle().getOwner());
+                if (builder != null) {
+                    return builder;
+                }
+
+                // Locate a project scoped model using default project
+                return locateBuilderForTarget(buildController.getGradle().getOwner(), modelName, param);
+            }
+        }
+
+        @Override
+        public ToolingModelBuilderLookup.Builder locateBuilderForTarget(BuildState target, String modelName, boolean param) throws UnknownModelException {
+            synchronized (treeMutableStateLock) {
+                // Force configuration of the build and locate builder for default project
+                buildController.getGradle().getOwner().ensureProjectsConfigured();
+                target.ensureProjectsConfigured();
+                ProjectInternal targetProject = target.getMutableModel().getDefaultProject();
+                ToolingModelBuilderLookup lookup = targetProject.getServices().get(ToolingModelBuilderLookup.class);
+                return lookup.locateForClientOperation(modelName, param, targetProject.getOwner());
+            }
+        }
+
+        @Override
+        public ToolingModelBuilderLookup.Builder locateBuilderForTarget(ProjectState target, String modelName, boolean param) throws UnknownModelException {
+            synchronized (treeMutableStateLock) {
+                // Force configuration of the containing build and then locate the builder for target project
+                buildController.getGradle().getOwner().ensureProjectsConfigured();
+                target.getOwner().ensureProjectsConfigured();
+                ToolingModelBuilderLookup lookup = target.getMutableModel().getServices().get(ToolingModelBuilderLookup.class);
+                return lookup.locateForClientOperation(modelName, param, target);
+            }
         }
 
         @Override
         public boolean queryModelActionsRunInParallel() {
-            return parallelActions;
+            return projectLeaseRegistry.getAllowsParallelExecution() && parallelActions;
         }
 
         @Override
diff --git a/subprojects/core/src/main/java/org/gradle/invocation/DefaultGradle.java b/subprojects/core/src/main/java/org/gradle/invocation/DefaultGradle.java
index e754e46..4e51a38 100644
--- a/subprojects/core/src/main/java/org/gradle/invocation/DefaultGradle.java
+++ b/subprojects/core/src/main/java/org/gradle/invocation/DefaultGradle.java
@@ -70,6 +70,7 @@
 import java.io.File;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Supplier;
 
 public abstract class DefaultGradle extends AbstractPluginAware implements GradleInternal {
 
@@ -86,7 +87,7 @@ public abstract class DefaultGradle extends AbstractPluginAware implements Gradl
     private final MutableActionSet<Project> rootProjectActions = new MutableActionSet<Project>();
     private boolean projectsLoaded;
     private Path identityPath;
-    private ClassLoaderScope classLoaderScope;
+    private Supplier<? extends ClassLoaderScope> classLoaderScope;
     private ClassLoaderScope baseProjectClassLoaderScope;
 
     public DefaultGradle(@Nullable BuildState parent, StartParameter startParameter, ServiceRegistryFactory parentRegistry) {
@@ -255,6 +256,9 @@ public void execute(Project project) {
 
     @Override
     public ProjectInternal getDefaultProject() {
+        if (defaultProject == null) {
+            throw new IllegalStateException("The default project is not yet available for " + this + ".");
+        }
         return defaultProject;
     }
 
@@ -464,7 +468,7 @@ protected DefaultObjectConfigurationAction createObjectConfigurationAction() {
     }
 
     @Override
-    public void setClassLoaderScope(ClassLoaderScope classLoaderScope) {
+    public void setClassLoaderScope(Supplier<? extends ClassLoaderScope> classLoaderScope) {
         if (this.classLoaderScope != null) {
             throw new IllegalStateException("Class loader scope already used");
         }
@@ -474,60 +478,44 @@ public void setClassLoaderScope(ClassLoaderScope classLoaderScope) {
     @Override
     public ClassLoaderScope getClassLoaderScope() {
         if (classLoaderScope == null) {
-            classLoaderScope = services.get(ClassLoaderScopeRegistry.class).getCoreAndPluginsScope();
+            classLoaderScope = () -> getClassLoaderScopeRegistry().getCoreAndPluginsScope();
         }
-        return classLoaderScope;
+        return classLoaderScope.get();
     }
 
+    @Inject
+    protected abstract ClassLoaderScopeRegistry getClassLoaderScopeRegistry();
+
     @Override
     @Inject
     public abstract ProjectRegistry<ProjectInternal> getProjectRegistry();
 
     @Inject
-    protected TextUriResourceLoader.Factory getResourceLoaderFactory() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract TextUriResourceLoader.Factory getResourceLoaderFactory();
 
     @Inject
-    protected ScriptHandlerFactory getScriptHandlerFactory() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract ScriptHandlerFactory getScriptHandlerFactory();
 
     @Inject
-    protected ScriptPluginFactory getScriptPluginFactory() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract ScriptPluginFactory getScriptPluginFactory();
 
     @Inject
-    protected FileResolver getFileResolver() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract FileResolver getFileResolver();
 
     @Inject
-    protected CurrentGradleInstallation getCurrentGradleInstallation() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract CurrentGradleInstallation getCurrentGradleInstallation();
 
     @Inject
-    protected ListenerManager getListenerManager() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract ListenerManager getListenerManager();
 
     @Inject
-    protected ListenerBuildOperationDecorator getListenerBuildOperationDecorator() {
-        throw new UnsupportedOperationException();
-    }
+    protected abstract ListenerBuildOperationDecorator getListenerBuildOperationDecorator();
 
     @Override
     @Inject
-    public PluginManagerInternal getPluginManager() {
-        throw new UnsupportedOperationException();
-    }
+    public abstract PluginManagerInternal getPluginManager();
 
     @Override
     @Inject
-    public PublicBuildPath getPublicBuildPath() {
-        throw new UnsupportedOperationException();
-    }
-
+    public abstract PublicBuildPath getPublicBuildPath();
 }
diff --git a/subprojects/core/src/main/java/org/gradle/process/internal/worker/DefaultWorkerProcessBuilder.java b/subprojects/core/src/main/java/org/gradle/process/internal/worker/DefaultWorkerProcessBuilder.java
index 86c02df..82d595a 100644
--- a/subprojects/core/src/main/java/org/gradle/process/internal/worker/DefaultWorkerProcessBuilder.java
+++ b/subprojects/core/src/main/java/org/gradle/process/internal/worker/DefaultWorkerProcessBuilder.java
@@ -100,10 +100,21 @@ public String getBaseName() {
 
     @Override
     public WorkerProcessBuilder applicationClasspath(Iterable<File> files) {
-        GUtil.addToCollection(applicationClasspath, files);
+        for (File file : files) {
+            if (file == null) {
+                throw new IllegalArgumentException("Illegal null value provided in this collection: " + files);
+            }
+            if (isEntryValid(file)) {
+                applicationClasspath.add(file);
+            }
+        }
         return this;
     }
 
+    private boolean isEntryValid(File file) {
+        return file.exists() || ("*".equals(file.getName()) && file.getParentFile() != null && file.getParentFile().exists());
+    }
+
     @Override
     public Set<File> getApplicationClasspath() {
         return applicationClasspath;
diff --git a/subprojects/core/src/main/java/org/gradle/testfixtures/internal/ProjectBuilderImpl.java b/subprojects/core/src/main/java/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
index 3f17e1e..1617690 100644
--- a/subprojects/core/src/main/java/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
+++ b/subprojects/core/src/main/java/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
@@ -21,10 +21,8 @@
 import org.gradle.api.artifacts.component.BuildIdentifier;
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.StartParameterInternal;
 import org.gradle.api.internal.artifacts.DefaultBuildIdentifier;
-import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.temp.DefaultTemporaryFileProvider;
 import org.gradle.api.internal.file.temp.TemporaryFileProvider;
@@ -236,6 +234,10 @@ protected BuildLifecycleController getBuildController() {
         }
 
         @Override
+        public void ensureProjectsLoaded() {
+        }
+
+        @Override
         public BuildWorkGraph getWorkGraph() {
             throw new UnsupportedOperationException();
         }
@@ -261,17 +263,12 @@ public boolean isImplicitBuild() {
         }
 
         @Override
-        public SettingsInternal getLoadedSettings() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
         public Path getCurrentPrefixForProjectsInChildBuilds() {
             return Path.ROOT;
         }
 
         @Override
-        public Path getIdentityPathForProject(Path projectPath) {
+        public Path calculateIdentityPathForProject(Path projectPath) {
             return projectPath;
         }
 
@@ -286,15 +283,6 @@ public <T> T run(Function<? super BuildTreeLifecycleController, T> action) {
         }
 
         @Override
-        public ProjectComponentIdentifier getIdentifierForProject(Path projectPath) {
-            String name = projectPath.getName();
-            if (name == null) {
-                name = "root";
-            }
-            return new DefaultProjectComponentIdentifier(getBuildIdentifier(), projectPath, projectPath, name);
-        }
-
-        @Override
         public IncludedBuildInternal getModel() {
             throw new UnsupportedOperationException();
         }
diff --git a/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/BuildScopeModelBuilder.java b/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/BuildScopeModelBuilder.java
new file mode 100644
index 0000000..0755f9c
--- /dev/null
+++ b/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/BuildScopeModelBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.provider.model.internal;
+
+import org.gradle.internal.build.BuildState;
+
+public interface BuildScopeModelBuilder {
+    /**
+     * Creates the model for the given target. The target build will not necessarily have been configured, and it is the builder's responsibility
+     * to transition the target into the appropriate state required to create the model.
+     */
+    Object create(BuildState target);
+}
diff --git a/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java b/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java
index aff7b14..30cd76c 100644
--- a/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java
+++ b/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistry.java
@@ -18,11 +18,13 @@
 
 import com.google.common.collect.Iterators;
 import org.gradle.api.Project;
-import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectState;
 import org.gradle.api.internal.project.ProjectStateRegistry;
 import org.gradle.configuration.internal.UserCodeApplicationContext;
 import org.gradle.internal.Cast;
+import org.gradle.internal.DisplayName;
+import org.gradle.internal.build.BuildState;
 import org.gradle.internal.operations.BuildOperationContext;
 import org.gradle.internal.operations.BuildOperationDescriptor;
 import org.gradle.internal.operations.BuildOperationExecutor;
@@ -85,32 +87,47 @@ public void register(ToolingModelBuilder builder) {
     @Override
     public ToolingModelBuilder getBuilder(String modelName) throws UnsupportedOperationException {
         Registration registration = get(modelName);
-        return new LenientToolingModelBuilder(registration.getBuilder());
+        return new LenientToolingModelBuilder(registration.getBuilder(), projectStateRegistry);
     }
 
     @Override
-    public Builder locateForClientOperation(String modelName, boolean parameter, GradleInternal target) throws UnknownModelException {
-        ProjectInternal project = target.getDefaultProject();
-        return new BuildOperationWrappingBuilder(new LockAllProjectsBuilder(locateForClientOperation(modelName, project, parameter), projectStateRegistry), modelName, project, buildOperationExecutor);
+    public Builder locateForClientOperation(String modelName, boolean parameter, ProjectState target) throws UnknownModelException {
+        return new BuildOperationWrappingBuilder(
+            new LockSingleProjectBuilder(
+                locateForClientOperation(modelName, target.getMutableModel(), parameter), target), modelName, target.getDisplayName(), buildOperationExecutor);
     }
 
+    @Nullable
     @Override
-    public Builder locateForClientOperation(String modelName, boolean parameter, ProjectInternal target) throws UnknownModelException {
-        return new BuildOperationWrappingBuilder(new LockSingleProjectBuilder(locateForClientOperation(modelName, target, parameter), target, projectStateRegistry), modelName, target, buildOperationExecutor);
+    public Builder maybeLocateForBuildScope(String modelName, boolean parameter, BuildState target) {
+        Registration registration = find(modelName);
+        if (registration == null) {
+            return null;
+        }
+
+        if (!(registration.getBuilder() instanceof BuildScopeModelBuilder)) {
+            return null;
+        }
+
+        BuildScopeModelBuilder buildScopeModelBuilder = (BuildScopeModelBuilder) registration.getBuilder();
+        return new BuildOperationWrappingBuilder(
+            restoreUserCodeApplication(
+                new LockAllProjectsBuilder(
+                    new BuildScopedBuilder(buildScopeModelBuilder, target), projectStateRegistry), registration), modelName, target.getDisplayName(), buildOperationExecutor);
     }
 
     private Builder locateForClientOperation(String modelName, ProjectInternal project, boolean parameter) throws UnknownModelException {
         Registration registration = get(modelName);
         if (registration.getBuilder() instanceof ParameterizedToolingModelBuilder) {
-            return wrap(new BuilderWithParameter(modelName, project, (ParameterizedToolingModelBuilder) registration.getBuilder()), registration);
+            return restoreUserCodeApplication(new BuilderWithParameter(modelName, project, (ParameterizedToolingModelBuilder) registration.getBuilder()), registration);
         }
         if (parameter) {
             throw new UnknownModelException(String.format("No parameterized builders are available to build a model of type '%s'.", modelName));
         }
-        return wrap(new BuilderWithNoParameter(modelName, project, registration.getBuilder()), registration);
+        return restoreUserCodeApplication(new BuilderWithNoParameter(modelName, project, registration.getBuilder()), registration);
     }
 
-    private Builder wrap(Builder delegate, Registration registration) {
+    private Builder restoreUserCodeApplication(Builder delegate, Registration registration) {
         UserCodeApplicationContext.Application registeredBy = registration.getRegisteredBy();
         if (registeredBy == null) {
             return delegate;
@@ -168,11 +185,34 @@ public UserCodeApplicationContext.Application getRegisteredBy() {
         }
     }
 
-    private class LenientToolingModelBuilder implements ToolingModelBuilder {
-        private final ToolingModelBuilder delegate;
+    private static class BuildScopedBuilder implements Builder {
+        private final BuildScopeModelBuilder buildScopeModelBuilder;
+        private final BuildState target;
 
-        public LenientToolingModelBuilder(ToolingModelBuilder delegate) {
+        public BuildScopedBuilder(BuildScopeModelBuilder buildScopeModelBuilder, BuildState target) {
+            this.buildScopeModelBuilder = buildScopeModelBuilder;
+            this.target = target;
+        }
+
+        @Nullable
+        @Override
+        public Class<?> getParameterType() {
+            return null;
+        }
+
+        @Override
+        public Object build(@Nullable Object parameter) {
+            return buildScopeModelBuilder.create(target);
+        }
+    }
+
+    private static class LenientToolingModelBuilder implements ToolingModelBuilder {
+        private final ToolingModelBuilder delegate;
+        private final ProjectStateRegistry projectStateRegistry;
+
+        public LenientToolingModelBuilder(ToolingModelBuilder delegate, ProjectStateRegistry projectStateRegistry) {
             this.delegate = delegate;
+            this.projectStateRegistry = projectStateRegistry;
         }
 
         @Override
@@ -251,18 +291,16 @@ public Object build(Object parameter) {
     }
 
     private static class LockSingleProjectBuilder extends DelegatingBuilder {
-        private final ProjectInternal project;
-        private final ProjectStateRegistry projectStateRegistry;
+        private final ProjectState target;
 
-        public LockSingleProjectBuilder(Builder delegate, ProjectInternal project, ProjectStateRegistry projectStateRegistry) {
+        public LockSingleProjectBuilder(Builder delegate, ProjectState target) {
             super(delegate);
-            this.project = project;
-            this.projectStateRegistry = projectStateRegistry;
+            this.target = target;
         }
 
         @Override
         public Object build(Object parameter) {
-            return projectStateRegistry.stateFor(project).fromMutableState(p -> delegate.build(parameter));
+            return target.fromMutableState(p -> delegate.build(parameter));
         }
     }
 
@@ -282,13 +320,13 @@ public Object build(Object parameter) {
 
     private static class BuildOperationWrappingBuilder extends DelegatingBuilder {
         private final String modelName;
-        private final ProjectInternal project;
+        private final DisplayName target;
         private final BuildOperationExecutor buildOperationExecutor;
 
-        private BuildOperationWrappingBuilder(Builder delegate, String modelName, ProjectInternal project, BuildOperationExecutor buildOperationExecutor) {
+        private BuildOperationWrappingBuilder(Builder delegate, String modelName, DisplayName target, BuildOperationExecutor buildOperationExecutor) {
             super(delegate);
             this.modelName = modelName;
-            this.project = project;
+            this.target = target;
             this.buildOperationExecutor = buildOperationExecutor;
         }
 
@@ -302,7 +340,7 @@ public Object call(BuildOperationContext context) {
 
                 @Override
                 public BuildOperationDescriptor.Builder description() {
-                    return BuildOperationDescriptor.displayName("Build model '" + modelName + "' for " + project.getDisplayName()).
+                    return BuildOperationDescriptor.displayName("Build model '" + modelName + "' for " + target.getDisplayName()).
                         progressDisplayName("Building model '" + modelName + "'");
                 }
             });
diff --git a/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/ToolingModelBuilderLookup.java b/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/ToolingModelBuilderLookup.java
index 3288d6f..2c14082 100644
--- a/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/ToolingModelBuilderLookup.java
+++ b/subprojects/core/src/main/java/org/gradle/tooling/provider/model/internal/ToolingModelBuilderLookup.java
@@ -16,9 +16,9 @@
 
 package org.gradle.tooling.provider.model.internal;
 
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectState;
 import org.gradle.configuration.internal.UserCodeApplicationContext;
+import org.gradle.internal.build.BuildState;
 import org.gradle.internal.service.scopes.Scopes;
 import org.gradle.internal.service.scopes.ServiceScope;
 import org.gradle.tooling.provider.model.ToolingModelBuilder;
@@ -32,14 +32,12 @@ public interface ToolingModelBuilderLookup {
     Registration find(String modelName);
 
     /**
-     * Locates a builder for a build-scoped model.
-     */
-    Builder locateForClientOperation(String modelName, boolean parameter, GradleInternal target) throws UnknownModelException;
-
-    /**
      * Locates a builder for a project-scoped model.
      */
-    Builder locateForClientOperation(String modelName, boolean parameter, ProjectInternal target) throws UnknownModelException;
+    Builder locateForClientOperation(String modelName, boolean parameter, ProjectState target) throws UnknownModelException;
+
+    @Nullable
+    Builder maybeLocateForBuildScope(String modelName, boolean parameter, BuildState target);
 
     interface Registration {
         ToolingModelBuilder getBuilder();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectStateRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectStateRegistryTest.groovy
index e2b4cf0..3eeba6f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectStateRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectStateRegistryTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.project
 
-
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier
 import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.SettingsInternal
 import org.gradle.api.internal.artifacts.DefaultBuildIdentifier
@@ -43,63 +43,66 @@
     def "adds projects for a build"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
 
         expect:
         registry.allProjects.size() == 3
 
-        def root = registry.stateFor(project(":"))
+        def root = registry.stateFor(projectId(":"))
         root.name == "root"
+        root.displayName.displayName == "root project 'root'"
         root.identityPath == Path.ROOT
         root.projectPath == Path.ROOT
         root.componentIdentifier.projectPath == ":"
         root.parent == null
 
-        def p1 = registry.stateFor(project("p1"))
+        def p1 = registry.stateFor(projectId("p1"))
         p1.name == "p1"
+        p1.displayName.displayName == "project :p1"
         p1.identityPath == Path.path(":p1")
         p1.projectPath == Path.path(":p1")
-        p1.parent == root
+        p1.parent.is(root)
         p1.componentIdentifier.projectPath == ":p1"
+        p1.childProjects.empty
 
-        def p2 = registry.stateFor(project("p2"))
+        def p2 = registry.stateFor(projectId("p2"))
         p2.name == "p2"
+        p2.displayName.displayName == "project :p2"
         p2.identityPath == Path.path(":p2")
         p2.projectPath == Path.path(":p2")
-        p2.parent == root
+        p2.parent.is(root)
         p2.componentIdentifier.projectPath == ":p2"
+        p2.childProjects.empty
+
+        root.childProjects.toList() == [p1, p2]
 
         registry.stateFor(root.componentIdentifier).is(root)
         registry.stateFor(p1.componentIdentifier).is(p1)
         registry.stateFor(p2.componentIdentifier).is(p2)
 
         def projects = registry.projectsFor(build.buildIdentifier)
+        projects.rootProject.is(root)
         projects.getProject(Path.ROOT).is(root)
         projects.getProject(Path.path(":p1")).is(p1)
         projects.getProject(Path.path(":p2")).is(p2)
 
-        // Currently need to attach the project objects in order to query all projects from registry
-        createRootProject()
-        createProject(p1, project("p1"))
-        createProject(p2, project("p2"))
-
         projects.allProjects.toList() == [root, p1, p2]
     }
 
     def "can create mutable project model"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
 
-        def rootProject = project(':')
-        def rootState = registry.stateFor(rootProject)
+        def rootProject = project(":")
+        def rootState = registry.stateFor(projectId(":"))
 
         1 * projectFactory.createProject(_, _, rootState, _, _, _) >> rootProject
 
         rootState.createMutableModel(Stub(ClassLoaderScope), Stub(ClassLoaderScope))
 
         def project = project("p1")
-        def state = registry.stateFor(project)
+        def state = registry.stateFor(projectId("p1"))
 
         1 * projectFactory.createProject(_, _, state, _, _, _) >> project
 
@@ -113,10 +116,9 @@
     def "cannot query mutable project instance when not set"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
-        def project = project("p1")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
 
-        def state = registry.stateFor(project)
+        def state = registry.stateFor(projectId("p1"))
 
         when:
         state.mutableModel
@@ -126,29 +128,14 @@
         e.message == 'The project object for project :p1 has not been attached yet.'
     }
 
-    void createRootProject() {
-        def rootProject = project(':')
-        def rootState = registry.stateFor(rootProject)
-
-        1 * projectFactory.createProject(_, _, rootState, _, _, _) >> rootProject
-
-        rootState.createMutableModel(Stub(ClassLoaderScope), Stub(ClassLoaderScope))
-    }
-
-    void createProject(ProjectState state, ProjectInternal project) {
-        1 * projectFactory.createProject(_, _, state, _, _, _) >> project
-
-        state.createMutableModel(Stub(ClassLoaderScope), Stub(ClassLoaderScope))
-    }
-
     def "one thread can access state at a time"() {
         given:
         def build = build("p1")
         def project = project("p1")
 
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
-        def state = registry.stateFor(project)
+        def state = registry.stateFor(projectId("p1"))
         createProject(state, project)
 
         when:
@@ -188,11 +175,11 @@
         def project1 = project("p1")
         def project2 = project("p2")
 
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
-        def state2 = registry.stateFor(project2)
+        def state2 = registry.stateFor(projectId("p2"))
         createProject(state2, project2)
 
         def projectLock1 = workerLeaseService.getProjectLock(build.getIdentityPath(), project1.getIdentityPath())
@@ -230,8 +217,8 @@
     def "can access projects with all projects locked"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
-        def state = registry.stateFor(project("p1"))
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
+        def state = registry.stateFor(projectId("p1"))
 
         expect:
         !state.hasMutableState()
@@ -252,7 +239,7 @@
     def "cannot lock projects state while another thread has locked all projects"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
 
         when:
         async {
@@ -276,7 +263,7 @@
     def "cannot lock project state while another thread has locked all projects"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
 
         when:
         async {
@@ -288,7 +275,7 @@
             }
             start {
                 thread.blockUntil.start
-                registry.stateFor(project("p1")).applyToMutableState {
+                registry.stateFor(projectId("p1")).applyToMutableState {
                 }
             }
         }
@@ -300,12 +287,12 @@
     def "releases lock for all projects while running blocking operation"() {
         given:
         def build = build("p1", "p2")
-        registry.registerProjects(build)
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
 
         when:
         async {
             registry.withMutableStateOfAllProjects {
-                def state = registry.stateFor(project("p1"))
+                def state = registry.stateFor(projectId("p1"))
                 assert state.hasMutableState()
                 registry.blocking {
                     assertTrue !state.hasMutableState()
@@ -320,12 +307,13 @@
 
     def "thread can be granted uncontrolled access to all projects"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
-        def state2 = registry.stateFor(project("p2"))
+        def state2 = registry.stateFor(projectId("p2"))
 
         when:
         async {
@@ -362,12 +350,13 @@
 
     def "multiple threads can nest calls with uncontrolled access to all projects"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
-        def state2 = registry.stateFor(project("p2"))
+        def state2 = registry.stateFor(projectId("p2"))
 
         when:
         async {
@@ -400,12 +389,13 @@
 
     def "thread can be granted uncontrolled access to a single project"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
-        def state2 = registry.stateFor(project("p2"))
+        def state2 = registry.stateFor(projectId("p2"))
 
         when:
         async {
@@ -442,13 +432,14 @@
 
     def "multiple threads can nest calls with uncontrolled access to specific projects"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
         def project2 = project("p2")
-        def state2 = registry.stateFor(project2)
+        def state2 = registry.stateFor(projectId("p2"))
         createProject(state2, project2)
 
         when:
@@ -486,13 +477,14 @@
 
     def "thread must own project state in order to set calculated value"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
         def project2 = project("p2")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
-        def state2 = registry.stateFor(project2)
+        def state2 = registry.stateFor(projectId("p2"))
         createProject(state2, project2)
         def calculatedValue = state1.newCalculatedValue("initial")
 
@@ -526,13 +518,14 @@
 
     def "thread must own project state in order to update calculated value"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
         def project2 = project("p2")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
-        def state2 = registry.stateFor(project2)
+        def state2 = registry.stateFor(projectId("p2"))
         createProject(state2, project2)
         def calculatedValue = state1.newCalculatedValue("initial")
 
@@ -569,10 +562,11 @@
 
     def "update thread blocks other update threads"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
         def calculatedValue = state1.newCalculatedValue("initial")
 
@@ -608,10 +602,11 @@
 
     def "update thread does not block other read threads"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
         def calculatedValue = state1.newCalculatedValue("initial")
 
@@ -640,13 +635,14 @@
 
     def "can have cycle in project dependencies"() {
         given:
-        registry.registerProjects(build("p1", "p2"))
+        def build = build("p1", "p2")
+        registry.registerProjects(build, build.loadedSettings.projectRegistry)
         createRootProject()
         def project1 = project("p1")
-        def state1 = registry.stateFor(project1)
+        def state1 = registry.stateFor(projectId("p1"))
         createProject(state1, project1)
         def project2 = project("p2")
-        def state2 = registry.stateFor(project2)
+        def state2 = registry.stateFor(projectId("p2"))
         createProject(state2, project2)
         def calculatedValue = state1.newCalculatedValue("initial")
 
@@ -682,11 +678,31 @@
         calculatedValue.get() == "updated2"
     }
 
+    void createRootProject() {
+        def rootProject = project(':')
+        def rootState = registry.stateFor(projectId(':'))
+
+        1 * projectFactory.createProject(_, _, rootState, _, _, _) >> rootProject
+
+        rootState.createMutableModel(Stub(ClassLoaderScope), Stub(ClassLoaderScope))
+    }
+
+    void createProject(ProjectState state, ProjectInternal project) {
+        1 * projectFactory.createProject(_, _, state, _, _, _) >> project
+
+        state.createMutableModel(Stub(ClassLoaderScope), Stub(ClassLoaderScope))
+    }
+
+    ProjectComponentIdentifier projectId(String name) {
+        def path = name == ':' ? Path.ROOT : Path.ROOT.child(name)
+        return new DefaultProjectComponentIdentifier(DefaultBuildIdentifier.ROOT, path, path, name)
+    }
+
     ProjectInternal project(String name) {
         def project = Stub(ProjectInternal)
         def path = name == ':' ? Path.ROOT : Path.ROOT.child(name)
         project.identityPath >> path
-        project.compareTo(_) >> { ProjectInternal other -> path.compareTo(other.identityPath) }
+//        project.compareTo(_) >> { ProjectInternal other -> path.compareTo(other.identityPath) }
         return project
     }
 
@@ -704,8 +720,7 @@
         def build = Stub(BuildState)
         build.loadedSettings >> settings
         build.buildIdentifier >> DefaultBuildIdentifier.ROOT
-        build.getIdentityPathForProject(_) >> { Path path -> path }
-        build.getIdentifierForProject(_) >> { Path path -> new DefaultProjectComponentIdentifier(DefaultBuildIdentifier.ROOT, path, path, "??") }
+        build.calculateIdentityPathForProject(_) >> { Path path -> path }
         def services = new DefaultServiceRegistry()
         services.add(projectFactory)
         build.mutableModel >> Stub(GradleInternal) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/worker/DefaultWorkerProcessBuilderSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/worker/DefaultWorkerProcessBuilderSpec.groovy
new file mode 100644
index 0000000..73b5fe4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/worker/DefaultWorkerProcessBuilderSpec.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.process.internal.worker
+
+import org.gradle.internal.id.IdGenerator
+import org.gradle.internal.logging.events.OutputEventListener
+import org.gradle.internal.remote.MessagingServer
+import org.gradle.process.internal.JavaExecHandleFactory
+import org.gradle.process.internal.health.memory.MemoryManager
+import org.gradle.process.internal.worker.child.ApplicationClassesInSystemClassLoaderWorkerImplementationFactory
+import org.gradle.process.internal.JavaExecHandleBuilder
+import spock.lang.Specification
+
+import static org.junit.Assert.assertTrue
+
+class DefaultWorkerProcessBuilderSpec extends Specification {
+    def javaExecHandleBuilder = Mock(JavaExecHandleBuilder)
+    def javaExecHandleFactory = new JavaExecHandleFactory() {
+        @Override
+        JavaExecHandleBuilder newJavaExec() {
+            return javaExecHandleBuilder
+        }
+    }
+    def messagingServer = Mock(MessagingServer)
+    def idGenerator = Mock(IdGenerator)
+    def applicationClassesInSystemClassLoaderWorkerImplementationFactory = Mock(ApplicationClassesInSystemClassLoaderWorkerImplementationFactory)
+    def outputEventListener = Mock(OutputEventListener)
+    def memoryManager = Mock(MemoryManager)
+    DefaultWorkerProcessBuilder builder = new DefaultWorkerProcessBuilder(javaExecHandleFactory,
+        messagingServer,
+        idGenerator,
+        applicationClassesInSystemClassLoaderWorkerImplementationFactory,
+        outputEventListener,
+        memoryManager)
+
+
+    def "validate entries in classpath"() {
+        when:
+        List<File> paths = new ArrayList()
+
+        String validPath1 = System.getProperty("user.dir")
+        String validPath2 = System.getProperty("java.home")
+        String validPath3 = System.getProperty("java.io.tmpdir")
+        String validPath4 = System.getProperty("user.home")
+        String validPath5 = System.getProperty("user.home") + File.separator + "*"
+        String validPath6 = "/*"
+        String inValidPath1 = System.getProperty("user.home") + File.separator + "Non exist path"
+        String inValidPath2 = System.getProperty("user.home") + File.separator + "Non exist path" + File.separator + "*"
+
+        paths.add(new File(validPath1))
+        paths.add(new File(inValidPath2))
+        paths.add(new File(validPath2))
+        paths.add(new File(validPath3))
+        paths.add(new File(inValidPath1))
+        paths.add(new File(validPath4))
+        paths.add(new File(validPath5))
+        paths.add(new File(validPath6))
+
+        def validPathSet = builder.applicationClasspath(paths).getApplicationClasspath()
+
+        then:
+        assertTrue(6 == validPathSet.size())
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.groovy
index 70f51fd..c391bff 100644
--- a/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/tooling/provider/model/internal/DefaultToolingModelBuilderRegistryTest.groovy
@@ -16,11 +16,12 @@
 
 package org.gradle.tooling.provider.model.internal
 
-import org.gradle.api.internal.GradleInternal
+
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.project.ProjectState
 import org.gradle.api.internal.project.ProjectStateRegistry
 import org.gradle.configuration.internal.UserCodeApplicationContext
+import org.gradle.internal.Describables
 import org.gradle.internal.Factory
 import org.gradle.internal.operations.BuildOperationContext
 import org.gradle.internal.operations.BuildOperationExecutor
@@ -68,43 +69,6 @@
         0 * _
     }
 
-    def "wraps model builder in build operation and lock and code application context for all projects when target is build instance"() {
-        def builder1 = Mock(ToolingModelBuilder)
-        def builder2 = Mock(ToolingModelBuilder)
-        def application = Mock(UserCodeApplicationContext.Application)
-        def gradle = Stub(GradleInternal)
-        def project = Stub(ProjectInternal)
-
-        when:
-        registry.register(builder1)
-        registry.register(builder2)
-
-        then:
-        userCodeApplicationContext.current() >> application
-        0 * _
-
-        when:
-        def actualBuilder = registry.locateForClientOperation("model", false, gradle)
-
-        then:
-        builder1.canBuild("model") >> false
-        builder2.canBuild("model") >> true
-        0 * _
-
-        when:
-        def result = actualBuilder.build(null)
-
-        then:
-        result == "result"
-
-        and:
-        1 * buildOperationExecutor.call(_) >> { CallableBuildOperation operation -> operation.call(Stub(BuildOperationContext)) }
-        1 * projectStateRegistry.withMutableStateOfAllProjects(_) >> { Factory factory -> factory.create() }
-        1 * application.reapply(_) >> { Supplier supplier -> supplier.get() }
-        1 * builder2.buildAll("model", project) >> "result"
-        0 * _
-    }
-
     def "wraps model builder in build operation and lock and code application context for project when target is project"() {
         def builder1 = Mock(ToolingModelBuilder)
         def builder2 = Mock(ToolingModelBuilder)
@@ -121,11 +85,13 @@
         0 * _
 
         when:
-        def actualBuilder = registry.locateForClientOperation("model", false, project)
+        def actualBuilder = registry.locateForClientOperation("model", false, projectState)
 
         then:
         builder1.canBuild("model") >> false
         builder2.canBuild("model") >> true
+        projectState.mutableModel >> project
+        projectState.displayName >> Describables.of("<project>")
 
         when:
         def result = actualBuilder.build(null)
@@ -135,7 +101,6 @@
 
         and:
         1 * buildOperationExecutor.call(_) >> { CallableBuildOperation operation -> operation.call(Stub(BuildOperationContext)) }
-        1 * projectStateRegistry.stateFor(project) >> projectState
         1 * projectState.fromMutableState(_) >> { Function f -> f.apply(project) }
         1 * application.reapply(_) >> { Supplier supplier -> supplier.get() }
         1 * builder2.buildAll("model", project) >> "result"
@@ -181,7 +146,7 @@
 
     def "fails when no parameterized builder is available for requested model"() {
         when:
-        registry.locateForClientOperation("model", true, Stub(ProjectInternal))
+        registry.locateForClientOperation("model", true, Stub(ProjectState))
 
         then:
         UnknownModelException e = thrown()
@@ -201,7 +166,7 @@
         builder2.canBuild("model") >> true
 
         when:
-        registry.locateForClientOperation("model", true, Stub(ProjectInternal))
+        registry.locateForClientOperation("model", true, Stub(ProjectState))
 
         then:
         UnknownModelException e = thrown()
@@ -210,17 +175,19 @@
 
     def "wraps parameterized model builder in build operation and all project lock"() {
         def builder = Mock(ParameterizedToolingModelBuilder)
-        def gradle = Stub(GradleInternal)
         def project = Stub(ProjectInternal)
+        def projectState = Mock(ProjectState)
 
         given:
         registry.register(builder)
 
         and:
         builder.canBuild("model") >> true
+        projectState.mutableModel >> project
+        projectState.displayName >> Describables.of("<project>")
 
         expect:
-        def actualBuilder = registry.locateForClientOperation("model", true, gradle)
+        def actualBuilder = registry.locateForClientOperation("model", true, projectState)
 
         when:
         def result = actualBuilder.build("param")
@@ -230,7 +197,7 @@
 
         and:
         1 * buildOperationExecutor.call(_) >> { CallableBuildOperation operation -> operation.call(Stub(BuildOperationContext)) }
-        1 * projectStateRegistry.withMutableStateOfAllProjects(_) >> { Factory factory -> factory.create() }
+        1 * projectState.fromMutableState(_) >> { Function factory -> factory.apply(project) }
         1 * builder.buildAll("model", "param", project) >> "result"
         0 * _
     }
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy
index 8eda60c..22ddb11 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy
@@ -695,7 +695,7 @@
 
         then:
         failure.assertHasDescription("A problem occurred evaluating root project 'depsub'.")
-        failure.assertHasCause("Project :doesnotexist not found.")
+        failure.assertHasCause("Project with path ':doesnotexist' not found in build ':'.")
     }
 
     void "replacing external module dependency with project dependency keeps the original configuration"() {
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MultiProjectProjectDependencyConflictResolutionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MultiProjectProjectDependencyConflictResolutionIntegrationTest.groovy
index 39b0474..1677ff5 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MultiProjectProjectDependencyConflictResolutionIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MultiProjectProjectDependencyConflictResolutionIntegrationTest.groovy
@@ -17,7 +17,6 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.internal.build.BuildState
 
 class MultiProjectProjectDependencyConflictResolutionIntegrationTest extends AbstractProjectDependencyConflictResolutionIntegrationSpec {
 
@@ -28,7 +27,7 @@
 
     @Override
     String getBuildId() {
-        "((${ProjectInternal.name}) project).getServices().get(${BuildState.name}.class).getBuildIdentifier()"
+        "((${ProjectInternal.name}) project).getOwner().getOwner().getBuildIdentifier()"
     }
 
     @Override
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
index 0d22d2a..1e9ade0 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
@@ -1237,6 +1237,7 @@
         buildFile << """
 configurations { compile }
 dependencies {
+    def moduleName = providers.gradleProperty('moduleName').forUseAtConfigurationTime().get()
     compile "group:\$moduleName:1.+"
 }
 configurations.all {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java
index 1d97497..b44b394 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/component/DefaultComponentIdentifierFactory.java
@@ -46,7 +46,7 @@ public ComponentIdentifier createComponentIdentifier(Module module) {
 
     @Override
     public ProjectComponentSelector createProjectComponentSelector(String projectPath) {
-        return DefaultProjectComponentSelector.newSelector(currentBuild.getIdentifierForProject(Path.path(projectPath)));
+        return DefaultProjectComponentSelector.newSelector(currentBuild.getProjects().getProject(Path.path(projectPath)).getComponentIdentifier());
     }
 
     @Override
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java
index cc401e6..25ff1d6 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java
@@ -327,7 +327,7 @@ public void convert(String notation, NotationConvertResult<? super ProjectCompon
         }
 
         static ProjectComponentIdentifier identifierForProject(IncludedBuildState build, String notation) {
-            return build.getIdentifierForProject(Path.path(notation));
+            return build.getProjects().getProject(Path.path(notation)).getComponentIdentifier();
         }
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java
index 8c72981..ad83d09 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java
@@ -92,12 +92,14 @@ public class CachingModuleComponentRepository implements ModuleComponentReposito
     private final LocateInCacheRepositoryAccess locateInCacheRepositoryAccess = new LocateInCacheRepositoryAccess();
     private final ResolveAndCacheRepositoryAccess resolveAndCacheRepositoryAccess = new ResolveAndCacheRepositoryAccess();
 
-    public CachingModuleComponentRepository(ModuleComponentRepository delegate,
-                                            ModuleRepositoryCaches caches,
-                                            CachePolicy cachePolicy,
-                                            BuildCommencedTimeProvider timeProvider,
-                                            ComponentMetadataProcessor metadataProcessor,
-                                            ChangingValueDependencyResolutionListener listener) {
+    public CachingModuleComponentRepository(
+        ModuleComponentRepository delegate,
+        ModuleRepositoryCaches caches,
+        CachePolicy cachePolicy,
+        BuildCommencedTimeProvider timeProvider,
+        ComponentMetadataProcessor metadataProcessor,
+        ChangingValueDependencyResolutionListener listener
+    ) {
         this.delegate = delegate;
         this.moduleMetadataCache = caches.moduleMetadataCache;
         this.moduleVersionsCache = caches.moduleVersionsCache;
@@ -175,10 +177,11 @@ private void listModuleVersionsFromCache(ModuleDependencyMetadata dependency, Bu
                 if (expiry.isMustCheck()) {
                     LOGGER.debug("Version listing in dynamic revision cache is expired: will perform fresh resolve of '{}' in '{}'", requested, delegate.getName());
                 } else {
-                    listener.onDynamicVersionSelection(requested, expiry);
-                    result.listed(versionList);
                     // When age == 0, verified since the start of this build, assume listing hasn't changed
-                    result.setAuthoritative(cachedModuleVersionList.getAge().toMillis() == 0);
+                    boolean authoritative = cachedModuleVersionList.getAge().toMillis() == 0;
+                    result.listed(versionList);
+                    result.setAuthoritative(authoritative);
+                    listener.onDynamicVersionSelection(requested, expiry, versions);
                 }
             }
         }
@@ -379,7 +382,11 @@ public void listModuleVersions(ModuleDependencyMetadata dependency, BuildableMod
                         .map(original -> DefaultModuleVersionIdentifier.newId(moduleId, original))
                         .collect(Collectors.toSet());
                     moduleVersionsCache.cacheModuleVersionList(delegate, moduleId, versionList);
-                    listener.onDynamicVersionSelection(dependency.getSelector(), cachePolicy.versionListExpiry(moduleId, versions, Duration.ZERO));
+                    listener.onDynamicVersionSelection(
+                        dependency.getSelector(),
+                        cachePolicy.versionListExpiry(moduleId, versions, Duration.ZERO),
+                        versions
+                    );
                     break;
                 case Failed:
                     break;
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingValueDependencyResolutionListener.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingValueDependencyResolutionListener.java
index cd34201..c15f615 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingValueDependencyResolutionListener.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingValueDependencyResolutionListener.java
@@ -16,12 +16,15 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.artifacts.component.ModuleComponentSelector;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.Expiry;
 import org.gradle.internal.service.scopes.EventScope;
 import org.gradle.internal.service.scopes.Scopes;
 
+import java.util.Set;
+
 /**
  * Notified of the use of changing values during dependency resolution, so this can be noted in the configuration cache inputs
  */
@@ -29,7 +32,7 @@
 public interface ChangingValueDependencyResolutionListener {
     ChangingValueDependencyResolutionListener NO_OP = new ChangingValueDependencyResolutionListener() {
         @Override
-        public void onDynamicVersionSelection(ModuleComponentSelector requested, Expiry expiry) {
+        public void onDynamicVersionSelection(ModuleComponentSelector requested, Expiry expiry, Set<ModuleVersionIdentifier> versions) {
         }
 
         @Override
@@ -40,7 +43,7 @@ public void onChangingModuleResolve(ModuleComponentIdentifier moduleId, Expiry e
     /**
      * Called when a dynamic version is selected using the set of candidate versions queried from a repository.
      */
-    void onDynamicVersionSelection(ModuleComponentSelector requested, Expiry expiry);
+    void onDynamicVersionSelection(ModuleComponentSelector requested, Expiry expiry, Set<ModuleVersionIdentifier> versions);
 
     /**
      * Called when a changing artifact is resolved using the artifact state queried from a repository.
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/AbstractModuleVersionsCache.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/AbstractModuleVersionsCache.java
index 4f3e071..2f78c6e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/AbstractModuleVersionsCache.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/AbstractModuleVersionsCache.java
@@ -46,8 +46,11 @@ public CachedModuleVersionList getCachedModuleResolution(ModuleComponentReposito
         return entry == null ? null : versionList(entry);
     }
 
-    private CachedModuleVersionList versionList(ModuleVersionsCacheEntry moduleVersionsCacheEntry) {
-        return new DefaultCachedModuleVersionList(moduleVersionsCacheEntry, timeProvider);
+    private CachedModuleVersionList versionList(ModuleVersionsCacheEntry entry) {
+        return new DefaultCachedModuleVersionList(
+            entry.moduleVersionListing,
+            timeProvider.getCurrentTime() - entry.createTimestamp
+        );
     }
 
     private ModuleAtRepositoryKey createKey(ModuleComponentRepository repository, ModuleIdentifier moduleId) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/DefaultCachedModuleVersionList.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/DefaultCachedModuleVersionList.java
index 703803d..06356cc 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/DefaultCachedModuleVersionList.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/dynamicversions/DefaultCachedModuleVersionList.java
@@ -15,8 +15,6 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.modulecache.dynamicversions;
 
-import org.gradle.util.internal.BuildCommencedTimeProvider;
-
 import java.time.Duration;
 import java.util.Set;
 
@@ -24,9 +22,9 @@ class DefaultCachedModuleVersionList implements ModuleVersionsCache.CachedModule
     private final Set<String> moduleVersions;
     private final long ageMillis;
 
-    public DefaultCachedModuleVersionList(ModuleVersionsCacheEntry entry, BuildCommencedTimeProvider timeProvider) {
-        this.moduleVersions = entry.moduleVersionListing;
-        ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
+    public DefaultCachedModuleVersionList(Set<String> moduleVersions, long ageMillis) {
+        this.moduleVersions = moduleVersions;
+        this.ageMillis = ageMillis;
     }
 
     @Override
diff --git a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/resolve/AbstractProjectDependencyConflictResolutionIntegrationSpec.groovy b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/resolve/AbstractProjectDependencyConflictResolutionIntegrationSpec.groovy
index d67c778..e407143 100644
--- a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/resolve/AbstractProjectDependencyConflictResolutionIntegrationSpec.groovy
+++ b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/resolve/AbstractProjectDependencyConflictResolutionIntegrationSpec.groovy
@@ -183,7 +183,7 @@
         def projectId(String projectName) {
             def buildId = $buildId
             def projectPath = $projectPath
-            return project.services.get(${BuildStateRegistry.name}).getBuild(buildId).getIdentifierForProject(${Path.name}.path(projectPath))
+            return project.services.get(${BuildStateRegistry.name}).getBuild(buildId).projects.getProject(${Path.name}.path(projectPath)).componentIdentifier
         }
 """
     }
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy
index dbd3036..0d9c425 100644
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/HelpTaskIntegrationTest.groovy
@@ -114,7 +114,7 @@
 """
 
         and:
-        sub.file(".gradle").assertDoesNotExist()
+        sub.file(".gradle").assertIsDir()
         executer.gradleUserHomeDir.file(BuildScopeCacheDir.UNDEFINED_BUILD).assertDoesNotExist()
     }
 
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index 2e56521..6287e7c 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -13,7 +13,8 @@
 [Nicola Corti](https://github.com/cortinico),
 [Marcin ZajÄ…czkowski](https://github.com/szpak),
 [Alex Landau](https://github.com/AlexLandau),
-[Stefan Oehme](https://github.com/oehme)
+[Stefan Oehme](https://github.com/oehme),
+[yinghao niu](https://github.com/towith)
 
 ## Upgrade instructions
 
diff --git a/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.graffle b/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.graffle
index 94731a6..4392887 100644
--- a/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.graffle
+++ b/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.graffle
Binary files differ
diff --git a/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.png b/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.png
index a61ec12..68a1921 100644
--- a/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.png
+++ b/subprojects/docs/src/docs/userguide/img/dependency-management-shortcut-repositories.png
Binary files differ
diff --git a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/build.gradle b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/build.gradle
index e3d95de..0ee976c 100644
--- a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/build.gradle
+++ b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/build.gradle
@@ -13,8 +13,6 @@
     implementation 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.6.2'
     testImplementation platform("org.spockframework:spock-bom:2.0-groovy-3.0")
     testImplementation 'org.spockframework:spock-core'
-    testImplementation 'org.spockframework:spock-junit4'
-    testImplementation 'junit:junit:4.13.1'
 }
 
 tasks.named('test') {
diff --git a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/JavaConventionPluginTest.groovy b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/JavaConventionPluginTest.groovy
index a9826ef..58a71ec 100644
--- a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/JavaConventionPluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/JavaConventionPluginTest.groovy
@@ -14,8 +14,8 @@
 
     def "fails on checkstyle error"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'example')
-        testProjectDir.newFile('src/main/java/com/example/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/example').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/example/Foo.java') << """
             package com.example;
 
             import java.util.*;
@@ -37,8 +37,8 @@
 
     def "fails on checkstyle warning"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'example')
-        testProjectDir.newFile('src/main/java/com/example/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/example').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/example/Foo.java') << """
             package com.example;
 
             class Foo {
@@ -60,8 +60,8 @@
 
     def "fails on spotbugs error"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'example')
-        testProjectDir.newFile('src/main/java/com/example/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/example').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/example/Foo.java') << """
             package com.example;
 
             class Foo {
@@ -81,8 +81,8 @@
 
     def "warns on deprecated API usage"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'example')
-        testProjectDir.newFile('src/main/java/com/example/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/example').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/example/Foo.java') << """
             package com.example;
 
             public class Foo {
@@ -91,7 +91,7 @@
             }
         """
 
-        testProjectDir.newFile('src/main/java/com/example/Bar.java') << """
+        new File(testProjectDir, 'src/main/java/com/example/Bar.java') << """
             package com.example;
 
             public class Bar {
diff --git a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/LibraryPluginTest.groovy b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/LibraryPluginTest.groovy
index e4cfaf4..fda3ed3 100644
--- a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/LibraryPluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/LibraryPluginTest.groovy
@@ -45,8 +45,8 @@
             }
         """
 
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'example')
-        testProjectDir.newFile('src/main/java/com/example/Util.java') << """
+        new File(testProjectDir, 'src/main/java/com/example').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/example/Util.java') << """
             package com.example;
 
             public class Util {
@@ -61,7 +61,7 @@
         then:
         result.task(":jar").outcome == TaskOutcome.SUCCESS
         result.task(":publishLibraryPublicationToTestRepoRepository").outcome == TaskOutcome.SUCCESS
-        new File(testProjectDir.getRoot(), 'build/test-repo/com/example/my-library/0.1.0/my-library-0.1.0.jar').exists()
+        new File(testProjectDir, 'build/test-repo/com/example/my-library/0.1.0/my-library-0.1.0.jar').exists()
     }
 
     def "fails when no README exists"() {
@@ -74,7 +74,7 @@
 
     def "fails when README does not have API section"() {
         given:
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## Changelog
 - change 1
 - change 2
@@ -90,7 +90,7 @@
 
     def "fails when README does not have Changelog section"() {
         given:
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## API
 public API description
         """
@@ -104,7 +104,7 @@
     }
 
     private def readmeContainingMandatorySectionsExists() {
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## API
 public API description
 
diff --git a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/PluginTest.groovy b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/PluginTest.groovy
index c45b0b0..1150780 100644
--- a/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/PluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/multi-project-with-convention-plugins/groovy/buildSrc/src/test/groovy/com/example/PluginTest.groovy
@@ -1,24 +1,23 @@
 package com.example
 
 import org.gradle.testkit.runner.GradleRunner
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 abstract class PluginTest extends Specification {
-    @Rule
-    TemporaryFolder testProjectDir = new TemporaryFolder()
+    @TempDir
+    File testProjectDir
     File settingsFile
     File buildFile
 
     def setup() {
-        settingsFile = testProjectDir.newFile('settings.gradle') << "rootProject.name = 'test'"
-        buildFile = testProjectDir.newFile('build.gradle')
+        settingsFile = new File(testProjectDir, 'settings.gradle').tap { it << "rootProject.name = 'test'" }
+        buildFile = new File(testProjectDir, 'build.gradle')
     }
 
     def runTask(String task) {
         return GradleRunner.create()
-                .withProjectDir(testProjectDir.root)
+                .withProjectDir(testProjectDir)
                 .withArguments(task, '--stacktrace')
                 .withPluginClasspath()
                 .build()
@@ -26,7 +25,7 @@
 
     def runTaskWithFailure(String task) {
         return GradleRunner.create()
-                .withProjectDir(testProjectDir.root)
+                .withProjectDir(testProjectDir)
                 .withArguments(task, '--stacktrace')
                 .withPluginClasspath()
                 .buildAndFail()
diff --git a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/build.gradle b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/build.gradle
index 0415c5a..cc8bd10 100644
--- a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/build.gradle
+++ b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/build.gradle
@@ -35,8 +35,6 @@
     implementation 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.6.2'
     testImplementation platform("org.spockframework:spock-bom:2.0-groovy-3.0")
     testImplementation 'org.spockframework:spock-core'
-    testImplementation 'org.spockframework:spock-junit4'
-    testImplementation 'junit:junit:4.13.1'
 }
 
 tasks.named('test') {
diff --git a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/JavaConventionPluginTest.groovy b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/JavaConventionPluginTest.groovy
index 7fe443b..6c738b8 100644
--- a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/JavaConventionPluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/JavaConventionPluginTest.groovy
@@ -14,8 +14,8 @@
 
     def "fails on checkstyle error"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'myorg')
-        testProjectDir.newFile('src/main/java/com/myorg/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/myorg').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/myorg/Foo.java') << """
             package com.myorg;
 
             import java.util.*;
@@ -37,8 +37,8 @@
 
     def "fails on checkstyle warning"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'myorg')
-        testProjectDir.newFile('src/main/java/com/myorg/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/myorg').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/myorg/Foo.java') << """
             package com.myorg;
 
             class Foo {
@@ -60,8 +60,8 @@
 
     def "fails on spotbugs error"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'myorg')
-        testProjectDir.newFile('src/main/java/com/myorg/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/myorg').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/myorg/Foo.java') << """
             package com.myorg;
 
             class Foo {
@@ -81,8 +81,8 @@
 
     def "warns on deprecated API usage"() {
         given:
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'myorg')
-        testProjectDir.newFile('src/main/java/com/myorg/Foo.java') << """
+        new File(testProjectDir, 'src/main/java/com/myorg').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/myorg/Foo.java') << """
             package com.myorg;
 
             public class Foo {
@@ -91,7 +91,7 @@
             }
         """
 
-        testProjectDir.newFile('src/main/java/com/myorg/Bar.java') << """
+        new File(testProjectDir, 'src/main/java/com/myorg/Bar.java') << """
             package com.myorg;
 
             public class Bar {
diff --git a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/LibraryPluginTest.groovy b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/LibraryPluginTest.groovy
index fbdf5bc..a95eb5d 100644
--- a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/LibraryPluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/LibraryPluginTest.groovy
@@ -45,8 +45,8 @@
             }
         """
 
-        testProjectDir.newFolder('src', 'main', 'java', 'com', 'myorg')
-        testProjectDir.newFile('src/main/java/com/myorg/Util.java') << """
+        new File(testProjectDir, 'src/main/java/com/myorg').mkdirs()
+        new File(testProjectDir, 'src/main/java/com/myorg/Util.java') << """
             package com.myorg;
 
             public class Util {
@@ -61,7 +61,7 @@
         then:
         result.task(":jar").outcome == TaskOutcome.SUCCESS
         result.task(":publishLibraryPublicationToTestRepoRepository").outcome == TaskOutcome.SUCCESS
-        new File(testProjectDir.getRoot(), 'build/test-repo/com/myorg/my-library/0.1.0/my-library-0.1.0.jar').exists()
+        new File(testProjectDir, 'build/test-repo/com/myorg/my-library/0.1.0/my-library-0.1.0.jar').exists()
     }
 
     def "fails when no README exists"() {
@@ -74,7 +74,7 @@
 
     def "fails when README does not have API section"() {
         given:
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## Changelog
 - change 1
 - change 2
@@ -90,7 +90,7 @@
 
     def "fails when README does not have Changelog section"() {
         given:
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## API
 public API description
         """
@@ -104,7 +104,7 @@
     }
 
     private def readmeContainingMandatorySectionsExists() {
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## API
 public API description
 
diff --git a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/PluginTest.groovy b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/PluginTest.groovy
index 5a1d589..45984da 100644
--- a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/PluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/PluginTest.groovy
@@ -1,24 +1,23 @@
 package com.myorg
 
 import org.gradle.testkit.runner.GradleRunner
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 abstract class PluginTest extends Specification {
-    @Rule
-    TemporaryFolder testProjectDir = new TemporaryFolder()
+    @TempDir
+    File testProjectDir
     File settingsFile
     File buildFile
 
     def setup() {
-        settingsFile = testProjectDir.newFile('settings.gradle') << "rootProject.name = 'test'"
-        buildFile = testProjectDir.newFile('build.gradle')
+        settingsFile = new File(testProjectDir, 'settings.gradle').tap { it << "rootProject.name = 'test'" }
+        buildFile = new File(testProjectDir, 'build.gradle')
     }
 
     def runTask(String task) {
         return GradleRunner.create()
-                .withProjectDir(testProjectDir.root)
+                .withProjectDir(testProjectDir)
                 .withArguments(task, '--stacktrace')
                 .withPluginClasspath()
                 .build()
@@ -26,7 +25,7 @@
 
     def runTaskWithFailure(String task) {
         return GradleRunner.create()
-                .withProjectDir(testProjectDir.root)
+                .withProjectDir(testProjectDir)
                 .withArguments(task, '--stacktrace')
                 .withPluginClasspath()
                 .buildAndFail()
diff --git a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/ServicePluginTest.groovy b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/ServicePluginTest.groovy
index 6270470..976d2b7 100644
--- a/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/ServicePluginTest.groovy
+++ b/subprojects/docs/src/samples/build-organization/publishing-convention-plugins/groovy/convention-plugins/src/test/groovy/com/myorg/ServicePluginTest.groovy
@@ -15,7 +15,7 @@
 
     def "integrationTest and readmeCheck tasks run with check task"() {
         given:
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 ## Service API
 
         """
@@ -37,8 +37,8 @@
             }
         """
 
-        testProjectDir.newFolder('src', 'integrationTest', 'java', 'com', 'myorg')
-        testProjectDir.newFile('src/integrationTest/java/com/myorg/SomeIntegrationTest.java') << """
+        new File(testProjectDir, 'src/integrationTest/java/com/myorg').mkdirs()
+        new File(testProjectDir, 'src/integrationTest/java/com/myorg/SomeIntegrationTest.java') << """
             package com.myorg;
 
             import org.junit.Test;
@@ -67,7 +67,7 @@
 
     def "fails when README does not have service API section"() {
         given:
-        testProjectDir.newFile('README.md') << """
+        new File(testProjectDir, 'README.md') << """
 asdfadfsasf
         """
 
diff --git a/subprojects/docs/src/snippets/configurationCache/testKit/groovy/build.gradle b/subprojects/docs/src/snippets/configurationCache/testKit/groovy/build.gradle
index 2168058..5ca18cc 100644
--- a/subprojects/docs/src/snippets/configurationCache/testKit/groovy/build.gradle
+++ b/subprojects/docs/src/snippets/configurationCache/testKit/groovy/build.gradle
@@ -6,8 +6,6 @@
 dependencies {
     testImplementation platform("org.spockframework:spock-bom:2.0-groovy-3.0")
     testImplementation 'org.spockframework:spock-core'
-    testImplementation 'org.spockframework:spock-junit4'
-    testImplementation 'junit:junit:4.13.1'
 }
 
 tasks.named("test") {
diff --git a/subprojects/docs/src/snippets/configurationCache/testKit/groovy/src/test/groovy/org/example/BuildLogicFunctionalTest.groovy b/subprojects/docs/src/snippets/configurationCache/testKit/groovy/src/test/groovy/org/example/BuildLogicFunctionalTest.groovy
index a9619a9..45c4ce4 100644
--- a/subprojects/docs/src/snippets/configurationCache/testKit/groovy/src/test/groovy/org/example/BuildLogicFunctionalTest.groovy
+++ b/subprojects/docs/src/snippets/configurationCache/testKit/groovy/src/test/groovy/org/example/BuildLogicFunctionalTest.groovy
@@ -1,20 +1,19 @@
 package org.example
 
 import org.gradle.testkit.runner.GradleRunner
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import static org.gradle.testkit.runner.TaskOutcome.*
 
 class BuildLogicFunctionalTest extends Specification {
 
-    @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+    @TempDir File testProjectDir
     File buildFile
 
     def setup() {
-        testProjectDir.newFile('settings.gradle') << ""
-        buildFile = testProjectDir.newFile('build.gradle')
+        new File(testProjectDir, 'settings.gradle') << ""
+        buildFile = new File(testProjectDir, 'build.gradle')
     }
 
     // tag::functional-test-configuration-cache[]
@@ -44,7 +43,7 @@
 
     def runner() {
         return GradleRunner.create()
-            .withProjectDir(testProjectDir.root)
+            .withProjectDir(testProjectDir)
             .withPluginClasspath()
     }
 }
diff --git a/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/groovy/build.gradle b/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/groovy/build.gradle
index 338a0f6..ef912e3 100644
--- a/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/groovy/build.gradle
+++ b/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/groovy/build.gradle
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-// tag::use-plugin[]
+// tag::use_plugin[]
 plugins {
     id 'java-library'
     id 'checkstyle'
     // Use the plugin `jmh` as declared in the `libs` version catalog
     alias(libs.plugins.jmh)
 }
-// end::use-plugin[]
+// end::use_plugin[]
 
 // tag::simple_dependency_use[]
 dependencies {
diff --git a/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/kotlin/build.gradle.kts b/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/kotlin/build.gradle.kts
index f15747c..8be9f44 100644
--- a/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/kotlin/build.gradle.kts
+++ b/subprojects/docs/src/snippets/dependencyManagement/catalogs-settings/kotlin/build.gradle.kts
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-// tag::use-plugin[]
+// tag::use_plugin[]
 plugins {
     `java-library`
     checkstyle
     alias(libs.plugins.jmh)
 }
-// end::use-plugin[]
+// end::use_plugin[]
 
 // tag::simple_dependency_use[]
 dependencies {
diff --git a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/build.gradle b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/build.gradle
index ce1e34b..1658c66 100644
--- a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/build.gradle
+++ b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/build.gradle
@@ -50,18 +50,12 @@
 dependencies {
     testImplementation platform("org.spockframework:spock-bom:2.0-groovy-3.0")
     testImplementation 'org.spockframework:spock-core'
-    testImplementation 'org.spockframework:spock-junit4'
-    testImplementation 'junit:junit:4.13.1'
 
     integrationTestImplementation platform("org.spockframework:spock-bom:2.0-groovy-3.0")
     integrationTestImplementation 'org.spockframework:spock-core'
-    integrationTestImplementation 'org.spockframework:spock-junit4'
-    integrationTestImplementation 'junit:junit:4.13.1'
 
     functionalTestImplementation platform("org.spockframework:spock-bom:2.0-groovy-3.0")
     functionalTestImplementation 'org.spockframework:spock-core'
-    functionalTestImplementation 'org.spockframework:spock-junit4'
-    functionalTestImplementation 'junit:junit:4.13.1'
 }
 
 tasks.withType(Test).configureEach {
diff --git a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy
index 8d0eea6..db93d1e 100644
--- a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy
+++ b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/groovy/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy
@@ -1,18 +1,17 @@
 package org.myorg
 
 import org.gradle.testkit.runner.GradleRunner
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
 
 class UrlVerifierPluginFunctionalTest extends Specification {
-    @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+    @TempDir File testProjectDir
     File buildFile
 
     def setup() {
-        buildFile = testProjectDir.newFile('build.gradle')
+        buildFile = new File(testProjectDir, 'build.gradle')
         buildFile << """
             plugins {
                 id 'org.myorg.url-verifier'
@@ -29,7 +28,7 @@
 
         when:
         def result = GradleRunner.create()
-            .withProjectDir(testProjectDir.root)
+            .withProjectDir(testProjectDir)
             .withArguments('verifyUrl')
             .withPluginClasspath()
             .build()
diff --git a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/build.gradle.kts b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/build.gradle.kts
index 41ebc0a..ec0bae9 100644
--- a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/build.gradle.kts
+++ b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/build.gradle.kts
@@ -50,21 +50,14 @@
 dependencies {
     testImplementation(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
     testImplementation("org.spockframework:spock-core")
-    testImplementation("org.spockframework:spock-junit4")
-    testImplementation("junit:junit:4.13.1")
 
     "integrationTestImplementation"(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
     "integrationTestImplementation"("org.spockframework:spock-core")
-    "integrationTestImplementation"("org.spockframework:spock-junit4")
-    "integrationTestImplementation"("junit:junit:4.13.1")
 
     "functionalTestImplementation"(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
     "functionalTestImplementation"("org.spockframework:spock-core")
-    "functionalTestImplementation"("org.spockframework:spock-junit4")
-    "functionalTestImplementation"("junit:junit:4.13.1")
 }
 
-
 tasks.withType<Test>().configureEach {
     // Using JUnitPlatform for running tests
     useJUnitPlatform()
diff --git a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy
index 8d0eea6..db93d1e 100644
--- a/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy
+++ b/subprojects/docs/src/snippets/developingPlugins/testingPlugins/kotlin/url-verifier-plugin/src/functionalTest/groovy/org/myorg/UrlVerifierPluginFunctionalTest.groovy
@@ -1,18 +1,17 @@
 package org.myorg
 
 import org.gradle.testkit.runner.GradleRunner
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
 
 class UrlVerifierPluginFunctionalTest extends Specification {
-    @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+    @TempDir File testProjectDir
     File buildFile
 
     def setup() {
-        buildFile = testProjectDir.newFile('build.gradle')
+        buildFile = new File(testProjectDir, 'build.gradle')
         buildFile << """
             plugins {
                 id 'org.myorg.url-verifier'
@@ -29,7 +28,7 @@
 
         when:
         def result = GradleRunner.create()
-            .withProjectDir(testProjectDir.root)
+            .withProjectDir(testProjectDir)
             .withArguments('verifyUrl')
             .withPluginClasspath()
             .build()
diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/DefaultExecutionHistoryStore.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/DefaultExecutionHistoryStore.java
index 0cfc07c..3ed63e8 100644
--- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/DefaultExecutionHistoryStore.java
+++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/DefaultExecutionHistoryStore.java
@@ -95,7 +95,7 @@ public void remove(String key) {
     private static ImmutableSortedMap<String, FileCollectionFingerprint> prepareForSerialization(ImmutableSortedMap<String, CurrentFileCollectionFingerprint> fingerprints) {
         return copyOfSorted(transformValues(
             fingerprints,
-            value -> new SerializableFileCollectionFingerprint(value.getFingerprints(), value.getRootHashes(), value.getStrategyConfigurationHash())
+            value -> value.archive(SerializableFileCollectionFingerprint::new)
         ));
     }
 }
diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/FileCollectionFingerprintSerializer.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/FileCollectionFingerprintSerializer.java
index fe135d8..99c2b4f 100644
--- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/FileCollectionFingerprintSerializer.java
+++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/FileCollectionFingerprintSerializer.java
@@ -72,7 +72,7 @@ public void write(Encoder encoder, FileCollectionFingerprint value) throws Excep
         fingerprintMapSerializer.write(encoder, value.getFingerprints());
         if (!value.getFingerprints().isEmpty()) {
             writeRootHashes(encoder, value.getRootHashes());
-            hashCodeSerializer.write(encoder, value.getStrategyConfigurationHash());
+            hashCodeSerializer.write(encoder, ((SerializableFileCollectionFingerprint) value).getStrategyConfigurationHash());
         }
     }
 
diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/SerializableFileCollectionFingerprint.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/SerializableFileCollectionFingerprint.java
index fbfb1a6..6b3dbb5 100644
--- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/SerializableFileCollectionFingerprint.java
+++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/impl/SerializableFileCollectionFingerprint.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableMultimap;
 import org.gradle.internal.fingerprint.FileCollectionFingerprint;
 import org.gradle.internal.fingerprint.FileSystemLocationFingerprint;
+import org.gradle.internal.fingerprint.FingerprintingStrategy;
 import org.gradle.internal.hash.HashCode;
 
 import java.util.Map;
@@ -46,6 +47,10 @@ public ImmutableMultimap<String, HashCode> getRootHashes() {
     }
 
     @Override
+    public boolean wasCreatedWithStrategy(FingerprintingStrategy strategy) {
+        return strategy.getConfigurationHash().equals(strategyConfigurationHash);
+    }
+
     public HashCode getStrategyConfigurationHash() {
         return strategyConfigurationHash;
     }
diff --git a/subprojects/execution/src/testFixtures/groovy/org/gradle/internal/execution/TestExecutionHistoryStore.java b/subprojects/execution/src/testFixtures/groovy/org/gradle/internal/execution/TestExecutionHistoryStore.java
index c22edd9..0a4cde6 100644
--- a/subprojects/execution/src/testFixtures/groovy/org/gradle/internal/execution/TestExecutionHistoryStore.java
+++ b/subprojects/execution/src/testFixtures/groovy/org/gradle/internal/execution/TestExecutionHistoryStore.java
@@ -75,7 +75,7 @@ public void remove(String key) {
     private static ImmutableSortedMap<String, FileCollectionFingerprint> prepareForSerialization(ImmutableSortedMap<String, CurrentFileCollectionFingerprint> fingerprints) {
         return copyOfSorted(transformValues(
             fingerprints,
-            value -> new SerializableFileCollectionFingerprint(value.getFingerprints(), value.getRootHashes(), value.getStrategyConfigurationHash())
+            value -> value.archive(SerializableFileCollectionFingerprint::new)
         ));
     }
 
diff --git a/subprojects/file-watching/src/integTest/groovy/org/gradle/internal/watch/WatchedDirectoriesFileSystemWatchingIntegrationTest.groovy b/subprojects/file-watching/src/integTest/groovy/org/gradle/internal/watch/WatchedDirectoriesFileSystemWatchingIntegrationTest.groovy
index 828ef7f..a451659 100644
--- a/subprojects/file-watching/src/integTest/groovy/org/gradle/internal/watch/WatchedDirectoriesFileSystemWatchingIntegrationTest.groovy
+++ b/subprojects/file-watching/src/integTest/groovy/org/gradle/internal/watch/WatchedDirectoriesFileSystemWatchingIntegrationTest.groovy
@@ -67,7 +67,11 @@
         withWatchFs().run "hello", "--info"
         then:
         outputContains "Hello from original task!"
-        assertWatchableHierarchies([ImmutableSet.of(testDirectory), ImmutableSet.of(testDirectory, file("buildSrc"))])
+
+        // roots discovered during build start up are not registered in a stable order
+        def watchable = determineWatchableHierarchies(output)
+        assert watchable == [ImmutableSet.of(testDirectory), ImmutableSet.of(testDirectory, file("buildSrc"))] ||
+            watchable == [ImmutableSet.of(file("buildSrc")), ImmutableSet.of(testDirectory, file("buildSrc"))]
     }
 
     def "works with composite build"() {
diff --git a/subprojects/ide/src/crossVersionTest/groovy/org/gradle/plugins/ide/tooling/m72/CompositeBuildCrossVersionSpec.groovy b/subprojects/ide/src/crossVersionTest/groovy/org/gradle/plugins/ide/tooling/m72/CompositeBuildCrossVersionSpec.groovy
index 30e9f53..daecf42 100644
--- a/subprojects/ide/src/crossVersionTest/groovy/org/gradle/plugins/ide/tooling/m72/CompositeBuildCrossVersionSpec.groovy
+++ b/subprojects/ide/src/crossVersionTest/groovy/org/gradle/plugins/ide/tooling/m72/CompositeBuildCrossVersionSpec.groovy
@@ -67,7 +67,8 @@
         nestedBuildSrc.editableBuilds.empty
     }
 
-    @TargetGradleVersion(">4.9 <7.1") // older versions do not like nested included builds or nested buildSrc builds
+    @TargetGradleVersion(">4.9 <7.1")
+    // versions 4.9 and older do not like nested included builds or nested buildSrc builds
     def "older versions do not include buildSrc builds in model"() {
         buildsWithBuildSrc()
 
@@ -125,7 +126,35 @@
         includedBuildSrc.editableBuilds.empty
     }
 
-    @TargetGradleVersion(">=6.8") // older versions do not handle cycles
+    def "can query model for multi-project buildSrc builds"() {
+        multiProjectBuildSrc(projectDir)
+        settingsFile << """
+            includeBuild("child")
+        """
+
+        def childBuild = file("child")
+        multiProjectBuildSrc(childBuild)
+
+        given:
+        def model = withConnection {
+            it.getModel(GradleBuild)
+        }
+
+        expect:
+        model.includedBuilds.size() == 1
+        model.editableBuilds.size() == 3
+
+        def buildSrc = model.editableBuilds[0]
+        buildSrc.buildIdentifier.rootDir == file("buildSrc")
+        buildSrc.projects.size() == 3
+
+        def nestedBuildSrc = model.editableBuilds[2]
+        nestedBuildSrc.buildIdentifier.rootDir == file("child/buildSrc")
+        nestedBuildSrc.projects.size() == 3
+    }
+
+    @TargetGradleVersion(">=6.8")
+    // versions older than 6.8 do not handle cycles
     def "can query model when there are cycles in the included build graph"() {
         settingsFile << """
             includeBuild("child1")
@@ -163,7 +192,8 @@
         included2.includedBuilds[0].is(included1)
     }
 
-    @TargetGradleVersion(">=6.8") // older versions do not allow root to be included by child
+    @TargetGradleVersion(">=6.8")
+    // versions older than 6.8 do not allow root to be included by child
     def "can query model when included build includes root build"() {
         settingsFile << """
             includeBuild("child")
@@ -192,6 +222,26 @@
         included.includedBuilds[0].is(model)
     }
 
+    def "can query model for nested buildSrc builds"() {
+        def buildSrcDir = buildSrc(projectDir)
+        def nestedBuildSrcDir = buildSrc(buildSrcDir)
+
+        given:
+        def model = withConnection {
+            it.getModel(GradleBuild)
+        }
+
+        expect:
+        model.includedBuilds.empty
+        model.editableBuilds.size() == 2
+
+        def buildSrc = model.editableBuilds[0]
+        buildSrc.buildIdentifier.rootDir == buildSrcDir
+
+        def nestedBuildSrc = model.editableBuilds[1]
+        nestedBuildSrc.buildIdentifier.rootDir == nestedBuildSrcDir
+    }
+
     def "build action can fetch model for buildSrc project"() {
         buildsWithBuildSrc()
 
@@ -234,7 +284,16 @@
         buildSrc(nestedBuild)
     }
 
-    void buildSrc(TestFile dir) {
-        dir.file("buildSrc/src/main/java/Thing.java") << "class Thing { }"
+    TestFile buildSrc(TestFile dir) {
+        def buildSrc = dir.file("buildSrc")
+        buildSrc.file("src/main/java/Thing.java") << "class Thing { }"
+        return buildSrc
+    }
+
+    void multiProjectBuildSrc(TestFile dir) {
+        dir.file("buildSrc/settings.gradle") << """
+            include("a")
+            include("b")
+        """
     }
 }
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/idea/IdeaPlugin.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/idea/IdeaPlugin.java
index 472090c..0d61a43 100644
--- a/subprojects/ide/src/main/java/org/gradle/plugins/ide/idea/IdeaPlugin.java
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/idea/IdeaPlugin.java
@@ -369,42 +369,50 @@ public FileCollection call() {
 
         // Convention
         ConventionMapping convention = ((IConventionAware) ideaModel.getModule()).getConventionMapping();
+        Set<File> sourceDirs = Sets.newLinkedHashSet();
         convention.map("sourceDirs", new Callable<Set<File>>() {
             @Override
             public Set<File> call() {
                 SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
-                return sourceSets.getByName("main").getAllJava().getSrcDirs();
+                sourceDirs.addAll(sourceSets.getByName("main").getAllJava().getSrcDirs());
+                return sourceDirs;
             }
         });
+        Set<File> testSourceDirs = Sets.newLinkedHashSet();
         convention.map("testSourceDirs", new Callable<Set<File>>() {
             @Override
             public Set<File> call() {
                 SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
-                return sourceSets.getByName("test").getAllJava().getSrcDirs();
+                testSourceDirs.addAll(sourceSets.getByName("test").getAllJava().getSrcDirs());
+                return testSourceDirs;
             }
         });
+        Set<File> resourceDirs = Sets.newLinkedHashSet();
         convention.map("resourceDirs", new Callable<Set<File>>() {
             @Override
-            public Set<File> call() throws Exception {
+            public Set<File> call() {
                 SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
-                return sourceSets.getByName("main").getResources().getSrcDirs();
+                resourceDirs.addAll(sourceSets.getByName("main").getResources().getSrcDirs());
+                return resourceDirs;
             }
         });
+        Set<File> testResourceDirs = Sets.newLinkedHashSet();
         convention.map("testResourceDirs", new Callable<Set<File>>() {
             @Override
-            public Set<File> call() throws Exception {
+            public Set<File> call() {
                 SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
-                return sourceSets.getByName("test").getResources().getSrcDirs();
+                testResourceDirs.addAll(sourceSets.getByName("test").getResources().getSrcDirs());
+                return testResourceDirs;
             }
         });
+        Map<String, FileCollection> singleEntryLibraries = new LinkedHashMap<String, FileCollection>(2);
         convention.map("singleEntryLibraries", new Callable<Map<String, FileCollection>>() {
             @Override
             public Map<String, FileCollection> call() {
                 SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
-                LinkedHashMap<String, FileCollection> map = new LinkedHashMap<String, FileCollection>(2);
-                map.put("RUNTIME", sourceSets.getByName("main").getOutput().getDirs());
-                map.put("TEST", sourceSets.getByName("test").getOutput().getDirs());
-                return map;
+                singleEntryLibraries.putIfAbsent("RUNTIME", sourceSets.getByName("main").getOutput().getDirs());
+                singleEntryLibraries.putIfAbsent("TEST", sourceSets.getByName("test").getOutput().getDirs());
+                return singleEntryLibraries;
             }
 
         });
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java
index 32a5543..f71ccb2 100644
--- a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilder.java
@@ -28,13 +28,14 @@
 import org.gradle.plugins.ide.internal.tooling.model.DefaultGradleBuild;
 import org.gradle.tooling.internal.gradle.DefaultProjectIdentifier;
 import org.gradle.tooling.provider.model.ToolingModelBuilder;
+import org.gradle.tooling.provider.model.internal.BuildScopeModelBuilder;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-public class GradleBuildBuilder implements ToolingModelBuilder {
+public class GradleBuildBuilder implements ToolingModelBuilder, BuildScopeModelBuilder {
     private final BuildStateRegistry buildStateRegistry;
 
     public GradleBuildBuilder(BuildStateRegistry buildStateRegistry) {
@@ -49,7 +50,13 @@ public boolean canBuild(String modelName) {
     @Override
     public DefaultGradleBuild buildAll(String modelName, Project target) {
         BuildState targetBuild = ((GradleInternal) target.getGradle()).getOwner();
-        return convert(targetBuild, new LinkedHashMap<>());
+        return create(targetBuild);
+    }
+
+    @Override
+    public DefaultGradleBuild create(BuildState target) {
+        target.ensureProjectsLoaded();
+        return convert(target, new LinkedHashMap<>());
     }
 
     private DefaultGradleBuild convert(BuildState targetBuild, Map<BuildState, DefaultGradleBuild> all) {
@@ -60,6 +67,9 @@ private DefaultGradleBuild convert(BuildState targetBuild, Map<BuildState, Defau
         model = new DefaultGradleBuild();
         all.put(targetBuild, model);
 
+        // Make sure the project tree has been loaded and can be queried (but not necessarily configured)
+        targetBuild.ensureProjectsLoaded();
+
         GradleInternal gradle = targetBuild.getMutableModel();
         addProjects(targetBuild, model);
         addIncludedBuilds(gradle, model, all);
@@ -83,7 +93,6 @@ private void addIncludedBuilds(GradleInternal gradle, DefaultGradleBuild model,
             BuildState target = reference.getTarget();
             if (target instanceof IncludedBuildState) {
                 IncludedBuildState includedBuildState = (IncludedBuildState) target;
-                includedBuildState.getConfiguredBuild();
                 DefaultGradleBuild convertedIncludedBuild = convert(includedBuildState, all);
                 model.addIncludedBuild(convertedIncludedBuild);
             } else if (target instanceof RootBuildState) {
@@ -96,27 +105,27 @@ private void addIncludedBuilds(GradleInternal gradle, DefaultGradleBuild model,
     }
 
     private void addProjects(BuildState target, DefaultGradleBuild model) {
-        Map<Project, BasicGradleProject> convertedProjects = new LinkedHashMap<>();
+        Map<ProjectState, BasicGradleProject> convertedProjects = new LinkedHashMap<>();
 
-        Project rootProject = target.getMutableModel().getRootProject();
-        BasicGradleProject convertedRootProject = convert(rootProject, convertedProjects);
+        ProjectState rootProject = target.getProjects().getRootProject();
+        BasicGradleProject convertedRootProject = convert(target, rootProject, convertedProjects);
         model.setRootProject(convertedRootProject);
 
         for (ProjectState project : target.getProjects().getAllProjects()) {
-            model.addProject(convertedProjects.get(project.getMutableModel()));
+            model.addProject(convertedProjects.get(project));
         }
     }
 
-    private BasicGradleProject convert(Project project, Map<Project, BasicGradleProject> convertedProjects) {
-        DefaultProjectIdentifier id = new DefaultProjectIdentifier(project.getRootDir(), project.getPath());
+    private BasicGradleProject convert(BuildState owner, ProjectState project, Map<ProjectState, BasicGradleProject> convertedProjects) {
+        DefaultProjectIdentifier id = new DefaultProjectIdentifier(owner.getBuildRootDir(), project.getProjectPath().getPath());
         BasicGradleProject converted = new BasicGradleProject().setName(project.getName()).setProjectIdentifier(id);
         converted.setProjectDirectory(project.getProjectDir());
         if (project.getParent() != null) {
             converted.setParent(convertedProjects.get(project.getParent()));
         }
         convertedProjects.put(project, converted);
-        for (Project child : project.getChildProjects().values()) {
-            converted.addChild(convert(child, convertedProjects));
+        for (ProjectState child : project.getChildProjects()) {
+            converted.addChild(convert(owner, child, convertedProjects));
         }
         return converted;
     }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreatorTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreatorTest.groovy
index 97f6212..ffcbdb0 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreatorTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreatorTest.groovy
@@ -21,14 +21,13 @@
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.util.PatternSet
 import org.gradle.plugins.ide.eclipse.model.SourceFolder
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class SourceFoldersCreatorTest extends Specification {
 
-    @Rule
-    TemporaryFolder tempFolder;
+    @TempDir
+    File tempFolder;
 
     SourceSet sourceSet;
     SourceDirectorySet java
@@ -49,7 +48,7 @@
         _ * sourceSet.allSource >> allSource
         _ * sourceSet.allJava >> java
         _ * sourceSet.resources >> resources
-        projectRootFolder = tempFolder.newFolder("project-root")
+        projectRootFolder = new File(tempFolder, "project-root").tap { mkdirs() }
         defaultOutputFolder = new File(projectRootFolder, 'bin/default')
     }
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
index e121d8e..ceca64a 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
@@ -27,6 +27,7 @@
 import org.gradle.plugins.ide.idea.model.IdeaModel
 import org.gradle.test.fixtures.AbstractProjectBuilderSpec
 import org.gradle.util.TestUtil
+import spock.lang.Issue
 
 import static org.gradle.api.reflect.TypeOf.typeOf
 
@@ -213,6 +214,7 @@
         publicTypeOfExtension("idea") == typeOf(IdeaModel)
     }
 
+    @Issue('https://github.com/gradle/gradle/issues/8749')
     def "can add to file set properties"() {
         given:
         applyPluginToProjects()
@@ -228,6 +230,23 @@
         property << [{ it.sourceDirs }, { it.testSourceDirs }, { it.resourceDirs }, { it.testResourceDirs }, { it.excludeDirs }]
     }
 
+    @Issue('https://github.com/gradle/gradle/issues/8749')
+    def "can add to file set properties when java plugin is applied too"() {
+        given:
+        project.apply plugin: JavaPlugin
+        applyPluginToProjects()
+        def source = new File("foo")
+
+        when:
+        property(project.idea.module).add(source)
+
+        then:
+        property(project.idea.module).contains(source)
+
+        where:
+        property << [{ it.sourceDirs }, { it.testSourceDirs }, { it.resourceDirs }, { it.testResourceDirs }, { it.excludeDirs }]
+    }
+
     private TypeOf<?> publicTypeOfExtension(String named) {
         project.extensions.extensionsSchema.find { it.name == named }.publicType
     }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy
index be84cfe..5e2bd74 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/tooling/GradleBuildBuilderTest.groovy
@@ -16,9 +16,8 @@
 
 package org.gradle.plugins.ide.internal.tooling
 
-
 import org.gradle.api.internal.GradleInternal
-import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.ProjectState
 import org.gradle.internal.build.BuildProjectRegistry
 import org.gradle.internal.build.BuildState
 import org.gradle.internal.build.BuildStateRegistry
@@ -27,6 +26,7 @@
 import org.gradle.test.fixtures.file.CleanupTestDirectory
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.Path
 import org.gradle.util.TestUtil
 import org.junit.ClassRule
 import spock.lang.Shared
@@ -71,9 +71,14 @@
     def "builds model for included builds"() {
         def buildRegistry = Mock(BuildStateRegistry)
 
-        def rootProject = Mock(ProjectInternal)
-        def project1 = Mock(ProjectInternal)
-        def project2 = Mock(ProjectInternal)
+        def rootProject = Mock(ProjectState)
+        rootProject.projectPath >> Path.ROOT
+
+        def includeProject1 = Mock(ProjectState)
+        includeProject1.projectPath >> Path.ROOT
+
+        def includedProject2 = Mock(ProjectState)
+        includedProject2.projectPath >> Path.ROOT
 
         def rootDir = temporaryFolder.createDir("root")
         def dir1 = temporaryFolder.createDir("dir1")
@@ -94,51 +99,42 @@
         def includedBuild1 = Mock(IncludedBuildInternal)
         def includedBuild2 = Mock(IncludedBuildInternal)
 
-        rootProject.gradle >> rootBuild
-        rootProject.rootDir >> rootDir
-        rootProject.childProjects >> [:]
-        rootProject.allprojects >> [rootProject]
+        rootProject.childProjects >> [].toSet()
+        includeProject1.childProjects >> [].toSet()
+        includedProject2.childProjects >> [].toSet()
 
-        project1.rootDir >> dir1
-        project1.childProjects >> [:]
-        project1.allprojects >> [project1]
-
-        project2.rootDir >> dir2
-        project2.childProjects >> [:]
-        project2.allprojects >> [project2]
-
-        rootBuild.owner >> rootBuildState
-        rootBuild.rootProject >> rootProject
         rootBuild.includedBuilds() >> [includedBuild1]
 
         rootBuildState.mutableModel >> rootBuild
+        rootBuildState.buildRootDir >> rootDir
         rootBuildState.projects >> rootProjects
 
         includedBuild1.target >> includedBuildState1
         includedBuild2.target >> includedBuildState2
 
         includedBuildState1.projects >> projects1
-        includedBuildState1.configuredBuild >> build1
+        includedBuildState1.buildRootDir >> dir1
         includedBuildState1.importableBuild >> true
 
-        rootProjects.allProjects >> [].toSet()
-        projects1.allProjects >> [].toSet()
-        projects2.allProjects >> [].toSet()
+        rootProjects.allProjects >> [rootProject].toSet()
+        rootProjects.rootProject >> rootProject
 
-        build1.owner >> includedBuildState1
+        projects1.allProjects >> [includeProject1].toSet()
+        projects1.rootProject >> includeProject1
+
+        projects2.allProjects >> [includeProject1].toSet()
+        projects2.rootProject >> includedProject2
+
         build1.includedBuilds() >> [includedBuild2]
-        build1.rootProject >> project1
         build1.parent >> rootBuild
 
         includedBuildState1.mutableModel >> build1
 
         includedBuildState2.projects >> projects2
-        includedBuildState2.configuredBuild >> build2
+        includedBuildState2.buildRootDir >> dir2
         includedBuildState2.importableBuild >> true
 
-        build2.owner >> includedBuildState2
         build2.includedBuilds() >> []
-        build2.rootProject >> project2
         build2.parent >> rootBuild
 
         includedBuildState2.mutableModel >> build2
@@ -152,7 +148,7 @@
         def builder = new GradleBuildBuilder(buildRegistry)
 
         expect:
-        def model = builder.buildAll("org.gradle.tooling.model.gradle.GradleBuild", rootProject)
+        def model = builder.create(rootBuildState)
         model.includedBuilds.size() == 1
 
         def model1 = model.includedBuilds[0]
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RepoScriptBlockUtil.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RepoScriptBlockUtil.groovy
index db748bd..1b7a829 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RepoScriptBlockUtil.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RepoScriptBlockUtil.groovy
@@ -58,7 +58,7 @@
         RESTLET('https://maven.restlet.com', System.getProperty('org.gradle.integtest.mirrors.restlet'), 'maven'),
         GRADLE('https://repo.gradle.org/gradle/repo', System.getProperty('org.gradle.integtest.mirrors.gradle'), 'maven'),
         JBOSS('https://repository.jboss.org/maven2/', System.getProperty('org.gradle.integtest.mirrors.jboss'), 'maven'),
-        GRADLE_PLUGIN("https://plugins.gradle.org/m2", System.getProperty('org.gradle.integtest.mirrors.gradleplugins'), 'maven'),
+        GRADLE_PLUGIN("https://plugins.gradle.org/m2", System.getProperty('org.gradle.integtest.mirrors.gradle-prod-plugins'), 'maven'),
         GRADLE_LIB_RELEASES('https://repo.gradle.org/gradle/libs-releases', System.getProperty('org.gradle.integtest.mirrors.gradle'), 'maven'),
         GRADLE_LIB_MILESTONES('https://repo.gradle.org/gradle/libs-milestones', System.getProperty('org.gradle.integtest.mirrors.gradle'), 'maven'),
         GRADLE_LIB_SNAPSHOTS('https://repo.gradle.org/gradle/libs-snapshots', System.getProperty('org.gradle.integtest.mirrors.gradle'), 'maven'),
diff --git a/subprojects/internal-performance-testing/src/integTest/groovy/org/gradle/performance/fixture/MavenDownloaderTest.groovy b/subprojects/internal-performance-testing/src/integTest/groovy/org/gradle/performance/fixture/MavenDownloaderTest.groovy
index c8b116a..3034f07 100644
--- a/subprojects/internal-performance-testing/src/integTest/groovy/org/gradle/performance/fixture/MavenDownloaderTest.groovy
+++ b/subprojects/internal-performance-testing/src/integTest/groovy/org/gradle/performance/fixture/MavenDownloaderTest.groovy
@@ -23,22 +23,23 @@
 import org.gradle.util.TestPrecondition
 import org.junit.Assume
 import org.junit.AssumptionViolatedException
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 import spock.lang.Unroll
 
+import java.nio.file.Files
+
 @Requires([TestPrecondition.ONLINE])
 class MavenDownloaderTest extends Specification {
 
-    @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder()
+    @TempDir
+    File tmpDir
 
     def installRoot
     def downloader
 
     def setup() {
-        installRoot = tmpDir.newFolder()
+        installRoot = Files.createTempDirectory(tmpDir.toPath(), null).toFile()
         downloader = new MavenInstallationDownloader(installRoot)
         if (JavaVersion.current().isJava7()) {
             System.setProperty("https.protocols", "TLSv1.2")
diff --git a/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/AbstractCompilerPluginTest.groovy b/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/AbstractCompilerPluginTest.groovy
index 0d1adb0..ea722bc 100644
--- a/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/AbstractCompilerPluginTest.groovy
+++ b/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/AbstractCompilerPluginTest.groovy
@@ -16,21 +16,21 @@
 
 package com.gradle.internal.compiler.java
 
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
+import java.nio.file.Files
 import java.nio.file.Paths
 
 class AbstractCompilerPluginTest extends Specification {
 
-    @Rule
-    TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    File temporaryFolder
 
     protected File sourceFolder
 
     def setup() {
-        sourceFolder = temporaryFolder.newFolder()
+        sourceFolder = Files.createTempDirectory(temporaryFolder.toPath(), null).toFile()
     }
 
     List<File> toSourceFiles(List<String> bodies) {
diff --git a/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/listeners/ConstantsCollectorTest.groovy b/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/listeners/ConstantsCollectorTest.groovy
index 3e6e42c..a4aa1cd 100644
--- a/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/listeners/ConstantsCollectorTest.groovy
+++ b/subprojects/java-compiler-plugin/src/test/groovy/com/gradle/internal/compiler/java/listeners/ConstantsCollectorTest.groovy
@@ -21,6 +21,8 @@
 import org.gradle.internal.compiler.java.listeners.constants.ConstantDependentsConsumer
 import spock.lang.Requires
 
+import java.nio.file.Files
+
 class ConstantsCollectorTest extends AbstractCompilerPluginTest {
 
     TestCompiler compiler
@@ -35,7 +37,7 @@
             (String constantOrigin, String dependent) -> privateDependentToConstants.computeIfAbsent(dependent, { new LinkedHashSet<>() }).add(constantOrigin)
         )
         compiler = new TestCompiler(
-            temporaryFolder.newFolder(),
+            Files.createTempDirectory(temporaryFolder.toPath(), null).toFile(),
             { f -> Optional.empty() },
             {},
             consumer
diff --git a/subprojects/jvm-services/build.gradle.kts b/subprojects/jvm-services/build.gradle.kts
index ac6cf69..c5c7d0d 100644
--- a/subprojects/jvm-services/build.gradle.kts
+++ b/subprojects/jvm-services/build.gradle.kts
@@ -8,8 +8,9 @@
     implementation(project(":base-services"))
     implementation(project(":core-api"))
     implementation(project(":file-temp"))
-    implementation(project(":process-services"))
+    implementation(project(":functional"))
     implementation(project(":logging"))
+    implementation(project(":process-services"))
     implementation(libs.inject)
     implementation(libs.nativePlatform)
     implementation(libs.guava)
diff --git a/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java b/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java
index 6d03519..056f6a9 100644
--- a/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java
+++ b/subprojects/jvm-services/src/main/java/org/gradle/internal/jvm/inspection/JvmInstallationMetadata.java
@@ -16,10 +16,9 @@
 
 package org.gradle.internal.jvm.inspection;
 
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
 import org.gradle.api.JavaVersion;
 import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.serialization.Cached;
 
 import javax.annotation.Nullable;
 import java.io.File;
@@ -77,7 +76,7 @@ class DefaultJvmInstallationMetadata implements JvmInstallationMetadata {
         private final String implementationVersion;
         private final String runtimeVersion;
         private final String jvmVersion;
-        private final Supplier<Set<JavaInstallationCapability>> capabilities = Suppliers.memoize(this::gatherCapabilities);
+        private final Cached<Set<JavaInstallationCapability>> capabilities = Cached.of(this::gatherCapabilities);
 
         private DefaultJvmInstallationMetadata(File javaHome, String implementationVersion, String runtimeVersion, String jvmVersion, String vendor, String implementationName) {
             this.javaHome = javaHome.toPath();
@@ -128,7 +127,7 @@ public String getDisplayName() {
 
         private String determineVendorName() {
             JvmVendor.KnownJvmVendor vendor = getVendor().getKnownVendor();
-            if(vendor == JvmVendor.KnownJvmVendor.ORACLE) {
+            if (vendor == JvmVendor.KnownJvmVendor.ORACLE) {
                 if (implementationName != null && implementationName.contains("OpenJDK")) {
                     return "OpenJDK";
                 }
@@ -158,7 +157,7 @@ private Set<JavaInstallationCapability> gatherCapabilities() {
                 capabilities.add(JavaInstallationCapability.JAVA_COMPILER);
             }
             boolean isJ9vm = implementationName.contains("J9");
-            if(isJ9vm) {
+            if (isJ9vm) {
                 capabilities.add(JavaInstallationCapability.J9_VIRTUAL_MACHINE);
             }
             return capabilities;
diff --git a/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/CachingJvmMetadataDetectorTest.groovy b/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/CachingJvmMetadataDetectorTest.groovy
index 88955db..171d3fb 100644
--- a/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/CachingJvmMetadataDetectorTest.groovy
+++ b/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/CachingJvmMetadataDetectorTest.groovy
@@ -22,14 +22,15 @@
 import org.gradle.testfixtures.internal.NativeServicesTestFixture
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
+
+import java.nio.file.Files
 
 class CachingJvmMetadataDetectorTest extends Specification {
 
-    @Rule
-    TemporaryFolder temporaryFolder
+    @TempDir
+    File temporaryFolder
 
     def "returned metadata from delegate"() {
         def metadata = Mock(JvmInstallationMetadata)
@@ -69,11 +70,11 @@
         NativeServicesTestFixture.initialize()
         def metaDataDetector = new DefaultJvmMetadataDetector(
             TestFiles.execHandleFactory(),
-            TestFiles.tmpDirTemporaryFileProvider(temporaryFolder.root)
+            TestFiles.tmpDirTemporaryFileProvider(temporaryFolder)
         )
         def detector = new CachingJvmMetadataDetector(metaDataDetector)
         File javaHome1 = Jvm.current().javaHome
-        def link = new TestFile(temporaryFolder.newFolder(), "jdklink")
+        def link = new TestFile(Files.createTempDirectory(temporaryFolder.toPath(), null).toFile(), "jdklink")
         link.createLink(javaHome1)
 
         when:
diff --git a/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/DefaultJvmMetadataDetectorTest.groovy b/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/DefaultJvmMetadataDetectorTest.groovy
index 75cbc27..33a82b3 100644
--- a/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/DefaultJvmMetadataDetectorTest.groovy
+++ b/subprojects/jvm-services/src/test/groovy/org/gradle/internal/jvm/inspection/DefaultJvmMetadataDetectorTest.groovy
@@ -24,19 +24,18 @@
 import org.gradle.process.internal.ExecHandleBuilder
 import org.gradle.process.internal.ExecHandleFactory
 import org.gradle.test.fixtures.file.TestFile
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import org.spockframework.runtime.SpockAssertionError
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class DefaultJvmMetadataDetectorTest extends Specification {
 
-    @Rule
-    TemporaryFolder temporaryFolder
+    @TempDir
+    File temporaryFolder
 
     TestFile tmpDir
     def setup() {
-        tmpDir = new TestFile(temporaryFolder.newFolder("tmp"))
+        tmpDir = new TestFile(new File(temporaryFolder, "tmp").tap { mkdirs() })
     }
 
     def "cleans up generated Probe class"() {
@@ -45,7 +44,7 @@
 
         when:
         def detector = createDefaultJvmMetadataDetector(execHandleFactory)
-        def javaHome = temporaryFolder.newFolder("localGradle")
+        def javaHome = new File(temporaryFolder, "localGradle").tap { mkdirs() }
         def metadata = detector.getMetadata(javaHome)
 
         then:
@@ -59,7 +58,7 @@
 
         when:
         def detector = createDefaultJvmMetadataDetector(execHandleFactory)
-        File javaHome = temporaryFolder.newFolder(jdk)
+        File javaHome = new File(temporaryFolder, jdk).tap { mkdirs() }
         if (!jre) {
             def binDir = new File(javaHome, "bin")
             if (binDir.mkdir()) {
@@ -123,7 +122,7 @@
 
         when:
         def detector = createDefaultJvmMetadataDetector(execHandleFactory)
-        def javaHome = temporaryFolder.newFolder(jdk)
+        def javaHome = new File(temporaryFolder, jdk).tap { mkdirs() }
         def metadata = detector.getMetadata(javaHome)
 
         then:
@@ -152,7 +151,7 @@
         def detector = createDefaultJvmMetadataDetector(execHandleFactory)
         File javaHome = new File(jdk)
         if (exists) {
-            javaHome = temporaryFolder.newFolder(jdk)
+            javaHome = new File(temporaryFolder, jdk).tap { mkdirs() }
         }
         def metadata = detector.getMetadata(javaHome)
 
diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt
index d1a9c87..9a1b382 100644
--- a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt
+++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt
@@ -341,7 +341,7 @@
 private
 fun Project.collectScriptPluginFiles(): List<File> =
     mutableListOf<File>().apply {
-        // TODO:cc undeclared build logic input
+        // TODO:configuration-cache undeclared build logic input
         gradlePlugin.pluginSourceSet.allSource.matching {
             it.include("**/*.gradle.kts")
         }.visit {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/WelcomeMessageActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/WelcomeMessageActionTest.groovy
index 8e7b36b..10745c52 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/WelcomeMessageActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/WelcomeMessageActionTest.groovy
@@ -26,8 +26,8 @@
 import org.gradle.util.SetSystemProperties
 import org.gradle.util.internal.TextUtil
 import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import static org.gradle.launcher.cli.DefaultCommandLineActionFactory.WELCOME_MESSAGE_ENABLED_SYSTEM_PROPERTY
 
@@ -36,8 +36,8 @@
     @Rule
     public final SetSystemProperties sysProperties = new SetSystemProperties()
 
-    @Rule
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    public File temporaryFolder
 
     BuildLayoutResult buildLayout
     File gradleUserHomeDir
@@ -46,7 +46,7 @@
     ExecutionListener listener
 
     def setup() {
-        gradleUserHomeDir = temporaryFolder.root
+        gradleUserHomeDir = temporaryFolder
         buildLayout = Mock(BuildLayoutResult) {
             getGradleUserHomeDir() >> gradleUserHomeDir
         }
diff --git a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingFileSystemLocationSnapshotHasherTest.groovy b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingFileSystemLocationSnapshotHasherTest.groovy
index de463ed..f1e4538 100644
--- a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingFileSystemLocationSnapshotHasherTest.groovy
+++ b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingFileSystemLocationSnapshotHasherTest.groovy
@@ -20,9 +20,8 @@
 import org.gradle.internal.fingerprint.LineEndingSensitivity
 import org.gradle.internal.hash.Hashing
 import org.gradle.internal.snapshot.RegularFileSnapshot
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 import spock.lang.Unroll
 
 import java.nio.charset.Charset
@@ -30,8 +29,8 @@
 import org.gradle.api.internal.changedetection.state.LineEndingContentFixture as content
 
 class LineEndingNormalizingFileSystemLocationSnapshotHasherTest extends Specification {
-    @Rule
-    TemporaryFolder tempDir = new TemporaryFolder()
+    @TempDir
+    File tempDir
 
     @Unroll
     def "calculates hash for text file with #description"() {
@@ -89,7 +88,7 @@
     def "always calls delegate for directories"() {
         def delegate = Mock(LineEndingNormalizingFileSystemLocationSnapshotHasher)
         def hasher = LineEndingNormalizingFileSystemLocationSnapshotHasher.wrap(delegate, lineEndingSensitivity)
-        def dir = file('dir')
+        def dir = file('dir').tap { it.text = "" }
         def snapshot = snapshot(dir, FileType.Directory)
 
         when:
@@ -103,7 +102,7 @@
     }
 
     def "throws IOException generated from hasher"() {
-        def file = file('doesNotExist')
+        def file = file('doesNotExist').tap { it.text = "" }
         def delegate = Mock(LineEndingNormalizingFileSystemLocationSnapshotHasher)
         def hasher = LineEndingNormalizingFileSystemLocationSnapshotHasher.wrap(delegate, LineEndingSensitivity.NORMALIZE_LINE_ENDINGS)
         def snapshot = this.snapshot(file)
@@ -142,7 +141,7 @@
     }
 
     File file(String path) {
-        return tempDir.newFile(path)
+        return new File(tempDir, path)
     }
 
     RegularFileSnapshot snapshot(File file, FileType fileType = FileType.RegularFile) {
diff --git a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingInputStreamHasherTest.groovy b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingInputStreamHasherTest.groovy
index 0519de5..9688ba4 100644
--- a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingInputStreamHasherTest.groovy
+++ b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingInputStreamHasherTest.groovy
@@ -16,16 +16,15 @@
 
 package org.gradle.api.internal.changedetection.state
 
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 import spock.lang.Unroll
 
 import org.gradle.api.internal.changedetection.state.LineEndingContentFixture as content
 
 class LineEndingNormalizingInputStreamHasherTest extends Specification {
-    @Rule
-    TemporaryFolder tempDir = new TemporaryFolder()
+    @TempDir
+    File tempDir
 
     def hasher = new LineEndingNormalizingInputStreamHasher()
 
@@ -88,7 +87,7 @@
     }
 
     File file(String path) {
-        return tempDir.newFile(path)
+        return new File(tempDir, path)
     }
 
     static InputStream inputStream(String content) {
diff --git a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingResourceHasherTest.groovy b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingResourceHasherTest.groovy
index a2c886a..c69fbf8 100644
--- a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingResourceHasherTest.groovy
+++ b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/LineEndingNormalizingResourceHasherTest.groovy
@@ -24,9 +24,8 @@
 import org.gradle.internal.fingerprint.hashing.ZipEntryContext
 import org.gradle.internal.hash.Hashing
 import org.gradle.internal.snapshot.RegularFileSnapshot
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 import spock.lang.Unroll
 
 import java.nio.charset.Charset
@@ -34,8 +33,8 @@
 import org.gradle.api.internal.changedetection.state.LineEndingContentFixture as content
 
 class LineEndingNormalizingResourceHasherTest extends Specification {
-    @Rule
-    TemporaryFolder tempDir = new TemporaryFolder()
+    @TempDir
+    File tempDir
 
     @Unroll
     def "calculates hash for text file with #description"() {
@@ -127,7 +126,7 @@
     }
 
     def "throws IOException generated from hasher"() {
-        def file = file('doesNotExist')
+        def file = file('doesNotExist').tap { it.text = "" }
         def delegate = Mock(ResourceHasher)
         def hasher = LineEndingNormalizingResourceHasher.wrap(delegate, LineEndingSensitivity.NORMALIZE_LINE_ENDINGS)
         def snapshotContext = snapshotContext(file)
@@ -177,7 +176,7 @@
     }
 
     File file(String path) {
-        return tempDir.newFile(path)
+        return new File(tempDir, path)
     }
 
     static ZipEntryContext zipContext(File file, boolean directory = false) {
diff --git a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/MetaInfAwareClasspathResourceHasherTest.groovy b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/MetaInfAwareClasspathResourceHasherTest.groovy
index fe85005..4c3159a 100644
--- a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/MetaInfAwareClasspathResourceHasherTest.groovy
+++ b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/MetaInfAwareClasspathResourceHasherTest.groovy
@@ -25,10 +25,10 @@
 import org.gradle.internal.hash.HashCode
 import org.gradle.internal.hash.Hasher
 import org.gradle.internal.snapshot.RegularFileSnapshot
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
+import java.nio.file.Files
 import java.util.function.Supplier
 import java.util.jar.Attributes
 import java.util.jar.Manifest
@@ -36,7 +36,7 @@
 class MetaInfAwareClasspathResourceHasherTest extends Specification {
     public static final String MANIFEST_PATH = 'META-INF/MANIFEST.MF'
 
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @TempDir File tmpDir
 
     ResourceEntryFilter manifestResourceFilter = new IgnoringResourceEntryFilter(ImmutableSet.copyOf("created-by"))
 
@@ -448,8 +448,7 @@
 
     def fileSnapshot(String path, Map<String, Object> attributesMap = [:], Exception exception = null) {
         ByteArrayOutputStream manifestBytes = getManifestByteStream(attributesMap)
-        tmpDir.create()
-        File root = tmpDir.newFolder()
+        File root = Files.createTempDirectory(tmpDir.toPath(), null).toFile()
         File manifestFile = new File(root, MANIFEST_PATH)
         manifestFile.parentFile.mkdirs()
         manifestFile.write(manifestBytes.toString())
diff --git a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/PropertiesFileAwareClasspathResourceHasherTest.groovy b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/PropertiesFileAwareClasspathResourceHasherTest.groovy
index 1d306c2..6d66e70 100644
--- a/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/PropertiesFileAwareClasspathResourceHasherTest.groovy
+++ b/subprojects/normalization-java/src/test/groovy/org/gradle/api/internal/changedetection/state/PropertiesFileAwareClasspathResourceHasherTest.groovy
@@ -29,17 +29,17 @@
 import org.gradle.internal.hash.Hashing
 import org.gradle.internal.snapshot.RegularFileSnapshot
 import org.gradle.internal.util.PropertiesUtils
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 import spock.lang.Unroll
 
 import java.nio.charset.Charset
+import java.nio.file.Files
 import java.util.function.Supplier
 
 @Unroll
 class PropertiesFileAwareClasspathResourceHasherTest extends Specification {
-    @Rule TemporaryFolder tmpdir = new TemporaryFolder()
+    @TempDir File tmpdir
     Map<String, ResourceEntryFilter> filters = Maps.newHashMap()
     ResourceHasher delegate = new RuntimeClasspathResourceHasher()
     ResourceHasher unfilteredHasher = new PropertiesFileAwareClasspathResourceHasher(delegate, PropertiesFileFilter.FILTER_NOTHING)
@@ -356,7 +356,7 @@
     }
 
     RegularFileSnapshotContext fileSnapshot(String path, byte[] bytes) {
-        def dir = tmpdir.newFolder()
+        def dir = Files.createTempDirectory(tmpdir.toPath(), null).toFile()
         def file = new File(dir, path)
         file.parentFile.mkdirs()
         file << bytes
diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy
index 4878a09..1a3a215 100644
--- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy
+++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy
@@ -33,7 +33,7 @@
 class JavaFirstUsePerformanceTest extends AbstractCrossVersionPerformanceTest {
 
     def setup() {
-        runner.targetVersions = ["7.2-20210720234250+0000"]
+        runner.targetVersions = ["7.3-20210818234011+0000"]
     }
 
     def "first use"() {
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkDownloaderTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkDownloaderTest.groovy
index 8378b8b..3f7072b 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkDownloaderTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkDownloaderTest.groovy
@@ -25,21 +25,22 @@
 import org.gradle.internal.resource.ExternalResourceName
 import org.gradle.internal.resource.ExternalResourceRepository
 import org.gradle.internal.verifier.HttpRedirectVerifier
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
+
+import java.nio.file.Files
 
 class AdoptOpenJdkDownloaderTest extends Specification {
 
-    @Rule
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    public File temporaryFolder
 
     def "cancelled download does not leave destination file behind"() {
         RepositoryTransportFactory transportFactory = newFailingTransportFactory()
 
         given:
         def downloader = new AdoptOpenJdkDownloader(transportFactory)
-        def destinationFile = new File(temporaryFolder.newFolder(), "target")
+        def destinationFile = new File(Files.createTempDirectory(temporaryFolder.toPath(), null).toFile(), "target")
 
         when:
         downloader.download(URI.create("https://foo"), destinationFile)
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkRemoteBinaryTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkRemoteBinaryTest.groovy
index 20e7234..b92601e 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkRemoteBinaryTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkRemoteBinaryTest.groovy
@@ -26,15 +26,14 @@
 import org.gradle.jvm.toolchain.internal.DefaultJvmVendorSpec
 import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec
 import org.gradle.util.TestUtil
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 import spock.lang.Unroll
 
 class AdoptOpenJdkRemoteBinaryTest extends Specification {
 
-    @Rule
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    public File temporaryFolder
 
     @Unroll
     def "generates url for jdk #jdkVersion on #operatingSystemName (#architecture)"() {
@@ -124,7 +123,7 @@
         def binary = new AdoptOpenJdkRemoteBinary(systemInfo, operatingSystem, downloader, providerFactory())
 
         when:
-        def targetFile = temporaryFolder.newFile("jdk")
+        def targetFile = new File(temporaryFolder, "jdk")
         binary.download(spec, targetFile)
 
         then:
@@ -182,7 +181,7 @@
         def binary = new AdoptOpenJdkRemoteBinary(systemInfo, operatingSystem, downloader, providerFactory())
 
         when:
-        def targetFile = temporaryFolder.newFile("jdk")
+        def targetFile = new File(temporaryFolder, "jdk")
         binary.download(spec, targetFile)
 
         then:
@@ -203,7 +202,7 @@
         def binary = new AdoptOpenJdkRemoteBinary(systemInfo, operatingSystem, downloader, providerFactory())
 
         when:
-        def targetFile = temporaryFolder.newFile("jdk")
+        def targetFile = new File(temporaryFolder, "jdk")
         binary.download(spec, targetFile)
 
         then:
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/DefaultJavaToolchainProvisioningServiceTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/DefaultJavaToolchainProvisioningServiceTest.groovy
index d594010..5a428fb 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/DefaultJavaToolchainProvisioningServiceTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/DefaultJavaToolchainProvisioningServiceTest.groovy
@@ -21,14 +21,13 @@
 import org.gradle.cache.FileLock
 import org.gradle.internal.operations.TestBuildOperationExecutor
 import org.gradle.jvm.toolchain.JavaToolchainSpec
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class DefaultJavaToolchainProvisioningServiceTest extends Specification {
 
-    @Rule
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    public File temporaryFolder
 
     def "cache is properly locked around provisioning a jdk"() {
         def cache = Mock(JdkCacheDirectory)
@@ -75,7 +74,7 @@
         binary.canProvideMatchingJdk(spec) >> true
         cache.acquireWriteLock(_, _) >> lock
         binary.toFilename(spec) >> 'jdk-123.zip'
-        def downloadLocation = temporaryFolder.newFile("jdk.zip")
+        def downloadLocation = new File(temporaryFolder, "jdk.zip")
         downloadLocation.createNewFile()
         cache.getDownloadLocation(_ as String) >> downloadLocation
         def provisioningService = new DefaultJavaToolchainProvisioningService(binary, cache, providerFactory, new TestBuildOperationExecutor())
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/JdkCacheDirectoryTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/JdkCacheDirectoryTest.groovy
index db19ba2..5f50627 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/JdkCacheDirectoryTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/install/internal/JdkCacheDirectoryTest.groovy
@@ -26,13 +26,13 @@
 import org.gradle.initialization.GradleUserHomeDirProvider
 import org.gradle.util.internal.Resources
 import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class JdkCacheDirectoryTest extends Specification {
 
-    @Rule
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    public File temporaryFolder
 
     @Rule
     public final Resources resources = new Resources()
@@ -51,16 +51,16 @@
     def "lists jdk directories when listing java homes"() {
         given:
         def jdkCacheDirectory = new JdkCacheDirectory(newHomeDirProvider(), Mock(FileOperations), mockLockManager())
-        def install1 = temporaryFolder.newFolder("jdks/jdk-123")
+        def install1 = new File(temporaryFolder, "jdks/jdk-123").tap { mkdirs() }
         new File(install1, "provisioned.ok").createNewFile()
 
-        def install2 = temporaryFolder.newFolder("jdks/jdk-345")
+        def install2 = new File(temporaryFolder, "jdks/jdk-345").tap { mkdirs() }
         new File(install2, "provisioned.ok").createNewFile()
 
-        def install3 = temporaryFolder.newFolder("jdks/jdk-mac/Contents/Home")
-        new File(temporaryFolder.getRoot(), "jdks/jdk-mac/provisioned.ok").createNewFile()
+        def install3 = new File(temporaryFolder, "jdks/jdk-mac/Contents/Home").tap { mkdirs() }
+        new File(temporaryFolder, "jdks/jdk-mac/provisioned.ok").createNewFile()
 
-        temporaryFolder.newFolder("jdks/notReady")
+        new File(temporaryFolder, "jdks/notReady").tap { mkdirs() }
 
         when:
         def homes = jdkCacheDirectory.listJavaHomes()
@@ -71,7 +71,7 @@
 
     def "provisions jdk from tar.gz archive"() {
         def jdkArchive = resources.getResource("jdk.tar.gz")
-        def jdkCacheDirectory = new JdkCacheDirectory(newHomeDirProvider(), TestFiles.fileOperations(temporaryFolder.root, tmpFileProvider()), mockLockManager())
+        def jdkCacheDirectory = new JdkCacheDirectory(newHomeDirProvider(), TestFiles.fileOperations(temporaryFolder, tmpFileProvider()), mockLockManager())
 
         when:
         def installedJdk = jdkCacheDirectory.provisionFromArchive(jdkArchive)
@@ -84,7 +84,7 @@
 
     def "provisions jdk from zip archive"() {
         def jdkArchive = resources.getResource("jdk.zip")
-        def jdkCacheDirectory = new JdkCacheDirectory(newHomeDirProvider(), TestFiles.fileOperations(temporaryFolder.root, tmpFileProvider()), mockLockManager())
+        def jdkCacheDirectory = new JdkCacheDirectory(newHomeDirProvider(), TestFiles.fileOperations(temporaryFolder, tmpFileProvider()), mockLockManager())
 
         when:
         def installedJdk = jdkCacheDirectory.provisionFromArchive(jdkArchive)
@@ -100,14 +100,13 @@
         new GradleUserHomeDirProvider() {
             @Override
             File getGradleUserHomeDirectory() {
-                temporaryFolder.create()
-                return temporaryFolder.root
+                return temporaryFolder
             }
         }
     }
 
     TemporaryFileProvider tmpFileProvider() {
-        new DefaultTemporaryFileProvider({ temporaryFolder.root })
+        new DefaultTemporaryFileProvider({ temporaryFolder })
     }
 
     FileLockManager mockLockManager() {
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/ToolchainReportRendererTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/ToolchainReportRendererTest.groovy
index 0d2b5a0..a5b0632 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/ToolchainReportRendererTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/toolchain/internal/ToolchainReportRendererTest.groovy
@@ -22,14 +22,13 @@
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.jvm.toolchain.internal.task.ReportableToolchain
 import org.gradle.jvm.toolchain.internal.task.ToolchainReportRenderer
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 class ToolchainReportRendererTest extends Specification {
 
-    @Rule
-    TemporaryFolder temporaryFolder
+    @TempDir
+    File temporaryFolder
 
     InstallationLocation installation = Mock(InstallationLocation)
 
@@ -57,7 +56,7 @@
 
     def "jdk is rendered properly"() {
         given:
-        File javaHome = temporaryFolder.newFolder("javahome")
+        File javaHome = new File(temporaryFolder, "javahome").tap { mkdirs() }
         def metadata = JvmInstallationMetadata.from(
             javaHome,
             "1.8.0",
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/modulemap/GenerateModuleMapFileTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/modulemap/GenerateModuleMapFileTest.groovy
index 4a8eb8d..547a951 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/modulemap/GenerateModuleMapFileTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/modulemap/GenerateModuleMapFileTest.groovy
@@ -16,20 +16,19 @@
 
 package org.gradle.nativeplatform.internal.modulemap
 
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import static org.gradle.nativeplatform.internal.modulemap.GenerateModuleMapFile.generateFile
 import static org.gradle.util.internal.TextUtil.normaliseLineSeparators
 
 class GenerateModuleMapFileTest extends Specification {
-    @Rule TemporaryFolder tempDir = new TemporaryFolder()
+    @TempDir File tempDir
 
     def "can generate a simple module map file"() {
-        def moduleMapFile = tempDir.newFile("module.modulemap")
-        def headers = tempDir.newFolder('headers')
-        def moreHeaders = tempDir.newFolder('moreHeaders')
+        def moduleMapFile = new File(tempDir, "module.modulemap")
+        def headers = new File(tempDir, 'headers').tap { mkdirs() }
+        def moreHeaders = new File(tempDir, 'moreHeaders').tap { mkdirs() }
 
         when:
         generateFile(moduleMapFile, "foo", [headers.absolutePath, moreHeaders.absolutePath])
@@ -44,9 +43,9 @@
     }
 
     def "does not include non-existent directories"() {
-        def moduleMapFile = tempDir.newFile("module.modulemap")
-        def headers = tempDir.newFolder('headers')
-        def moreHeaders = tempDir.newFolder('moreHeaders')
+        def moduleMapFile = new File(tempDir, "module.modulemap")
+        def headers = new File(tempDir, 'headers').tap { mkdirs() }
+        def moreHeaders = new File(tempDir, 'moreHeaders').tap { mkdirs() }
 
         when:
         generateFile(moduleMapFile, "foo", [headers.absolutePath, moreHeaders.absolutePath, new File('does-not-exist').absolutePath])
@@ -61,9 +60,9 @@
     }
 
     def "creates parent directory if necessary"() {
-        def moduleMapFile = new File(tempDir.root, "maps/module.modulemap")
-        def headers = tempDir.newFolder('headers')
-        def moreHeaders = tempDir.newFolder('moreHeaders')
+        def moduleMapFile = new File(tempDir, "maps/module.modulemap")
+        def headers = new File(tempDir, 'headers').tap { mkdirs() }
+        def moreHeaders = new File(tempDir, 'moreHeaders').tap { mkdirs() }
 
         given:
         assert !moduleMapFile.parentFile.exists()
diff --git a/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/plugins/JavaGradlePluginPlugin.java b/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/plugins/JavaGradlePluginPlugin.java
index 7f307c6..a4308dd 100644
--- a/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/plugins/JavaGradlePluginPlugin.java
+++ b/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/plugins/JavaGradlePluginPlugin.java
@@ -24,6 +24,7 @@
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.ArtifactView;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.file.CopySpec;
 import org.gradle.api.file.FileCollection;
@@ -69,6 +70,8 @@
 import java.util.Set;
 import java.util.concurrent.Callable;
 
+import static org.gradle.api.internal.lambdas.SerializableLambdas.spec;
+
 /**
  * A plugin for building java gradle plugins. Automatically generates plugin descriptors. Emits warnings for common error conditions. <p> Provides a direct integration with TestKit by declaring the
  * {@code gradleTestKit()} dependency for the test compile configuration and a dependency on the plugin classpath manifest generation task for the test runtime configuration. Default conventions can
@@ -190,13 +193,7 @@ private TaskProvider<PluginUnderTestMetadata> createAndConfigurePluginUnderTestM
                 SourceSet sourceSet = extension.getPluginSourceSet();
                 Configuration runtimeClasspath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName());
                 ArtifactView view = runtimeClasspath.getIncoming().artifactView(config -> {
-                    config.componentFilter(componentId -> {
-                        if (componentId instanceof OpaqueComponentIdentifier) {
-                            DependencyFactory.ClassPathNotation classPathNotation = ((OpaqueComponentIdentifier) componentId).getClassPathNotation();
-                            return classPathNotation != DependencyFactory.ClassPathNotation.GRADLE_API && classPathNotation != DependencyFactory.ClassPathNotation.LOCAL_GROOVY;
-                        }
-                        return true;
-                    });
+                    config.componentFilter(spec(JavaGradlePluginPlugin::excludeGradleApi));
                 });
                 return pluginUnderTestMetadataTask.getProject().getObjects().fileCollection().from(
                     sourceSet.getOutput(),
@@ -206,6 +203,14 @@ private TaskProvider<PluginUnderTestMetadata> createAndConfigurePluginUnderTestM
         });
     }
 
+    private static boolean excludeGradleApi(ComponentIdentifier componentId) {
+        if (componentId instanceof OpaqueComponentIdentifier) {
+            DependencyFactory.ClassPathNotation classPathNotation = ((OpaqueComponentIdentifier) componentId).getClassPathNotation();
+            return classPathNotation != DependencyFactory.ClassPathNotation.GRADLE_API && classPathNotation != DependencyFactory.ClassPathNotation.LOCAL_GROOVY;
+        }
+        return true;
+    }
+
     private void establishTestKitAndPluginClasspathDependencies(Project project, GradlePluginDevelopmentExtension extension, TaskProvider<PluginUnderTestMetadata> pluginClasspathTask) {
         project.afterEvaluate(new TestKitAndPluginClasspathDependenciesAction(extension, pluginClasspathTask));
     }
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractGradleceptionSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractGradleceptionSmokeTest.groovy
index bac3431..ba90a95 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractGradleceptionSmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractGradleceptionSmokeTest.groovy
@@ -40,12 +40,11 @@
     protected BuildResult result
 
     def setup() {
-        new TestFile("build/gradleBuildCurrent").copyTo(testProjectDir.root)
+        new TestFile("build/gradleBuildCurrent").copyTo(testProjectDir)
 
         and:
         def buildJavaHome = AvailableJavaHomes.getAvailableJdks(new GradleBuildJvmSpec()).last().javaHome
         file("gradle.properties") << "\norg.gradle.java.home=${buildJavaHome}\n"
-
     }
 
     BuildResult getResult() {
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy
index aee8d63..0d32f98 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy
@@ -34,9 +34,8 @@
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.testkit.runner.GradleRunner
 import org.gradle.testkit.runner.internal.DefaultGradleRunner
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 import spock.lang.Specification
+import spock.lang.TempDir
 
 import static org.gradle.integtests.fixtures.RepoScriptBlockUtil.createMirrorInitScript
 import static org.gradle.integtests.fixtures.RepoScriptBlockUtil.gradlePluginRepositoryMirrorUrl
@@ -206,15 +205,15 @@
 
     private static final String INIT_SCRIPT_LOCATION = "org.gradle.smoketests.init.script"
 
-    @Rule
-    final TemporaryFolder testProjectDir = new TemporaryFolder()
+    @TempDir
+    File testProjectDir
     File buildFile
 
     File settingsFile
 
     def setup() {
-        buildFile = new File(testProjectDir.root, defaultBuildFileName)
-        settingsFile = new File(testProjectDir.root, "settings.gradle")
+        buildFile = new File(testProjectDir, defaultBuildFileName)
+        settingsFile = new File(testProjectDir, "settings.gradle")
     }
 
     protected String getDefaultBuildFileName() {
@@ -222,11 +221,11 @@
     }
 
     void withKotlinBuildFile() {
-        buildFile = new File(testProjectDir.root, "${getDefaultBuildFileName()}.kts")
+        buildFile = new File(testProjectDir, "${getDefaultBuildFileName()}.kts")
     }
 
     TestFile file(String filename) {
-        def file = new TestFile(testProjectDir.root, filename)
+        def file = new TestFile(testProjectDir, filename)
         def parentDir = file.getParentFile()
         assert parentDir.isDirectory() || parentDir.mkdirs()
 
@@ -237,7 +236,7 @@
         def gradleRunner = GradleRunner.create()
             .withGradleInstallation(IntegrationTestBuildContext.INSTANCE.gradleHomeDir)
             .withTestKitDir(IntegrationTestBuildContext.INSTANCE.gradleUserHomeDir)
-            .withProjectDir(testProjectDir.root)
+            .withProjectDir(testProjectDir)
             .forwardOutput()
             .withArguments(
                 tasks.toList() + outputParameters() + repoMirrorParameters() + configurationCacheParameters()
@@ -309,7 +308,7 @@
 
     protected void useSample(String sampleDirectory) {
         def smokeTestDirectory = new File(this.getClass().getResource(sampleDirectory).toURI())
-        FileUtils.copyDirectory(smokeTestDirectory, testProjectDir.root)
+        FileUtils.copyDirectory(smokeTestDirectory, testProjectDir)
     }
 
     protected SmokeTestGradleRunner useAgpVersion(String agpVersion, SmokeTestGradleRunner runner) {
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BomSupportPluginsSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BomSupportPluginsSmokeTest.groovy
index 5f93d8a..40a4c49 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BomSupportPluginsSmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BomSupportPluginsSmokeTest.groovy
@@ -34,7 +34,7 @@
         def springVersion = springVersion
         def bomVersion = bomVersion
 
-        def settingsFile = testProjectDir.newFile('settings.gradle')
+        def settingsFile = new File(testProjectDir, 'settings.gradle')
         settingsFile << """
             rootProject.name = 'springbootproject'
         """
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GradleBuildConfigurationCacheSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GradleBuildConfigurationCacheSmokeTest.groovy
index fe98cd3..3140091 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GradleBuildConfigurationCacheSmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GradleBuildConfigurationCacheSmokeTest.groovy
@@ -38,7 +38,12 @@
 })
 class GradleBuildConfigurationCacheSmokeTest extends AbstractGradleceptionSmokeTest {
 
-    @Ignore("wip:toolchain failure in CI")
+    def setup() {
+        // Generate Kotlin DSL sources once so they are included as :kotlin-dsl:compileKotlin inputs.
+        // TODO:configuration-cache handle generated sources better (see gradlebuild.kotlin-dsl-dependencies-embedded.gradle.kts:39)
+        run([':kotlin-dsl:generateKotlinDependencyExtensions'])
+    }
+
     def "can run Gradle unit tests with configuration cache enabled"() {
 
         given:
@@ -65,7 +70,6 @@
         result.task(":tooling-api:publishLocalPublicationToLocalRepository").outcome == TaskOutcome.SUCCESS
     }
 
-    @Ignore("wip:Kotlin 1.5.21")
     def "can run Gradle integ tests with configuration cache enabled"() {
 
         given: "tasks whose configuration can only be loaded in the original daemon"
@@ -92,7 +96,6 @@
         assertTestClassExecutedIn "subprojects/configuration-cache", "org.gradle.configurationcache.ConfigurationCacheDebugLogIntegrationTest"
     }
 
-    @Ignore("wip:Kotlin 1.5.21")
     def "can run Gradle cross-version tests with configuration cache enabled"() {
 
         given:
@@ -118,7 +121,6 @@
         result.task(":configuration-cache:embeddedCrossVersionTest").outcome == TaskOutcome.SUCCESS
     }
 
-    @Ignore("wip:Kotlin 1.5.21")
     def "can run Gradle smoke tests with configuration cache enabled"() {
 
         given:
@@ -144,7 +146,6 @@
         result.task(":smoke-test:smokeTest").outcome == TaskOutcome.SUCCESS
     }
 
-    @Ignore("wip:Kotlin 1.5.21")
     def "can run Gradle soak tests with configuration cache enabled"() {
 
         given:
@@ -170,7 +171,6 @@
         result.task(":soak:forkingIntegTest").outcome == TaskOutcome.SUCCESS
     }
 
-    @NotYetImplemented
     def "can run Gradle codeQuality with configuration cache enabled"() {
 
         given:
@@ -190,11 +190,11 @@
 
         then:
         assertConfigurationCacheStateLoaded()
-        result.task(":configuration-cache:runKtlintCheckOverMainSourceSet").outcome == TaskOutcome.SUCCESS
+        result.task(":configuration-cache:runKtlintCheckOverMainSourceSet").outcome == TaskOutcome.FROM_CACHE
         result.task(":configuration-cache:validatePlugins").outcome == TaskOutcome.SUCCESS
-        result.task(":configuration-cache:codenarcIntegTest").outcome == TaskOutcome.SUCCESS
+        result.task(":configuration-cache:codenarcIntegTest").outcome == TaskOutcome.FROM_CACHE
         result.task(":configuration-cache:checkstyleIntegTestGroovy").outcome == TaskOutcome.SUCCESS
-        result.task(":configuration-cache:classycleIntegTest").outcome == TaskOutcome.SUCCESS
+        result.task(":configuration-cache:classycleIntegTest").outcome == TaskOutcome.FROM_CACHE
         result.task(":configuration-cache:codeQuality").outcome == TaskOutcome.SUCCESS
     }
 
@@ -221,7 +221,6 @@
         result.task(":architecture-test:checkBinaryCompatibility").outcome == TaskOutcome.SUCCESS
     }
 
-    @Ignore("wip:Kotlin 1.5.21")
     def "can build and install Gradle binary distribution with configuration cache enabled"() {
 
         given:
@@ -256,7 +255,7 @@
         def tasks = [
             ':docs:docs',
             ':docs:docsTest',
-            "-D${ConfigurationCacheMaxProblemsOption.PROPERTY_NAME}=8192".toString(), // TODO remove
+            "-D${ConfigurationCacheMaxProblemsOption.PROPERTY_NAME}=8192".toString(), // TODO:configuration-cache remove
         ]
 
         when:
@@ -296,7 +295,7 @@
         run(
             tasks + [
                 "--${ConfigurationCacheOption.LONG_OPTION}".toString(),
-                "--${ConfigurationCacheProblemsOption.LONG_OPTION}=warn".toString(), // TODO remove
+                "--${ConfigurationCacheProblemsOption.LONG_OPTION}=warn".toString(), // TODO:configuration-cache remove
                 TEST_BUILD_TIMESTAMP
             ],
             // use a unique testKitDir per daemonId other than 0 as 0 means default daemon.
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GrettySmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GrettySmokeTest.groovy
index f93ca5e..c7a2d83 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GrettySmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/GrettySmokeTest.groovy
@@ -51,7 +51,7 @@
                 httpPort = 0
                 integrationTestTask = 'checkContainerUp'
                 servletContainer = 'jetty9'
-                logDir = '${testProjectDir.root.absolutePath}/jetty-logs'
+                logDir = '${testProjectDir.absolutePath}/jetty-logs'
                 logFileName = project.name
             }
 
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/JGitPluginSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/JGitPluginSmokeTest.groovy
index 2cc6fb1..161b7db 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/JGitPluginSmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/JGitPluginSmokeTest.groovy
@@ -28,7 +28,7 @@
     @ToBeFixedForConfigurationCache(because = "Gradle.buildFinished")
     def 'org.ajoberstar.grgit plugin'() {
         given:
-        GitFileRepository.init(testProjectDir.root)
+        GitFileRepository.init(testProjectDir)
         buildFile << """
             plugins {
                 id "org.ajoberstar.grgit" version "${TestedVersions.grgit}"
diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyGradleModuleMetadataSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyGradleModuleMetadataSmokeTest.groovy
index 6becbec..7ea9e7a 100644
--- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyGradleModuleMetadataSmokeTest.groovy
+++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyGradleModuleMetadataSmokeTest.groovy
@@ -39,11 +39,11 @@
         def androidPluginVersion = AGP_VERSIONS.getLatestOfMinor("4.2")
         def arch = OperatingSystem.current().macOsX ? 'MacosX64' : 'LinuxX64'
 
-        def expectedMetadata = new File(testProjectDir.root, 'expected-metadata')
-        def actualRepo = new File(testProjectDir.root, 'producer/repo')
+        def expectedMetadata = new File(testProjectDir, 'expected-metadata')
+        def actualRepo = new File(testProjectDir, 'producer/repo')
 
         when:
-        buildFile = new File(testProjectDir.root, "producer/${defaultBuildFileName}.kts")
+        buildFile = new File(testProjectDir, "producer/${defaultBuildFileName}.kts")
         replaceVariablesInBuildFile(
             kotlinVersion: kotlinVersion,
             androidPluginVersion: androidPluginVersion)
@@ -61,7 +61,7 @@
         }
 
         when:
-        buildFile = new File(testProjectDir.root, "consumer/${defaultBuildFileName}.kts")
+        buildFile = new File(testProjectDir, "consumer/${defaultBuildFileName}.kts")
         replaceVariablesInBuildFile(
             kotlinVersion: kotlinVersion,
             androidPluginVersion: androidPluginVersion)
@@ -128,7 +128,7 @@
 
     private BuildResult publish() {
         setIllegalAccessPermitForJDK16KotlinCompilerDaemonOptions(runner('publish'))
-            .withProjectDir(new File(testProjectDir.root, 'producer'))
+            .withProjectDir(new File(testProjectDir, 'producer'))
             .forwardOutput()
             // this deprecation is coming from the Kotlin plugin
             .expectDeprecationWarning("The AbstractCompile.destinationDir property has been deprecated. " +
@@ -141,7 +141,7 @@
 
     private BuildResult consumer(String runTask) {
         setIllegalAccessPermitForJDK16KotlinCompilerDaemonOptions(runner(runTask, '-q'))
-            .withProjectDir(new File(testProjectDir.root, 'consumer'))
+            .withProjectDir(new File(testProjectDir, 'consumer'))
             .forwardOutput()
             .build()
     }
@@ -149,7 +149,7 @@
     // Reevaluate if this is still needed when upgrading android plugin. Currently required with version 4.2.2
     private BuildResult consumerWithJdk16WorkaroundForAndroidManifest(String runTask) {
         def runner = runner(runTask, '-q')
-            .withProjectDir(new File(testProjectDir.root, 'consumer'))
+            .withProjectDir(new File(testProjectDir, 'consumer'))
             .forwardOutput()
         if (JavaVersion.current().isJava9Compatible()) {
             runner.withJvmArguments("--add-opens", "java.base/java.io=ALL-UNNAMED")
diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/CurrentFileCollectionFingerprint.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/CurrentFileCollectionFingerprint.java
index 0a72e22..f8e2c4d 100644
--- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/CurrentFileCollectionFingerprint.java
+++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/CurrentFileCollectionFingerprint.java
@@ -16,9 +16,12 @@
 
 package org.gradle.internal.fingerprint;
 
+import com.google.common.collect.ImmutableMultimap;
 import org.gradle.internal.hash.HashCode;
 import org.gradle.internal.snapshot.FileSystemSnapshot;
 
+import java.util.Map;
+
 /**
  * A file collection fingerprint taken during this build.
  */
@@ -28,6 +31,11 @@ public interface CurrentFileCollectionFingerprint extends FileCollectionFingerpr
      */
     HashCode getHash();
 
+    /**
+     * An identifier for the strategy.
+     *
+     * Used to select a compare strategy.
+     */
     String getStrategyIdentifier();
 
     /**
@@ -36,4 +44,15 @@ public interface CurrentFileCollectionFingerprint extends FileCollectionFingerpr
     FileSystemSnapshot getSnapshot();
 
     boolean isEmpty();
+
+    /**
+     * Archive the file collection fingerprint.
+     *
+     * @return a file collection fingerprint which can be archived.
+     */
+    FileCollectionFingerprint archive(ArchivedFileCollectionFingerprintFactory factory);
+
+    interface ArchivedFileCollectionFingerprintFactory {
+        FileCollectionFingerprint createArchivedFileCollectionFingerprint(Map<String, FileSystemLocationFingerprint> fingerprints, ImmutableMultimap<String, HashCode> rootHashes, HashCode strategyConfigurationHash);
+    }
 }
diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java
index 3fc4c66..146b5d4 100644
--- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java
+++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java
@@ -17,10 +17,8 @@
 package org.gradle.internal.fingerprint;
 
 import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
 import org.gradle.internal.hash.HashCode;
-import org.gradle.internal.hash.Hashing;
 
 import java.util.Map;
 
@@ -39,16 +37,9 @@ public interface FileCollectionFingerprint {
      */
     ImmutableMultimap<String, HashCode> getRootHashes();
 
-    /**
-     * The absolute paths for the roots of this file collection fingerprint.
-     */
-    default ImmutableSet<String> getRootPaths() {
-        return getRootHashes().keySet();
-    }
+    boolean wasCreatedWithStrategy(FingerprintingStrategy strategy);
 
     FileCollectionFingerprint EMPTY = new FileCollectionFingerprint() {
-        private final HashCode strategyConfigurationHash = Hashing.signature(getClass());
-
         @Override
         public Map<String, FileSystemLocationFingerprint> getFingerprints() {
             return ImmutableSortedMap.of();
@@ -60,13 +51,8 @@ public ImmutableMultimap<String, HashCode> getRootHashes() {
         }
 
         @Override
-        public ImmutableSet<String> getRootPaths() {
-            return ImmutableSet.of();
-        }
-
-        @Override
-        public HashCode getStrategyConfigurationHash() {
-            return strategyConfigurationHash;
+        public boolean wasCreatedWithStrategy(FingerprintingStrategy strategy) {
+            return false;
         }
 
         @Override
@@ -74,6 +60,4 @@ public String toString() {
             return "EMPTY";
         }
     };
-
-    HashCode getStrategyConfigurationHash();
 }
diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbstractFingerprintingStrategy.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbstractFingerprintingStrategy.java
index 6d448c3..fc93b71 100644
--- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbstractFingerprintingStrategy.java
+++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbstractFingerprintingStrategy.java
@@ -75,7 +75,7 @@ private static String failedToNormalize(FileSystemLocationSnapshot snapshot) {
         return String.format("Failed to normalize content of '%s'.", snapshot.getAbsolutePath());
     }
 
-    public DirectorySensitivity getDirectorySensitivity() {
+    protected DirectorySensitivity getDirectorySensitivity() {
         return directorySensitivity;
     }
 
diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/DefaultCurrentFileCollectionFingerprint.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/DefaultCurrentFileCollectionFingerprint.java
index 07ce00f..23e4e68 100644
--- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/DefaultCurrentFileCollectionFingerprint.java
+++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/DefaultCurrentFileCollectionFingerprint.java
@@ -50,7 +50,7 @@ public static CurrentFileCollectionFingerprint from(FileSystemSnapshot roots, Fi
         ImmutableMultimap<String, HashCode> rootHashes = SnapshotUtil.getRootHashes(roots);
         Map<String, FileSystemLocationFingerprint> fingerprints;
         if (candidate != null
-            && candidate.getStrategyConfigurationHash().equals(strategy.getConfigurationHash())
+            && candidate.wasCreatedWithStrategy(strategy)
             && equalRootHashes(candidate.getRootHashes(), rootHashes)
         ) {
             fingerprints = candidate.getFingerprints();
@@ -109,6 +109,11 @@ public ImmutableMultimap<String, HashCode> getRootHashes() {
     }
 
     @Override
+    public boolean wasCreatedWithStrategy(FingerprintingStrategy strategy) {
+        return strategy.getConfigurationHash().equals(strategyConfigurationHash);
+    }
+
+    @Override
     public String getStrategyIdentifier() {
         return identifier;
     }
@@ -119,8 +124,8 @@ public FileSystemSnapshot getSnapshot() {
     }
 
     @Override
-    public HashCode getStrategyConfigurationHash() {
-        return strategyConfigurationHash;
+    public FileCollectionFingerprint archive(ArchivedFileCollectionFingerprintFactory factory) {
+        return factory.createArchivedFileCollectionFingerprint(fingerprints, rootHashes, strategyConfigurationHash);
     }
 
     @Override
diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java
index fc31b0e..3a8d6a8 100644
--- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java
+++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java
@@ -17,9 +17,10 @@
 package org.gradle.internal.fingerprint.impl;
 
 import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
 import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint;
+import org.gradle.internal.fingerprint.FileCollectionFingerprint;
 import org.gradle.internal.fingerprint.FileSystemLocationFingerprint;
+import org.gradle.internal.fingerprint.FingerprintingStrategy;
 import org.gradle.internal.hash.HashCode;
 import org.gradle.internal.hash.Hashing;
 import org.gradle.internal.snapshot.FileSystemSnapshot;
@@ -63,13 +64,8 @@ public ImmutableMultimap<String, HashCode> getRootHashes() {
     }
 
     @Override
-    public ImmutableSet<String> getRootPaths() {
-        return ImmutableSet.of();
-    }
-
-    @Override
-    public HashCode getStrategyConfigurationHash() {
-        return SIGNATURE;
+    public boolean wasCreatedWithStrategy(FingerprintingStrategy strategy) {
+        return false;
     }
 
     @Override
@@ -78,6 +74,11 @@ public String getStrategyIdentifier() {
     }
 
     @Override
+    public FileCollectionFingerprint archive(ArchivedFileCollectionFingerprintFactory factory) {
+        return FileCollectionFingerprint.EMPTY;
+    }
+
+    @Override
     public String toString() {
         return identifier + "{EMPTY}";
     }
diff --git a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConsoleInputEndUserIntegrationTest.groovy b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConsoleInputEndUserIntegrationTest.groovy
index a0c56ba..f8189f4 100644
--- a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConsoleInputEndUserIntegrationTest.groovy
+++ b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConsoleInputEndUserIntegrationTest.groovy
@@ -34,8 +34,6 @@
                 testImplementation gradleTestKit()
                 testImplementation(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
                 testImplementation("org.spockframework:spock-core")
-                testImplementation("org.spockframework:spock-junit4")
-                testImplementation 'junit:junit:4.13.1'
             }
 
             test.useJUnitPlatform()
@@ -68,21 +66,20 @@
         """
             import org.gradle.testkit.runner.GradleRunner
             import static org.gradle.testkit.runner.TaskOutcome.*
-            import org.junit.Rule
-            import org.junit.rules.TemporaryFolder
             import spock.lang.Specification
+            import spock.lang.TempDir
 
             class Test extends Specification {
-                @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+                @TempDir File testProjectDir
                 File buildFile
 
                 def setup() {
-                    def buildSrcDir = testProjectDir.newFolder('buildSrc', 'src', 'main', 'java')
+                    def buildSrcDir = new File(testProjectDir, 'buildSrc/src/main/java').tap { mkdirs() }
                     def pluginFile = new File(buildSrcDir, 'BuildScanPlugin.java')
                     pluginFile << '''${buildScanPlugin()}'''
-                    def settingsFile = testProjectDir.newFile('settings.gradle')
+                    def settingsFile = new File(testProjectDir, 'settings.gradle')
                     settingsFile << "rootProject.name = 'test'"
-                    buildFile = testProjectDir.newFile('build.gradle')
+                    buildFile = new File(testProjectDir, 'build.gradle')
                     buildFile << '''${buildScanPluginApplication()}'''
                 }
 
@@ -109,7 +106,7 @@
     static String gradleRunnerWithoutStandardInput() {
         """
             GradleRunner.create()
-                .withProjectDir(testProjectDir.root)
+                .withProjectDir(testProjectDir)
                 .withArguments('$DUMMY_TASK_NAME')
                 .withDebug($debug)
                 .build()
@@ -119,7 +116,7 @@
     static String gradleRunnerWithStandardInput() {
         """
             GradleRunner.create()
-                .withProjectDir(testProjectDir.root)
+                .withProjectDir(testProjectDir)
                 .withArguments('$DUMMY_TASK_NAME')
                 .withDebug($debug)
                 .withStandardInput(System.in)
diff --git a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConventionalPluginClasspathInjectionEndUserIntegrationTest.groovy b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConventionalPluginClasspathInjectionEndUserIntegrationTest.groovy
index 8784f68..17e6c6f 100644
--- a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConventionalPluginClasspathInjectionEndUserIntegrationTest.groovy
+++ b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerConventionalPluginClasspathInjectionEndUserIntegrationTest.groovy
@@ -36,8 +36,6 @@
             dependencies {
                 testImplementation(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
                 testImplementation("org.spockframework:spock-core")
-                testImplementation("org.spockframework:spock-junit4")
-                testImplementation 'junit:junit:4.13.1'
             }
             test.useJUnitPlatform()
         """
@@ -47,22 +45,21 @@
         file("src/test/groovy/Test.groovy") << """
             import org.gradle.testkit.runner.GradleRunner
             import static org.gradle.testkit.runner.TaskOutcome.*
-            import org.junit.Rule
-            import org.junit.rules.TemporaryFolder
             import spock.lang.Specification
+            import spock.lang.TempDir
 
             class Test extends Specification {
 
-                @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+                @TempDir File testProjectDir
 
                 def "execute helloWorld task"() {
                     given:
-                    testProjectDir.newFile('settings.gradle') << "rootProject.name = 'plugin-test'"
-                    testProjectDir.newFile('build.gradle') << '''$plugin.useDeclaration'''
+                    new File(testProjectDir, 'settings.gradle') << "rootProject.name = 'plugin-test'"
+                    new File(testProjectDir, 'build.gradle') << '''$plugin.useDeclaration'''
 
                     when:
                     def result = GradleRunner.create()
-                        .withProjectDir(testProjectDir.root)
+                        .withProjectDir(testProjectDir)
                         .withArguments('helloWorld')
                         .withPluginClasspath()
                         .withDebug($debug)
diff --git a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerDefaultTestKitDirIntegrationTest.groovy b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerDefaultTestKitDirIntegrationTest.groovy
index f2a1f5e..e6b0df0 100644
--- a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerDefaultTestKitDirIntegrationTest.groovy
+++ b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerDefaultTestKitDirIntegrationTest.groovy
@@ -49,8 +49,6 @@
                 implementation localGroovy()
                 testImplementation(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
                 testImplementation("org.spockframework:spock-core")
-                testImplementation("org.spockframework:spock-junit4")
-                testImplementation("junit:junit:4.13.1")
             }
 
             ${mavenCentralRepository()}
diff --git a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerPluginClasspathInjectionEndUserIntegrationTest.groovy b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerPluginClasspathInjectionEndUserIntegrationTest.groovy
index 5a4dcfd..6e0e471 100644
--- a/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerPluginClasspathInjectionEndUserIntegrationTest.groovy
+++ b/subprojects/test-kit/src/integTest/groovy/org/gradle/testkit/runner/enduser/GradleRunnerPluginClasspathInjectionEndUserIntegrationTest.groovy
@@ -46,8 +46,6 @@
                 implementation localGroovy()
                 testImplementation(platform("org.spockframework:spock-bom:2.0-groovy-3.0"))
                 testImplementation("org.spockframework:spock-core")
-                testImplementation("org.spockframework:spock-junit4")
-                testImplementation 'junit:junit:4.13.1'
                 testImplementation gradleTestKit()
                 testImplementation files(createClasspathManifest)
             }
@@ -65,17 +63,16 @@
         file("src/test/groovy/Test.groovy") << """
             import org.gradle.testkit.runner.GradleRunner
             import static org.gradle.testkit.runner.TaskOutcome.*
-            import org.junit.Rule
-            import org.junit.rules.TemporaryFolder
             import spock.lang.Specification
+            import spock.lang.TempDir
 
             class Test extends Specification {
-                @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+                @TempDir File testProjectDir
                 File buildFile
 
                 def setup() {
-                    testProjectDir.newFile('settings.gradle') << "rootProject.name = 'test'"
-                    buildFile = testProjectDir.newFile('build.gradle')
+                    new File(testProjectDir, 'settings.gradle') << "rootProject.name = 'test'"
+                    buildFile = new File(testProjectDir, 'build.gradle')
                     def pluginClasspath = getClass().classLoader.findResource("plugin-classpath.txt")
                       .readLines()
                       .collect { it.replace('\\\\', '\\\\\\\\') } // escape backslashes in Windows paths
@@ -97,7 +94,7 @@
 
                     when:
                     def result = GradleRunner.create()
-                        .withProjectDir(testProjectDir.root)
+                        .withProjectDir(testProjectDir)
                         .withArguments('helloWorld')
                         .withDebug($debug)
                         .build()
@@ -119,18 +116,17 @@
         file("src/test/groovy/Test.groovy") << """
             import org.gradle.testkit.runner.GradleRunner
             import static org.gradle.testkit.runner.TaskOutcome.*
-            import org.junit.Rule
-            import org.junit.rules.TemporaryFolder
             import spock.lang.Specification
+            import spock.lang.TempDir
 
             class Test extends Specification {
-                @Rule TemporaryFolder testProjectDir = new TemporaryFolder()
+                @TempDir File testProjectDir
                 File buildFile
                 List<File> pluginClasspath
 
                 def setup() {
-                    testProjectDir.newFile('settings.gradle') << "rootProject.name = 'test'"
-                    buildFile = testProjectDir.newFile('build.gradle')
+                    new File(testProjectDir, 'settings.gradle') << "rootProject.name = 'test'"
+                    buildFile = new File(testProjectDir, 'build.gradle')
                     pluginClasspath = getClass().classLoader.findResource("plugin-classpath.txt")
                       .readLines()
                       .collect { new File(it) }
@@ -142,7 +138,7 @@
 
                     when:
                     def result = GradleRunner.create()
-                        .withProjectDir(testProjectDir.root)
+                        .withProjectDir(testProjectDir)
                         .withArguments('helloWorld')
                         .withPluginClasspath(pluginClasspath)
                         .withDebug($debug)
diff --git a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
index dbbf362..425ccb3 100644
--- a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
+++ b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
@@ -14,6 +14,7 @@
 
     @Override
     List<String> asArguments() {
-        ["-DexpectedClassPath=${testClasspath.asPath}".toString()]
+        FileCollection filteredTestClasspath = testClasspath.filter { f -> f.exists() || ("*".equals(f.getName()) && f.getParentFile() != null && f.getParentFile().exists()) }
+        ["-DexpectedClassPath=${filteredTestClasspath.asPath}".toString()]
     }
 }
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildControllerFactory.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildControllerFactory.java
index 6fe0630..96eb96c 100644
--- a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildControllerFactory.java
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildControllerFactory.java
@@ -19,23 +19,20 @@
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.internal.build.BuildStateRegistry;
 import org.gradle.internal.build.BuildToolingModelController;
-import org.gradle.internal.resources.ProjectLeaseRegistry;
 import org.gradle.internal.service.scopes.Scopes;
 import org.gradle.internal.service.scopes.ServiceScope;
 
 @ServiceScope(Scopes.BuildTree.class)
 public class BuildControllerFactory {
     private final BuildCancellationToken buildCancellationToken;
-    private final ProjectLeaseRegistry projectLeaseRegistry;
     private final BuildStateRegistry buildStateRegistry;
 
-    public BuildControllerFactory(BuildCancellationToken buildCancellationToken, ProjectLeaseRegistry projectLeaseRegistry, BuildStateRegistry buildStateRegistry) {
+    public BuildControllerFactory(BuildCancellationToken buildCancellationToken, BuildStateRegistry buildStateRegistry) {
         this.buildCancellationToken = buildCancellationToken;
-        this.projectLeaseRegistry = projectLeaseRegistry;
         this.buildStateRegistry = buildStateRegistry;
     }
 
     public DefaultBuildController controllerFor(BuildToolingModelController controller) {
-        return new DefaultBuildController(controller, buildCancellationToken, projectLeaseRegistry, buildStateRegistry);
+        return new DefaultBuildController(controller, buildCancellationToken, buildStateRegistry);
     }
 }
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java
index bd3d3f7..6cf2e4d 100644
--- a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java
@@ -16,7 +16,6 @@
 
 package org.gradle.tooling.internal.provider.runner;
 
-import org.gradle.api.internal.GradleInternal;
 import org.gradle.internal.build.BuildToolingModelAction;
 import org.gradle.internal.build.BuildToolingModelController;
 import org.gradle.internal.buildtree.BuildActionRunner;
@@ -63,10 +62,6 @@ public Result run(BuildAction action, final BuildTreeLifecycleController buildCo
         }
     }
 
-    private static ToolingModelBuilderLookup getToolingModelBuilderRegistry(GradleInternal gradle) {
-        return gradle.getDefaultProject().getServices().get(ToolingModelBuilderLookup.class);
-    }
-
     private static class ModelCreateAction implements BuildToolingModelAction<Object> {
         private final BuildModelAction buildModelAction;
         private UnknownModelException modelLookupFailure;
@@ -83,11 +78,9 @@ public void beforeTasks(BuildToolingModelController controller) {
         @Override
         public Object fromBuildModel(BuildToolingModelController controller) {
             String modelName = buildModelAction.getModelName();
-            GradleInternal gradle = controller.getConfiguredModel();
-            ToolingModelBuilderLookup builderRegistry = getToolingModelBuilderRegistry(gradle);
             ToolingModelBuilderLookup.Builder builder;
             try {
-                builder = builderRegistry.locateForClientOperation(modelName, false, gradle);
+                builder = controller.locateBuilderForDefaultTarget(modelName, false);
             } catch (UnknownModelException e) {
                 modelLookupFailure = e;
                 throw e;
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/DefaultBuildController.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/DefaultBuildController.java
index 68d84f0..0dcf345 100644
--- a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/DefaultBuildController.java
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/DefaultBuildController.java
@@ -17,8 +17,7 @@
 package org.gradle.tooling.internal.provider.runner;
 
 import org.gradle.api.BuildCancelledException;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectState;
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.internal.Try;
 import org.gradle.internal.build.BuildState;
@@ -29,7 +28,6 @@
 import org.gradle.internal.operations.BuildOperationDescriptor;
 import org.gradle.internal.operations.MultipleBuildOperationFailures;
 import org.gradle.internal.operations.RunnableBuildOperation;
-import org.gradle.internal.resources.ProjectLeaseRegistry;
 import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
 import org.gradle.tooling.internal.adapter.ViewBuilder;
 import org.gradle.tooling.internal.gradle.GradleBuildIdentity;
@@ -45,6 +43,7 @@
 import org.gradle.tooling.provider.model.internal.ToolingModelBuilderLookup;
 import org.gradle.util.Path;
 
+import javax.annotation.Nullable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
@@ -54,18 +53,15 @@
 class DefaultBuildController implements org.gradle.tooling.internal.protocol.InternalBuildController, InternalBuildControllerVersion2, InternalActionAwareBuildController {
     private final BuildToolingModelController controller;
     private final BuildCancellationToken cancellationToken;
-    private final ProjectLeaseRegistry projectLeaseRegistry;
     private final BuildStateRegistry buildStateRegistry;
 
     public DefaultBuildController(
         BuildToolingModelController controller,
         BuildCancellationToken cancellationToken,
-        ProjectLeaseRegistry projectLeaseRegistry,
         BuildStateRegistry buildStateRegistry
     ) {
         this.controller = controller;
         this.cancellationToken = cancellationToken;
-        this.projectLeaseRegistry = projectLeaseRegistry;
         this.buildStateRegistry = buildStateRegistry;
     }
 
@@ -92,7 +88,7 @@ public BuildResult<?> getModel(Object target, ModelIdentifier modelIdentifier) t
      * This is used by consumers 4.4 and later
      */
     @Override
-    public BuildResult<?> getModel(Object target, ModelIdentifier modelIdentifier, Object parameter)
+    public BuildResult<?> getModel(@Nullable Object target, ModelIdentifier modelIdentifier, Object parameter)
         throws BuildExceptionVersion1, InternalUnsupportedModelException {
         assertCanQuery();
         if (cancellationToken.isCancellationRequested()) {
@@ -113,7 +109,7 @@ public BuildResult<?> getModel(Object target, ModelIdentifier modelIdentifier, O
 
     @Override
     public boolean getCanQueryProjectModelInParallel(Class<?> modelType) {
-        return projectLeaseRegistry.getAllowsParallelExecution() && controller.queryModelActionsRunInParallel();
+        return controller.queryModelActionsRunInParallel();
     }
 
     @Override
@@ -150,16 +146,16 @@ private Object getParameterizedModel(ToolingModelBuilderLookup.Builder builder,
         return builder.build(internalParameter);
     }
 
-    private ModelTarget getTarget(Object target) {
+    private ModelTarget getTarget(@Nullable Object target) {
         if (target == null) {
-            return new BuildScopedModel(controller.getConfiguredModel());
+            return new DefaultTargetModel(controller);
         } else if (target instanceof GradleProjectIdentity) {
             GradleProjectIdentity projectIdentity = (GradleProjectIdentity) target;
             BuildState build = findBuild(projectIdentity);
-            return new ProjectScopedModel(findProject(build, projectIdentity));
+            return new ProjectScopedModel(controller, findProject(build, projectIdentity));
         } else if (target instanceof GradleBuildIdentity) {
             GradleBuildIdentity buildIdentity = (GradleBuildIdentity) target;
-            return new BuildScopedModel(findBuild(buildIdentity).getMutableModel());
+            return new BuildScopedModel(controller, findBuild(buildIdentity));
         } else {
             throw new IllegalArgumentException("Don't know how to build models for " + target);
         }
@@ -179,14 +175,13 @@ private BuildState findBuild(GradleBuildIdentity buildIdentity) {
         }
     }
 
-    private ProjectInternal findProject(BuildState build, GradleProjectIdentity projectIdentity) {
-        return build.getProjects().getProject(Path.path(projectIdentity.getProjectPath())).getMutableModel();
+    private ProjectState findProject(BuildState build, GradleProjectIdentity projectIdentity) {
+        return build.getProjects().getProject(Path.path(projectIdentity.getProjectPath()));
     }
 
     private ToolingModelBuilderLookup.Builder getToolingModelBuilder(ModelTarget modelTarget, boolean parameter, ModelIdentifier modelIdentifier) {
-        ToolingModelBuilderLookup modelBuilderRegistry = modelTarget.targetProject.getServices().get(ToolingModelBuilderLookup.class);
         try {
-            return modelTarget.locate(modelBuilderRegistry, parameter, modelIdentifier);
+            return modelTarget.locate(parameter, modelIdentifier);
         } catch (UnknownModelException e) {
             throw (InternalUnsupportedModelException) new InternalUnsupportedModelException().initCause(e);
         }
@@ -199,37 +194,49 @@ private void assertCanQuery() {
     }
 
     private static abstract class ModelTarget {
-        final ProjectInternal targetProject;
-
-        protected ModelTarget(ProjectInternal targetProject) {
-            this.targetProject = targetProject;
-        }
-
-        abstract ToolingModelBuilderLookup.Builder locate(ToolingModelBuilderLookup lookup, boolean parameter, ModelIdentifier modelIdentifier);
+        abstract ToolingModelBuilderLookup.Builder locate(boolean parameter, ModelIdentifier modelIdentifier);
     }
 
     private static class ProjectScopedModel extends ModelTarget {
-        public ProjectScopedModel(ProjectInternal targetProject) {
-            super(targetProject);
+        private final BuildToolingModelController controller;
+        private final ProjectState target;
+
+        public ProjectScopedModel(BuildToolingModelController controller, ProjectState target) {
+            this.controller = controller;
+            this.target = target;
         }
 
         @Override
-        ToolingModelBuilderLookup.Builder locate(ToolingModelBuilderLookup lookup, boolean parameter, ModelIdentifier modelIdentifier) {
-            return lookup.locateForClientOperation(modelIdentifier.getName(), parameter, targetProject);
+        ToolingModelBuilderLookup.Builder locate(boolean parameter, ModelIdentifier modelIdentifier) {
+            return controller.locateBuilderForTarget(target, modelIdentifier.getName(), parameter);
         }
     }
 
     private static class BuildScopedModel extends ModelTarget {
-        private final GradleInternal targetBuild;
+        private final BuildToolingModelController controller;
+        private final BuildState target;
 
-        public BuildScopedModel(GradleInternal gradle) {
-            super(gradle.getDefaultProject());
-            this.targetBuild = gradle;
+        public BuildScopedModel(BuildToolingModelController controller, BuildState target) {
+            this.controller = controller;
+            this.target = target;
         }
 
         @Override
-        ToolingModelBuilderLookup.Builder locate(ToolingModelBuilderLookup lookup, boolean parameter, ModelIdentifier modelIdentifier) {
-            return lookup.locateForClientOperation(modelIdentifier.getName(), parameter, targetBuild);
+        ToolingModelBuilderLookup.Builder locate(boolean parameter, ModelIdentifier modelIdentifier) {
+            return controller.locateBuilderForTarget(target, modelIdentifier.getName(), parameter);
+        }
+    }
+
+    private static class DefaultTargetModel extends ModelTarget {
+        private final BuildToolingModelController controller;
+
+        public DefaultTargetModel(BuildToolingModelController controller) {
+            this.controller = controller;
+        }
+
+        @Override
+        ToolingModelBuilderLookup.Builder locate(boolean parameter, ModelIdentifier modelIdentifier) {
+            return controller.locateBuilderForDefaultTarget(modelIdentifier.getName(), parameter);
         }
     }
 
diff --git a/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/DefaultBuildControllerTest.groovy b/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/DefaultBuildControllerTest.groovy
index a9a3f81..9431542 100644
--- a/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/DefaultBuildControllerTest.groovy
+++ b/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/DefaultBuildControllerTest.groovy
@@ -17,8 +17,6 @@
 package org.gradle.tooling.internal.provider.runner
 
 import org.gradle.api.BuildCancelledException
-import org.gradle.api.internal.GradleInternal
-import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.project.ProjectState
 import org.gradle.initialization.BuildCancellationToken
 import org.gradle.internal.build.BuildProjectRegistry
@@ -27,8 +25,6 @@
 import org.gradle.internal.build.BuildToolingModelController
 import org.gradle.internal.concurrent.GradleThread
 import org.gradle.internal.operations.MultipleBuildOperationFailures
-import org.gradle.internal.resources.ProjectLeaseRegistry
-import org.gradle.internal.service.ServiceRegistry
 import org.gradle.tooling.internal.gradle.GradleBuildIdentity
 import org.gradle.tooling.internal.gradle.GradleProjectIdentity
 import org.gradle.tooling.internal.protocol.InternalUnsupportedModelException
@@ -43,25 +39,13 @@
 
 class DefaultBuildControllerTest extends Specification {
     def cancellationToken = Stub(BuildCancellationToken)
-    def gradle = Stub(GradleInternal) {
-        getServices() >> Stub(ServiceRegistry) {
-            get(BuildCancellationToken) >> cancellationToken
-        }
-    }
-    def registry = Stub(ToolingModelBuilderLookup)
-    def project = Stub(ProjectInternal) {
-        getServices() >> Stub(ServiceRegistry) {
-            get(ToolingModelBuilderLookup) >> registry
-        }
-    }
     def modelId = Stub(ModelIdentifier) {
         getName() >> 'some.model'
     }
     def modelBuilder = Stub(ToolingModelBuilderLookup.Builder)
-    def projectLeaseRegistry = Stub(ProjectLeaseRegistry)
     def buildStateRegistry = Stub(BuildStateRegistry)
     def toolingModelController = Mock(BuildToolingModelController)
-    def controller = new DefaultBuildController(toolingModelController, cancellationToken, projectLeaseRegistry, buildStateRegistry)
+    def controller = new DefaultBuildController(toolingModelController, cancellationToken, buildStateRegistry)
 
     def setup() {
         GradleThread.setManaged()
@@ -87,9 +71,7 @@
         def failure = new UnknownModelException("not found")
 
         given:
-        _ * toolingModelController.configuredModel >> gradle
-        _ * gradle.defaultProject >> project
-        _ * registry.locateForClientOperation('some.model', false, gradle) >> { throw failure }
+        _ * toolingModelController.locateBuilderForDefaultTarget('some.model', false) >> { throw failure }
 
         when:
         controller.getModel(null, modelId)
@@ -136,8 +118,7 @@
         _ * buildState3.buildRootDir >> rootDir
         _ * buildState3.projects >> projects3
         _ * projects3.getProject(Path.path(":some:path")) >> projectState
-        _ * projectState.mutableModel >> project
-        _ * registry.locateForClientOperation("some.model", false, project) >> modelBuilder
+        _ * toolingModelController.locateBuilderForTarget(projectState, "some.model", false) >> modelBuilder
         _ * modelBuilder.build(null) >> model
 
         when:
@@ -150,7 +131,6 @@
     def "uses builder for specified build"() {
         def rootDir = new File("dummy")
         def target = Stub(GradleBuildIdentity)
-        def rootProject = Stub(ProjectInternal)
         def buildState1 = Stub(BuildState)
         def buildState2 = Stub(BuildState)
         def model = new Object()
@@ -164,10 +144,7 @@
         _ * buildState1.importableBuild >> false
         _ * buildState2.importableBuild >> true
         _ * buildState2.buildRootDir >> rootDir
-        _ * buildState2.mutableModel >> gradle
-        _ * gradle.rootProject >> rootProject
-        _ * gradle.defaultProject >> project
-        _ * registry.locateForClientOperation("some.model", false, gradle) >> modelBuilder
+        _ * toolingModelController.locateBuilderForTarget(buildState2, "some.model", false) >> modelBuilder
         _ * modelBuilder.build(null) >> model
 
         when:
@@ -181,9 +158,7 @@
         def model = new Object()
 
         given:
-        _ * toolingModelController.configuredModel >> gradle
-        _ * gradle.defaultProject >> project
-        _ * registry.locateForClientOperation("some.model", false, gradle) >> modelBuilder
+        _ * toolingModelController.locateBuilderForDefaultTarget("some.model", false) >> modelBuilder
         _ * modelBuilder.build(null) >> model
 
         when:
@@ -219,9 +194,7 @@
         }
 
         given:
-        _ * toolingModelController.configuredModel >> gradle
-        _ * gradle.defaultProject >> project
-        _ * registry.locateForClientOperation("some.model", true, gradle) >> modelBuilder
+        _ * toolingModelController.locateBuilderForDefaultTarget("some.model", true) >> modelBuilder
         _ * modelBuilder.getParameterType() >> parameterType
         _ * modelBuilder.build(_) >> { CustomParameter param ->
             assert param != null
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/CancellationSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/CancellationSpec.groovy
index 097da44..b8ca5f3 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/CancellationSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/CancellationSpec.groovy
@@ -40,7 +40,14 @@
 '''
     }
 
-    def setupCancelInConfigurationBuild() {
+    void setupCancelInSettingsBuild() {
+        settingsFile << waitForCancel()
+        buildFile << """
+throw new RuntimeException("should not run")
+"""
+    }
+
+    void setupCancelInConfigurationBuild() {
         settingsFile << '''
 include 'sub'
 rootProject.name = 'cancelling'
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionDiscardsFailure.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionDiscardsConfigurationFailure.java
similarity index 84%
rename from subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionDiscardsFailure.java
rename to subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionDiscardsConfigurationFailure.java
index 717c6f0..b249b9e 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionDiscardsFailure.java
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionDiscardsConfigurationFailure.java
@@ -18,12 +18,13 @@
 
 import org.gradle.tooling.BuildAction;
 import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.GradleProject;
 
-public class ActionDiscardsFailure implements BuildAction<String> {
+public class ActionDiscardsConfigurationFailure implements BuildAction<String> {
     @Override
     public String execute(BuildController controller) {
         try {
-            controller.getBuildModel();
+            controller.getModel(GradleProject.class);
             throw new IllegalStateException("should fail");
         } catch (Exception e) {
             // Ignore
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionForwardsFailure.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionQueriesModelThatRequiresConfigurationPhase.java
similarity index 81%
copy from subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionForwardsFailure.java
copy to subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionQueriesModelThatRequiresConfigurationPhase.java
index 06d7dd0..7b9f49c 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionForwardsFailure.java
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionQueriesModelThatRequiresConfigurationPhase.java
@@ -18,11 +18,12 @@
 
 import org.gradle.tooling.BuildAction;
 import org.gradle.tooling.BuildController;
+import org.gradle.tooling.model.GradleProject;
 
-public class ActionForwardsFailure implements BuildAction<String> {
+public class ActionQueriesModelThatRequiresConfigurationPhase implements BuildAction<String> {
     @Override
     public String execute(BuildController controller) {
-        controller.getBuildModel();
+        controller.getModel(GradleProject.class);
         return "result";
     }
 }
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionForwardsFailure.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionQueriesModelThatRequiresOnlySettingsEvaluation.java
similarity index 89%
rename from subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionForwardsFailure.java
rename to subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionQueriesModelThatRequiresOnlySettingsEvaluation.java
index 06d7dd0..d06dc37 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionForwardsFailure.java
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/ActionQueriesModelThatRequiresOnlySettingsEvaluation.java
@@ -19,7 +19,7 @@
 import org.gradle.tooling.BuildAction;
 import org.gradle.tooling.BuildController;
 
-public class ActionForwardsFailure implements BuildAction<String> {
+public class ActionQueriesModelThatRequiresOnlySettingsEvaluation implements BuildAction<String> {
     @Override
     public String execute(BuildController controller) {
         controller.getBuildModel();
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/FetchModelAction.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/FetchModelAction.java
deleted file mode 100644
index 5508b3e..0000000
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/fixture/FetchModelAction.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.tooling.fixture;
-
-import org.gradle.tooling.BuildAction;
-import org.gradle.tooling.BuildController;
-
-public class FetchModelAction implements BuildAction<String> {
-    @Override
-    public String execute(BuildController controller) {
-        controller.getBuildModel();
-        return "result";
-    }
-}
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
index 693e1b8..2c796150 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.tooling.fixture.FetchModelAction
+import org.gradle.integtests.tooling.fixture.ActionQueriesModelThatRequiresConfigurationPhase
 import org.gradle.integtests.tooling.fixture.ToolingApiLoggingSpecification
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.tooling.model.GradleProject
@@ -90,7 +90,7 @@
 
         when:
         withConnection { connection ->
-            def action = connection.action(new FetchModelAction())
+            def action = connection.action(new ActionQueriesModelThatRequiresConfigurationPhase())
             action.standardOutput = stdout
             action.standardError = stderr
             return action.run()
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy
index ecdbbe0..eeefcc5 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r18/BuildActionCrossVersionSpec.groovy
@@ -16,11 +16,11 @@
 
 package org.gradle.integtests.tooling.r18
 
-
+import org.gradle.integtests.tooling.fixture.ActionQueriesModelThatRequiresConfigurationPhase
+import org.gradle.integtests.tooling.fixture.ActionQueriesModelThatRequiresOnlySettingsEvaluation
+import org.gradle.integtests.tooling.fixture.ActionShouldNotBeCalled
 import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.integtests.tooling.fixture.ActionForwardsFailure
-import org.gradle.integtests.tooling.fixture.ActionShouldNotBeCalled
 import org.gradle.tooling.BuildActionFailureException
 import org.gradle.tooling.BuildException
 import org.gradle.tooling.UnknownModelException
@@ -153,13 +153,62 @@
     }
 
     @TargetGradleVersion(">=7.3")
-    def "action receives failure when configuration fails"() {
+    def "action receives failure when init script fails"() {
+        given:
+        def initScript = file("init.gradle")
+        initScript << 'throw new RuntimeException("broken")'
+
+        when:
+        withConnection {
+            def action = it.action(new ActionQueriesModelThatRequiresOnlySettingsEvaluation())
+            action.withArguments("-I${initScript}")
+            collectOutputs(action)
+            action.run()
+        }
+
+        then:
+        BuildActionFailureException e = thrown()
+        e.message.startsWith('The supplied build action failed with an exception.')
+        e.cause.message.contains('A problem occurred evaluating initialization script.')
+
+        and:
+        failure.assertHasDescription('A problem occurred evaluating initialization script.')
+        assertHasConfigureFailedLogging()
+    }
+
+    @TargetGradleVersion(">=7.3")
+    def "action receives failure when settings evaluation fails"() {
+        given:
+        settingsFile << '''
+            rootProject.name = 'root'
+            throw new RuntimeException("broken")
+        '''
+
+        when:
+        withConnection {
+            def action = it.action(new ActionQueriesModelThatRequiresOnlySettingsEvaluation())
+            collectOutputs(action)
+            action.run()
+        }
+
+        then:
+        BuildActionFailureException e = thrown()
+        e.message.startsWith('The supplied build action failed with an exception.')
+        e.cause.message.contains("A problem occurred evaluating settings 'root'.")
+
+        and:
+        failure.assertHasDescription("A problem occurred evaluating settings 'root'.")
+        assertHasConfigureFailedLogging()
+    }
+
+    @TargetGradleVersion(">=7.3")
+    def "action receives failure when project configuration fails"() {
         given:
         buildFile << 'throw new RuntimeException("broken")'
 
         when:
         withConnection {
-            def action = it.action(new ActionForwardsFailure())
+            def action = it.action(new ActionQueriesModelThatRequiresConfigurationPhase())
             collectOutputs(action)
             action.run()
         }
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r22/CancellationCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r22/CancellationCrossVersionSpec.groovy
index fcdb9a9..7ee557a 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r22/CancellationCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r22/CancellationCrossVersionSpec.groovy
@@ -18,7 +18,7 @@
 
 import org.gradle.integtests.tooling.CancellationSpec
 import org.gradle.integtests.tooling.fixture.TestResultHandler
-import org.gradle.integtests.tooling.fixture.ActionForwardsFailure
+import org.gradle.integtests.tooling.fixture.ActionQueriesModelThatRequiresConfigurationPhase
 import org.gradle.tooling.BuildCancelledException
 import org.gradle.tooling.GradleConnector
 import org.gradle.tooling.ProjectConnection
@@ -27,10 +27,7 @@
 class CancellationCrossVersionSpec extends CancellationSpec {
 
     def "can cancel build during settings phase"() {
-        settingsFile << waitForCancel()
-        buildFile << """
-throw new RuntimeException("should not run")
-"""
+        setupCancelInSettingsBuild()
 
         def cancel = GradleConnector.newCancellationTokenSource()
         def sync = server.expectAndBlock("registered")
@@ -108,6 +105,29 @@
         configureOnDemand << [true, false]
     }
 
+    def "can cancel build action execution during settings phase"() {
+        setupCancelInSettingsBuild()
+
+        def cancel = GradleConnector.newCancellationTokenSource()
+        def sync = server.expectAndBlock("registered")
+        def resultHandler = new TestResultHandler()
+
+        when:
+        withConnection { ProjectConnection connection ->
+            def action = connection.action(new ActionQueriesModelThatRequiresConfigurationPhase())
+            action.withCancellationToken(cancel.token())
+            collectOutputs(action)
+            action.run(resultHandler)
+            sync.waitForAllPendingCalls(resultHandler)
+            cancel.cancel()
+            sync.releaseAll()
+            resultHandler.finished()
+        }
+
+        then:
+        configureWasCancelled(resultHandler, "Could not run build action using")
+    }
+
     def "can cancel build action execution during configuration phase"() {
         file("gradle.properties") << "org.gradle.configureondemand=${configureOnDemand}"
         setupCancelInConfigurationBuild()
@@ -118,7 +138,7 @@
 
         when:
         withConnection { ProjectConnection connection ->
-            def action = connection.action(new ActionForwardsFailure())
+            def action = connection.action(new ActionQueriesModelThatRequiresConfigurationPhase())
             action.withCancellationToken(cancel.token())
             collectOutputs(action)
             action.run(resultHandler)
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r41/BuildListenerCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r41/BuildListenerCrossVersionSpec.groovy
index 262e91d..f082a0d 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r41/BuildListenerCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r41/BuildListenerCrossVersionSpec.groovy
@@ -21,7 +21,7 @@
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.integtests.tooling.r18.FetchBuildEnvironment
 import org.gradle.tooling.ProjectConnection
-import org.gradle.tooling.model.gradle.GradleBuild
+import org.gradle.tooling.model.GradleProject
 
 @TargetGradleVersion(">=4.1")
 class BuildListenerCrossVersionSpec extends ToolingApiSpecification {
@@ -53,9 +53,9 @@
         when:
         withConnection {
             ProjectConnection connection ->
-                connection.model(GradleBuild)
-                .setStandardOutput(output)
-                .get()
+                connection.model(GradleProject)
+                    .setStandardOutput(output)
+                    .get()
         }
 
         then:
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/CancellationCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/CancellationCrossVersionSpec.groovy
index 2c690a4..4609ddb 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/CancellationCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/CancellationCrossVersionSpec.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.tooling.r48
 
 import org.gradle.integtests.tooling.CancellationSpec
-import org.gradle.integtests.tooling.fixture.FetchModelAction
+import org.gradle.integtests.tooling.fixture.ActionQueriesModelThatRequiresConfigurationPhase
 import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.TestResultHandler
 import org.gradle.integtests.tooling.fixture.ToolingApiVersion
@@ -37,7 +37,7 @@
         when:
         withConnection { ProjectConnection connection ->
             def action = connection.action()
-            action.projectsLoaded(new FetchModelAction(), Stub(IntermediateResultHandlerCollector))
+            action.projectsLoaded(new ActionQueriesModelThatRequiresConfigurationPhase(), Stub(IntermediateResultHandlerCollector))
             def build = action.build()
             build.withCancellationToken(cancel.token())
             collectOutputs(build)
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/PhasedBuildActionCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/PhasedBuildActionCrossVersionSpec.groovy
index eb2bd07..75415ed 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/PhasedBuildActionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r48/PhasedBuildActionCrossVersionSpec.groovy
@@ -16,8 +16,8 @@
 
 package org.gradle.integtests.tooling.r48
 
-import org.gradle.integtests.tooling.fixture.ActionDiscardsFailure
-import org.gradle.integtests.tooling.fixture.ActionForwardsFailure
+import org.gradle.integtests.tooling.fixture.ActionDiscardsConfigurationFailure
+import org.gradle.integtests.tooling.fixture.ActionQueriesModelThatRequiresConfigurationPhase
 import org.gradle.integtests.tooling.fixture.ActionShouldNotBeCalled
 import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
@@ -210,7 +210,7 @@
         when:
         withConnection { connection ->
             def action = connection.action()
-                .projectsLoaded(new ActionForwardsFailure(), projectsLoadedHandler)
+                .projectsLoaded(new ActionQueriesModelThatRequiresConfigurationPhase(), projectsLoadedHandler)
                 .buildFinished(new ActionShouldNotBeCalled(), buildFinishedHandler)
                 .build()
             collectOutputs(action)
@@ -242,8 +242,8 @@
         when:
         withConnection { connection ->
             def action = connection.action()
-                .projectsLoaded(new ActionDiscardsFailure(), projectsLoadedHandler)
-                .buildFinished(new ActionForwardsFailure(), buildFinishedHandler)
+                .projectsLoaded(new ActionDiscardsConfigurationFailure(), projectsLoadedHandler)
+                .buildFinished(new ActionQueriesModelThatRequiresConfigurationPhase(), buildFinishedHandler)
                 .build()
             collectOutputs(action)
             action.run()
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/CompositeDeduplicationCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/CompositeDeduplicationCrossVersionSpec.groovy
index c7b0408..d2a67cd 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/CompositeDeduplicationCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/CompositeDeduplicationCrossVersionSpec.groovy
@@ -20,16 +20,11 @@
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.model.eclipse.EclipseProject
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 
 @ToolingApiVersion(">=5.5")
 @TargetGradleVersion(">=4.0")
 class CompositeDeduplicationCrossVersionSpec extends ToolingApiSpecification {
 
-    @Rule
-    TemporaryFolder externalProjectFolder = new TemporaryFolder()
-
     def setup() {
         buildFile << """
         project(':b') {
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/ReservedProjectNamesCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/ReservedProjectNamesCrossVersionSpec.groovy
index d7142cf..391c951 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/ReservedProjectNamesCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r55/ReservedProjectNamesCrossVersionSpec.groovy
@@ -22,15 +22,14 @@
 import org.gradle.tooling.model.eclipse.EclipseProject
 import org.gradle.tooling.model.eclipse.EclipseWorkspace
 import org.gradle.tooling.model.eclipse.EclipseWorkspaceProject
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
+import spock.lang.TempDir
 
 @TargetGradleVersion('>=5.5')
 @ToolingApiVersion(">=5.5")
 class ReservedProjectNamesCrossVersionSpec extends ToolingApiSpecification {
 
-    @Rule
-    TemporaryFolder externalProjectFolder = new TemporaryFolder();
+    @TempDir
+    File externalProjectFolder
 
     def setup() {
         buildFile << """
@@ -160,7 +159,7 @@
     }
 
     EclipseWorkspace eclipseWorkspace(List<EclipseWorkspaceProject> projects) {
-        new DefaultEclipseWorkspace(externalProjectFolder.newFolder("workspace"), projects)
+        new DefaultEclipseWorkspace(new File(externalProjectFolder, "workspace").tap { mkdirs() }, projects)
     }
 
     EclipseWorkspaceProject gradleProject(String name) {
@@ -172,7 +171,7 @@
     }
 
     EclipseWorkspaceProject externalProject(String name) {
-        new DefaultEclipseWorkspaceProject(name, externalProjectFolder.newFolder(name))
+        new DefaultEclipseWorkspaceProject(name, new File(externalProjectFolder, name).tap { mkdirs() })
     }
 
 }
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r56/ClosedProjectSubstitutionCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r56/ClosedProjectSubstitutionCrossVersionSpec.groovy
index 1ddd7bb..0b2d73f 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r56/ClosedProjectSubstitutionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r56/ClosedProjectSubstitutionCrossVersionSpec.groovy
@@ -22,8 +22,6 @@
 import org.gradle.tooling.model.eclipse.EclipseProject
 import org.gradle.tooling.model.eclipse.EclipseWorkspace
 import org.gradle.tooling.model.eclipse.EclipseWorkspaceProject
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
 
 import java.util.regex.Pattern
 
@@ -31,9 +29,6 @@
 @ToolingApiVersion(">=5.6")
 class ClosedProjectSubstitutionCrossVersionSpec extends ToolingApiSpecification {
 
-    @Rule
-    TemporaryFolder externalProjectFolder = new TemporaryFolder()
-
     def "will substitute and run build dependencies for closed projects on startup"() {
         setup:
         multiProjectBuildInRootFolder("parent", ["child1", "child2"]) {
diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r73/DeferredConfigurationCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r73/DeferredConfigurationCrossVersionSpec.groovy
index 0630dbe..a652a95 100644
--- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r73/DeferredConfigurationCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r73/DeferredConfigurationCrossVersionSpec.groovy
@@ -19,7 +19,6 @@
 import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.integtests.tooling.fixture.ToolingApiVersion
-import spock.lang.Ignore
 
 @TargetGradleVersion(">=7.3")
 class DeferredConfigurationCrossVersionSpec extends ToolingApiSpecification {
@@ -70,7 +69,6 @@
         result.assertNotOutput(prefix)
     }
 
-    @Ignore
     def "runs settings scripts and does not configure projects when action queries GradleProject model"() {
         setupBuild()
 
@@ -90,7 +88,6 @@
         result.assertNotOutput(rootProjectMessage)
     }
 
-    @Ignore
     @ToolingApiVersion(">=4.8")
     def "runs settings scripts and does not configure projects when phased action queries GradleProject model"() {
         setupBuild()
diff --git a/subprojects/workers/src/test/groovy/org/gradle/workers/internal/DefaultWorkerExecutorParallelTest.groovy b/subprojects/workers/src/test/groovy/org/gradle/workers/internal/DefaultWorkerExecutorParallelTest.groovy
index 4571d3b..a5cf8fb 100644
--- a/subprojects/workers/src/test/groovy/org/gradle/workers/internal/DefaultWorkerExecutorParallelTest.groovy
+++ b/subprojects/workers/src/test/groovy/org/gradle/workers/internal/DefaultWorkerExecutorParallelTest.groovy
@@ -34,16 +34,15 @@
 import org.gradle.workers.WorkAction
 import org.gradle.workers.WorkParameters
 import org.gradle.workers.WorkerExecutionException
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
+import spock.lang.TempDir
 import spock.lang.Unroll
 
 import static org.gradle.internal.work.AsyncWorkTracker.ProjectLockRetention.RETAIN_PROJECT_LOCKS
 
 @UsesNativeServices
 class DefaultWorkerExecutorParallelTest extends ConcurrentSpec {
-    @Rule
-    public TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @TempDir
+    public File temporaryFolder
     def workerDaemonFactory = Mock(WorkerFactory)
     def workerInProcessFactory = Mock(WorkerFactory)
     def workerNoIsolationFactory = Mock(WorkerFactory)
@@ -55,7 +54,7 @@
         fileCollection() >> { TestFiles.fileCollectionFactory().configurableFiles() }
     }
     def workerDirectoryProvider = Stub(WorkerDirectoryProvider) {
-        getWorkingDirectory() >> { temporaryFolder.root }
+        getWorkingDirectory() >> { temporaryFolder }
     }
     def executionQueueFactory = Mock(WorkerExecutionQueueFactory)
     def executionQueue = Mock(ConditionalExecutionQueue)
@@ -70,7 +69,7 @@
         _ * instantiator.newInstance(DefaultClassLoaderWorkerSpec) >> { args -> new DefaultClassLoaderWorkerSpec(objectFactory) }
         _ * instantiator.newInstance(DefaultProcessWorkerSpec, _) >> { args -> new DefaultProcessWorkerSpec(args[1][0], objectFactory) }
         _ * instantiator.newInstance(DefaultWorkerExecutor.DefaultWorkQueue, _, _, _) >> { args -> new DefaultWorkerExecutor.DefaultWorkQueue(args[1][0], args[1][1], args[1][2]) }
-        workerExecutor = new DefaultWorkerExecutor(workerDaemonFactory, workerInProcessFactory, workerNoIsolationFactory, forkOptionsFactory, buildOperationWorkerRegistry, buildOperationExecutor, asyncWorkerTracker, workerDirectoryProvider, executionQueueFactory, classLoaderStructureProvider, actionExecutionSpecFactory, instantiator, temporaryFolder.root)
+        workerExecutor = new DefaultWorkerExecutor(workerDaemonFactory, workerInProcessFactory, workerNoIsolationFactory, forkOptionsFactory, buildOperationWorkerRegistry, buildOperationExecutor, asyncWorkerTracker, workerDirectoryProvider, executionQueueFactory, classLoaderStructureProvider, actionExecutionSpecFactory, instantiator, temporaryFolder)
         _ * actionExecutionSpecFactory.newIsolatedSpec(_, _, _, _, _) >> Mock(IsolatedParametersActionExecutionSpec)
     }
 
@@ -147,7 +146,7 @@
         @Override
         JavaForkOptionsInternal newJavaForkOptions() {
             def forkOptions = delegate.newJavaForkOptions()
-            forkOptions.setWorkingDir(temporaryFolder.root)
+            forkOptions.setWorkingDir(temporaryFolder)
             return forkOptions
         }