[go: nahoru, domu]

Splitting buildSrc into multiple projects

:buildSrc:plugins contains the plugins applied by projects
:buildSrc:private contains implementation

This allows the contents of plugins/ to not be added to the classpath that Gradle uses for evaluating projects, which means that changes to those classes don't necessarily invalidate the UP-TO-DATE status of other tasks applied by other plugins in those projects (most notably compileKotlin)

Bug: 140265324

Test: ./gradlew projects

Test: cd paging && ./gradlew checkApi

Test: Treehugger runs busytown/androidx.sh

Test: # demonstrate that unrelated changes in buildSrc no longer invalidate compileKotlin tasks unnecessarily

      # build kotlin
      ./gradlew :core:core:compileDebugKotlin
      # make some unrelated changes in buildSrc:
      sed -i 's/ignoreCase = true/ignoreCase = false/g' buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
      # build kotlin again
      ./gradlew :core:core:compileDebugKotlin
      # see that the tasks were up-to-date

Test: # demonstrate that making a relevant change in buildSrc does invalidate kotlinCompile tasks
      1: # change jvmTarget
         sed -i 's/jvmTarget = "1.8"/jvmTarget = "9"/' buildSrc/impl/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt

      2: # run another build
         ./gradlew :core:core:compileDebugKotlin

      3: # see that this task was out-of-date

Change-Id: I8fb321ca59f9eb0518f730748489a34f376de605
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index 75dad66..17d4740 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -27,7 +27,7 @@
 dependencies {
     implementation(gradleApi())
     testImplementation(libs.junit)
-    implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/build/libs/buildSrc.jar")))
+    implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/private/build/libs/private.jar")))
 }
 
 // Also do style checking of the buildSrc project from within this project too
@@ -51,4 +51,4 @@
 // Broken in AGP 7.0-alpha15 due to b/180408027
 tasks["lint"].configure { t ->
     t.enabled = false
-}
\ No newline at end of file
+}
diff --git a/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle b/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle
index d6119a6..b3cdbe7 100644
--- a/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXComposeImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle b/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle
index b429e58..2800850 100644
--- a/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXDocsImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXImplPlugin.gradle b/buildSrc/apply/applyAndroidXImplPlugin.gradle
index 6dabde0..e4441dc 100644
--- a/buildSrc/apply/applyAndroidXImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle b/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle
index c3e7988..eb6b13a 100644
--- a/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXPlaygroundRootImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/apply/applyAndroidXRootImplPlugin.gradle b/buildSrc/apply/applyAndroidXRootImplPlugin.gradle
index fbb73e3..eb77a06 100644
--- a/buildSrc/apply/applyAndroidXRootImplPlugin.gradle
+++ b/buildSrc/apply/applyAndroidXRootImplPlugin.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     dependencies {
-        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/build/buildSrc.jar"))
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
     }
 }
 
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 707fd6e..4af4322 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -24,7 +24,6 @@
 ext.supportRootFolder = project.projectDir.getParentFile()
 apply from: "repos.gradle"
 apply plugin: "kotlin"
-apply from: "kotlin-dsl-dependency.gradle"
 
 allprojects {
     repos.addMavenRepositories(repositories)
@@ -46,157 +45,6 @@
     }
 }
 
-configurations {
-    // Dependencies added to these configurations get copied into the corresponding configuration
-    // (cacheableApi gets copied into api, etc).
-    // Because we cache the resolutions of these configurations, performance is faster when
-    // artifacts are put into these configurations than when those artifacts are put into their
-    // corresponding configuration.
-    cacheableApi
-    cacheableImplementation {
-        extendsFrom(project.configurations.cacheableApi)
-    }
-    cacheableRuntimeOnly
-}
-
 dependencies {
-    cacheableApi(libs.androidGradlePluginz)
-    cacheableImplementation(libs.dexMemberList)
-    cacheableApi(libs.kotlinGradlePluginz)
-    cacheableImplementation(gradleApi())
-    cacheableApi(libs.dokkaGradlePluginz)
-    // needed by inspection plugin
-    cacheableImplementation(libs.protobufGradlePluginz)
-    cacheableImplementation(libs.wireGradlePluginz)
-    cacheableImplementation(libs.shadow)
-    // dependencies that aren't used by buildSrc directly but that we resolve here so that the
-    // root project doesn't need to re-resolve them and their dependencies on every build
-    cacheableRuntimeOnly(libs.hiltAndroidGradlePluginz)
-    // room kotlintestapp uses the ksp plugin but it does not publish a plugin marker yet
-    cacheableApi(libs.kspGradlePluginz)
-    cacheableApi(libs.japicmpPluginz)
-    // dependencies whose resolutions we don't need to cache
-    compileOnly(findGradleKotlinDsl()) // Only one file in this configuration, no need to cache it
-    implementation(project("jetpad-integration")) // Doesn't have a .pom, so not slow to load
-}
-
-// Exclude dokka coming from AGP. We don't need it and it conflicts with dackka: b/195305339
-configurations.configureEach { conf ->
-  conf.exclude(group:"org.jetbrains.dokka", module:"dokka-core")
-}
-
-apply plugin: "java-gradle-plugin"
-
-sourceSets {
-    ["public", "private", "plugins"].each { subdir ->
-      main.java.srcDirs += "${subdir}/src/main/kotlin"
-      main.resources.srcDirs += "${subdir}/src/main/resources"
-    }
-
-
-    main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
-    main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
-
-    main.java.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main/kotlin"
-    main.resources.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main" +
-            "/resources"
-
-    main.java.srcDirs += "${supportRootFolder}/compose/material/material/icons/generator/src/main" +
-            "/kotlin"
-}
-
-gradlePlugin {
-    plugins {
-        benchmark {
-            id = "androidx.benchmark"
-            implementationClass = "androidx.benchmark.gradle.BenchmarkPlugin"
-        }
-        inspection {
-            id = "androidx.inspection"
-            implementationClass = "androidx.inspection.gradle.InspectionPlugin"
-        }
-    }
-}
-
-// Saves configuration into destFile
-// Each line of destFile will be the absolute filepath of one of the files in configuration
-def saveConfigurationResolution(configuration, destFile) {
-    def resolvedConfiguration = configuration.resolvedConfiguration
-    def files = resolvedConfiguration.files
-    def paths = files.collect { f -> f.toString() }
-    def serialized = paths.join("\n")
-    destFile.text = serialized
-}
-
-// Parses a file into a list of Dependency objects representing a ResolvedConfiguration
-def parseConfigurationResolution(savedFile, throwOnError) {
-    def savedText = savedFile.text
-    def filenames = savedText.split("\n")
-    def valid = true
-    def dependencies = filenames.collect { filename ->
-        if (!project.file(filename).exists()) {
-            if (throwOnError) {
-                throw new GradleException("\nFile " + filename + " listed as a resolved dependency in " + savedFile + " does not exist!\n\nFor more information, see b/187075069")
-            } else {
-                valid = false
-            }
-        }
-        project.dependencies.create(project.files(filename))
-    }
-    if (!valid) {
-        return null
-    }
-    return dependencies
-}
-
-// Resolves a Configuration into a list of Dependency objects
-def resolveConfiguration(configuration) {
-    def resolvedName = configuration.name
-    def cacheDir = new File(project.buildDir, "/" + resolvedName)
-    def inputsFile = new File(cacheDir, "/deps")
-    def outputsFile = new File(cacheDir, "/result")
-
-    def inputText = fingerprintConfiguration(configuration)
-    def parsed = null
-    if (inputsFile.exists() && inputsFile.text == inputText) {
-        // Try to parse the previously resolved configuration, but don't give up if it mentions a
-        // nonexistent file. If something has since deleted one of the referenced files, we will
-        // try to reresolve that file later
-        parsed = parseConfigurationResolution(outputsFile, false)
-    }
-    // If the configuration has changed or if any of its files have been deleted, reresolve it
-    if (parsed == null) {
-        cacheDir.mkdirs()
-        saveConfigurationResolution(configuration, outputsFile)
-        inputsFile.text = inputText
-        // confirm that the resolved configuration parses successfully
-        parsed = parseConfigurationResolution(outputsFile, true)
-    }
-    return parsed
-}
-
-// Computes a unique string from a Configuration based on its dependencies
-// This is used for up-to-date checks
-def fingerprintConfiguration(configuration) {
-    def dependencies = configuration.allDependencies
-    def dependencyTexts = dependencies.collect { dep -> dep.group + ":" + dep.name + ":" + dep.version }
-    return dependencyTexts.join("\n")
-}
-
-// Imports the contents of fromConf into toConf
-// Uses caching to often short-circuit the resolution of fromConf
-def loadConfigurationQuicklyInto(fromConf, toConf) {
-    def resolved = resolveConfiguration(fromConf)
-    resolved.each { dep ->
-        project.dependencies.add(toConf.name, dep)
-    }
-}
-
-loadConfigurationQuicklyInto(configurations.cacheableApi, configurations.api)
-loadConfigurationQuicklyInto(configurations.cacheableImplementation, configurations.implementation)
-loadConfigurationQuicklyInto(configurations.cacheableRuntimeOnly, configurations.runtimeOnly)
-
-project.tasks.withType(Jar) { task ->
-    task.reproducibleFileOrder = true
-    task.preserveFileTimestamps = false
+  api(project("plugins"))
 }
diff --git a/buildSrc/plugins/build.gradle b/buildSrc/plugins/build.gradle
new file mode 100644
index 0000000..6779c30
--- /dev/null
+++ b/buildSrc/plugins/build.gradle
@@ -0,0 +1 @@
+apply from: "../shared.gradle"
diff --git a/buildSrc/private/build.gradle b/buildSrc/private/build.gradle
new file mode 100644
index 0000000..6779c30
--- /dev/null
+++ b/buildSrc/private/build.gradle
@@ -0,0 +1 @@
+apply from: "../shared.gradle"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 44c3b9f..eba54c5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -16,13 +16,13 @@
 
 package androidx.build
 
+import androidx.build.Multiplatform.Companion.isMultiplatformEnabled
 import androidx.build.checkapi.shouldConfigureApiTasks
 import groovy.lang.Closure
 import org.gradle.api.GradleException
 import org.gradle.api.Project
 import org.gradle.api.provider.Property
 import java.util.ArrayList
-
 /**
  * Extension for [AndroidXImplPlugin] that's responsible for holding configuration options.
  */
@@ -159,7 +159,13 @@
 
     var benchmarkRunAlsoInterpreted = false
 
-    var multiplatform = false
+    var multiplatform: Boolean
+        set(value) {
+            Multiplatform.setEnabledForProject(project, value)
+        }
+        get() {
+            return project.isMultiplatformEnabled()
+        }
 
     fun shouldEnforceKotlinStrictApiMode(): Boolean {
         return !legacyDisableKotlinStrictApiMode &&
diff --git a/buildSrc/public/README.md b/buildSrc/public/README.md
index 17062a3..a3e8122 100644
--- a/buildSrc/public/README.md
+++ b/buildSrc/public/README.md
@@ -1,3 +1,5 @@
-This is the :buildSrc:public project
+This directory contains code that other projects in this repository expect to be able to import and reference from their build.gradle files
 
-It contains code that other projects in this repository expect to be able to import and reference from their build.gradle files
+The files in this directory are used by the buildSrc:plugins and buildSrc:private projects.
+
+The files in this directory are essentially a project and can be turned into a real Gradle project if needed; at the moment, they are simply included in the corresponding builds because that runs more quickly.
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt b/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt
index c422169..b3f1195 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/Multiplatform.kt
@@ -17,8 +17,8 @@
 package androidx.build
 
 import androidx.build.COMPOSE_MPP_ENABLED
-import androidx.build.AndroidXExtension
 import org.gradle.api.Project
+import org.gradle.kotlin.dsl.extra
 
 /**
  * Setting this property enables multiplatform builds of Compose
@@ -27,14 +27,12 @@
 
 class Multiplatform {
     companion object {
-        @JvmStatic
         fun Project.isMultiplatformEnabled(): Boolean {
-            return properties.get(COMPOSE_MPP_ENABLED)?.toString()?.toBoolean()
-                ?: androidxExtension()?.multiplatform ?: false
+            return properties.get(COMPOSE_MPP_ENABLED)?.toString()?.toBoolean() ?: false
         }
 
-        private fun Project.androidxExtension(): AndroidXExtension? {
-            return extensions.findByType(AndroidXExtension::class.java)
+        fun setEnabledForProject(project: Project, enabled: Boolean) {
+            project.extra.set(COMPOSE_MPP_ENABLED, enabled)
         }
     }
 }
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt b/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
index 00d00a3..155a1d9 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SdkHelper.kt
@@ -54,7 +54,7 @@
  * Returns the root project's platform-specific SDK path as a file.
  */
 fun Project.getSdkPath(): File {
-    if (rootProject.plugins.hasPlugin(AndroidXPlaygroundRootPlugin::class.java) ||
+    if (rootProject.plugins.hasPlugin("androix.build.AndroidXPlaygroundRootImplPlugin") ||
         System.getenv("COMPOSE_DESKTOP_GITHUB_BUILD") != null
     ) {
         // This is not full checkout, use local settings instead.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SingleFileCopy.kt b/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
similarity index 100%
rename from buildSrc/private/src/main/kotlin/androidx/build/SingleFileCopy.kt
rename to buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
index 28bcb0f..3d32906 100644
--- a/buildSrc/settings.gradle
+++ b/buildSrc/settings.gradle
@@ -15,6 +15,8 @@
  */
 
 include ":jetpad-integration"
+include ":plugins"
+include ":private"
 
 enableFeaturePreview("VERSION_CATALOGS")
 
diff --git a/buildSrc/shared.gradle b/buildSrc/shared.gradle
new file mode 100644
index 0000000..a82ce62
--- /dev/null
+++ b/buildSrc/shared.gradle
@@ -0,0 +1,166 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+apply plugin: "kotlin"
+apply from: "../kotlin-dsl-dependency.gradle"
+
+buildscript {
+    project.ext.supportRootFolder = project.projectDir.getParentFile().getParentFile()
+    apply from: "../repos.gradle"
+    repos.addMavenRepositories(repositories)
+    dependencies {
+        classpath(libs.kotlinGradlePluginz)
+    }
+}
+
+configurations {
+    // Dependencies added to these configurations get copied into the corresponding configuration
+    // (cacheableApi gets copied into api, etc).
+    // Because we cache the resolutions of these configurations, performance is faster when
+    // artifacts are put into these configurations than when those artifacts are put into their
+    // corresponding configuration.
+    cacheableApi
+    cacheableImplementation {
+        extendsFrom(project.configurations.cacheableApi)
+    }
+    cacheableRuntimeOnly
+}
+
+dependencies {
+    cacheableApi(libs.androidGradlePluginz)
+    cacheableImplementation(libs.dexMemberList)
+    cacheableApi(libs.kotlinGradlePluginz)
+    cacheableImplementation(gradleApi())
+    cacheableApi(libs.dokkaGradlePluginz)
+    // needed by inspection plugin
+    cacheableImplementation(libs.protobufGradlePluginz)
+    cacheableImplementation(libs.wireGradlePluginz)
+    cacheableImplementation(libs.shadow)
+    // dependencies that aren't used by buildSrc directly but that we resolve here so that the
+    // root project doesn't need to re-resolve them and their dependencies on every build
+    cacheableRuntimeOnly(libs.hiltAndroidGradlePluginz)
+    // room kotlintestapp uses the ksp plugin but it does not publish a plugin marker yet
+    cacheableApi(libs.kspGradlePluginz)
+    cacheableApi(libs.japicmpPluginz)
+    // dependencies whose resolutions we don't need to cache
+    compileOnly(findGradleKotlinDsl()) // Only one file in this configuration, no need to cache it
+    implementation(project(":jetpad-integration")) // Doesn't have a .pom, so not slow to load
+}
+
+// Exclude dokka coming from AGP. We don't need it and it conflicts with dackka: b/195305339
+configurations.configureEach { conf ->
+  conf.exclude(group:"org.jetbrains.dokka", module:"dokka-core")
+}
+
+apply plugin: "java-gradle-plugin"
+
+sourceSets {
+    main.java.srcDirs += "../public/src/main/kotlin"
+    main.resources.srcDirs += "../public/src/main/resources"
+
+
+    main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
+    main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
+
+    main.java.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main/kotlin"
+    main.resources.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main" +
+            "/resources"
+
+    main.java.srcDirs += "${supportRootFolder}/compose/material/material/icons/generator/src/main" +
+            "/kotlin"
+}
+
+gradlePlugin {
+    plugins {
+        benchmark {
+            id = "androidx.benchmark"
+            implementationClass = "androidx.benchmark.gradle.BenchmarkPlugin"
+        }
+        inspection {
+            id = "androidx.inspection"
+            implementationClass = "androidx.inspection.gradle.InspectionPlugin"
+        }
+    }
+}
+
+// Saves configuration into destFile
+// Each line of destFile will be the absolute filepath of one of the files in configuration
+def saveConfigurationResolution(configuration, destFile) {
+    def resolvedConfiguration = configuration.resolvedConfiguration
+    def files = resolvedConfiguration.files
+    def paths = files.collect { f -> f.toString() }
+    def serialized = paths.join("\n")
+    destFile.text = serialized
+}
+
+// Parses a file into a list of Dependency objects representing a ResolvedConfiguration
+def parseConfigurationResolution(savedFile, throwOnError) {
+    def savedText = savedFile.text
+    def filenames = savedText.split("\n")
+    def valid = true
+    def dependencies = filenames.collect { filename ->
+        if (!project.file(filename).exists()) {
+            if (throwOnError) {
+                throw new GradleException("\nFile " + filename + " listed as a resolved dependency in " + savedFile + " does not exist!\n\nFor more information, see b/187075069")
+            } else {
+                valid = false
+            }
+        }
+        project.dependencies.create(project.files(filename))
+    }
+    if (!valid) {
+        return null
+    }
+    return dependencies
+}
+
+// Resolves a Configuration into a list of Dependency objects
+def resolveConfiguration(configuration) {
+    def resolvedName = configuration.name
+    def cacheDir = new File(project.buildDir, "/" + resolvedName)
+    def inputsFile = new File(cacheDir, "/deps")
+    def outputsFile = new File(cacheDir, "/result")
+
+    def inputText = fingerprintConfiguration(configuration)
+    def parsed = null
+    if (inputsFile.exists() && inputsFile.text == inputText) {
+        // Try to parse the previously resolved configuration, but don't give up if it mentions a
+        // nonexistent file. If something has since deleted one of the referenced files, we will
+        // try to reresolve that file later
+        parsed = parseConfigurationResolution(outputsFile, false)
+    }
+    // If the configuration has changed or if any of its files have been deleted, reresolve it
+    if (parsed == null) {
+        cacheDir.mkdirs()
+        saveConfigurationResolution(configuration, outputsFile)
+        inputsFile.text = inputText
+        // confirm that the resolved configuration parses successfully
+        parsed = parseConfigurationResolution(outputsFile, true)
+    }
+    return parsed
+}
+
+// Computes a unique string from a Configuration based on its dependencies
+// This is used for up-to-date checks
+def fingerprintConfiguration(configuration) {
+    def dependencies = configuration.allDependencies
+    def dependencyTexts = dependencies.collect { dep -> dep.group + ":" + dep.name + ":" + dep.version }
+    return dependencyTexts.join("\n")
+}
+
+// Imports the contents of fromConf into toConf
+// Uses caching to often short-circuit the resolution of fromConf
+def loadConfigurationQuicklyInto(fromConf, toConf) {
+    def resolved = resolveConfiguration(fromConf)
+    resolved.each { dep ->
+        project.dependencies.add(toConf.name, dep)
+    }
+}
+
+loadConfigurationQuicklyInto(configurations.cacheableApi, configurations.api)
+loadConfigurationQuicklyInto(configurations.cacheableImplementation, configurations.implementation)
+loadConfigurationQuicklyInto(configurations.cacheableRuntimeOnly, configurations.runtimeOnly)
+
+project.tasks.withType(Jar) { task ->
+    task.reproducibleFileOrder = true
+    task.preserveFileTimestamps = false
+}
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index c6f85d4..0379226 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -34,6 +34,15 @@
 
 import static androidx.build.dependencies.DependenciesKt.*
 
+buildscript {
+    dependencies {
+        // This dependency means that tasks in this project might become out-of-date whenever
+        // certain classes in buildSrc change, and should generally be avoided.
+        // See b/140265324 for more information
+        classpath(project.files("${project.rootProject.ext["outDir"]}/buildSrc/private/build/libs/private.jar"))
+    }
+}
+
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")