1 /*
<lambda>null2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.build
18 
19 import androidx.benchmark.gradle.BenchmarkPlugin
20 import androidx.build.AndroidXImplPlugin.Companion.TASK_TIMEOUT_MINUTES
21 import androidx.build.Release.DEFAULT_PUBLISH_CONFIG
22 import androidx.build.buildInfo.addCreateLibraryBuildInfoFileTasks
23 import androidx.build.checkapi.AndroidMultiplatformApiTaskConfig
24 import androidx.build.checkapi.JavaApiTaskConfig
25 import androidx.build.checkapi.KmpApiTaskConfig
26 import androidx.build.checkapi.LibraryApiTaskConfig
27 import androidx.build.checkapi.configureProjectForApiTasks
28 import androidx.build.docs.CheckTipOfTreeDocsTask.Companion.setUpCheckDocsTask
29 import androidx.build.gitclient.getHeadShaProvider
30 import androidx.build.gradle.isRoot
31 import androidx.build.kythe.configureProjectForKzipTasks
32 import androidx.build.license.addLicensesToPublishedArtifacts
33 import androidx.build.resources.CopyPublicResourcesDirTask
34 import androidx.build.resources.configurePublicResourcesStub
35 import androidx.build.sbom.configureSbomPublishing
36 import androidx.build.sbom.validateAllArchiveInputsRecognized
37 import androidx.build.sources.configureMultiplatformSourcesForAndroid
38 import androidx.build.sources.configureSourceJarForAndroid
39 import androidx.build.sources.configureSourceJarForJava
40 import androidx.build.sources.configureSourceJarForMultiplatform
41 import androidx.build.sources.registerValidateMultiplatformSourceSetNamingTask
42 import androidx.build.studio.StudioTask
43 import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
44 import androidx.build.testConfiguration.addToModuleInfo
45 import androidx.build.testConfiguration.configureTestConfigGeneration
46 import androidx.build.transform.configureAarAsJarForConfiguration
47 import androidx.build.uptodatedness.TaskUpToDateValidator
48 import androidx.build.uptodatedness.cacheEvenIfNoOutputs
49 import com.android.build.api.artifact.SingleArtifact
50 import com.android.build.api.attributes.BuildTypeAttr
51 import com.android.build.api.dsl.ApplicationExtension
52 import com.android.build.api.dsl.CommonExtension
53 import com.android.build.api.dsl.KotlinMultiplatformAndroidDeviceTestCompilation
54 import com.android.build.api.dsl.KotlinMultiplatformAndroidHostTestCompilation
55 import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
56 import com.android.build.api.dsl.LibraryExtension
57 import com.android.build.api.dsl.PrivacySandboxSdkExtension
58 import com.android.build.api.dsl.TestBuildType
59 import com.android.build.api.dsl.TestExtension
60 import com.android.build.api.variant.AndroidComponentsExtension
61 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
62 import com.android.build.api.variant.HasDeviceTests
63 import com.android.build.api.variant.HasUnitTestBuilder
64 import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
65 import com.android.build.api.variant.LibraryAndroidComponentsExtension
66 import com.android.build.api.variant.Variant
67 import com.android.build.gradle.AppPlugin
68 import com.android.build.gradle.LibraryPlugin
69 import com.android.build.gradle.TestPlugin
70 import com.android.build.gradle.api.KotlinMultiplatformAndroidPlugin
71 import com.android.build.gradle.api.PrivacySandboxSdkPlugin
72 import com.android.utils.appendCapitalized
73 import com.google.devtools.ksp.gradle.KspExtension
74 import com.google.devtools.ksp.gradle.KspGradleSubplugin
75 import com.google.protobuf.gradle.ProtobufExtension
76 import com.google.protobuf.gradle.ProtobufPlugin
77 import java.io.File
78 import java.time.Duration
79 import java.util.Locale
80 import javax.inject.Inject
81 import org.gradle.api.DefaultTask
82 import org.gradle.api.GradleException
83 import org.gradle.api.JavaVersion
84 import org.gradle.api.JavaVersion.VERSION_11
85 import org.gradle.api.JavaVersion.VERSION_17
86 import org.gradle.api.JavaVersion.VERSION_1_8
87 import org.gradle.api.Plugin
88 import org.gradle.api.Project
89 import org.gradle.api.Task
90 import org.gradle.api.artifacts.CacheableRule
91 import org.gradle.api.artifacts.ComponentMetadataContext
92 import org.gradle.api.artifacts.ComponentMetadataRule
93 import org.gradle.api.artifacts.Configuration
94 import org.gradle.api.attributes.Category
95 import org.gradle.api.attributes.Usage
96 import org.gradle.api.component.SoftwareComponentFactory
97 import org.gradle.api.configuration.BuildFeatures
98 import org.gradle.api.file.DuplicatesStrategy
99 import org.gradle.api.plugins.JavaPlugin
100 import org.gradle.api.plugins.JavaPluginExtension
101 import org.gradle.api.provider.Provider
102 import org.gradle.api.tasks.Copy
103 import org.gradle.api.tasks.TaskProvider
104 import org.gradle.api.tasks.bundling.Zip
105 import org.gradle.api.tasks.compile.JavaCompile
106 import org.gradle.api.tasks.javadoc.Javadoc
107 import org.gradle.api.tasks.testing.AbstractTestTask
108 import org.gradle.api.tasks.testing.Test
109 import org.gradle.api.tasks.testing.logging.TestExceptionFormat
110 import org.gradle.api.tasks.testing.logging.TestLogEvent
111 import org.gradle.build.event.BuildEventsListenerRegistry
112 import org.gradle.jvm.tasks.Jar
113 import org.gradle.kotlin.dsl.create
114 import org.gradle.kotlin.dsl.dependencies
115 import org.gradle.kotlin.dsl.findByType
116 import org.gradle.kotlin.dsl.getByType
117 import org.gradle.kotlin.dsl.named
118 import org.gradle.kotlin.dsl.withModule
119 import org.gradle.kotlin.dsl.withType
120 import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin
121 import org.gradle.plugin.devel.tasks.ValidatePlugins
122 import org.gradle.process.CommandLineArgumentProvider
123 import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
124 import org.jetbrains.kotlin.gradle.dsl.JvmTarget
125 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
126 import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
127 import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
128 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
129 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
130 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
131 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
132 import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
133 import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
134 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
135 import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
136 
137 /**
138  * A plugin which enables all of the Gradle customizations for AndroidX. This plugin reacts to other
139  * plugins being added and adds required and optional functionality.
140  */
141 abstract class AndroidXImplPlugin
142 @Inject
143 constructor(private val componentFactory: SoftwareComponentFactory) : Plugin<Project> {
144     @get:Inject abstract val registry: BuildEventsListenerRegistry
145     @get:Inject abstract val buildFeatures: BuildFeatures
146 
147     override fun apply(project: Project) {
148         if (project.isRoot)
149             throw Exception("Root project should use AndroidXRootImplPlugin instead")
150         val androidXExtension = initializeAndroidXExtension(project)
151 
152         val androidXKmpExtension =
153             project.extensions.create<AndroidXMultiplatformExtension>(
154                 AndroidXMultiplatformExtension.EXTENSION_NAME,
155                 project
156             )
157 
158         project.tasks.register(BUILD_ON_SERVER_TASK, DefaultTask::class.java)
159         // Perform different actions based on which plugins have been applied to the project.
160         // Many of the actions overlap, ex. API tracking.
161         project.plugins.configureEach { plugin ->
162             // PrivacySandboxSdkPlugin b/397703898, KotlinMultiplatformAndroidPlugin b/393137152
163             @Suppress("UnstableApiUsage")
164             when (plugin) {
165                 is JavaGradlePluginPlugin -> configureGradlePluginPlugin(project)
166                 is JavaPlugin -> configureWithJavaPlugin(project, androidXExtension)
167                 is LibraryPlugin -> configureWithLibraryPlugin(project, androidXExtension)
168                 is AppPlugin -> configureWithAppPlugin(project, androidXExtension)
169                 is TestPlugin -> configureWithTestPlugin(project, androidXExtension)
170                 is KspGradleSubplugin -> configureWithKspPlugin(project, androidXExtension)
171                 is KotlinMultiplatformAndroidPlugin ->
172                     configureWithKotlinMultiplatformAndroidPlugin(
173                         project,
174                         androidXKmpExtension.agpKmpExtension,
175                         androidXExtension
176                     )
177                 is KotlinBasePluginWrapper ->
178                     configureWithKotlinPlugin(
179                         project,
180                         androidXExtension,
181                         plugin,
182                         androidXKmpExtension
183                     )
184                 is PrivacySandboxSdkPlugin -> configureWithPrivacySandboxSdkPlugin(project)
185                 is ProtobufPlugin -> configureProtobufPlugin(project)
186             }
187         }
188 
189         project.configureLint()
190         project.configureKtfmt()
191         project.configureKotlinVersion()
192         project.configureJavaFormat()
193 
194         // Avoid conflicts between full Guava and LF-only Guava.
195         project.configureGuavaUpgradeHandler()
196 
197         // Configure all Jar-packing tasks for hermetic builds.
198         project.tasks.withType(Zip::class.java).configureEach { it.configureForHermeticBuild() }
199         project.tasks.withType(Copy::class.java).configureEach { it.configureForHermeticBuild() }
200 
201         val allHostTests = project.tasks.register("allHostTests")
202         // copy host side test results to DIST
203         project.tasks.withType(AbstractTestTask::class.java) { task ->
204             configureTestTask(project, task, allHostTests, androidXExtension)
205         }
206 
207         project.tasks.withType(Test::class.java).configureEach { task ->
208             configureJvmTestTask(project, task)
209         }
210 
211         project.configureTaskTimeouts()
212         project.configureMavenArtifactUpload(
213             androidXExtension,
214             androidXKmpExtension,
215             componentFactory
216         ) {
217             if (buildFeatures.isIsolatedProjectsEnabled()) return@configureMavenArtifactUpload
218             project.addCreateLibraryBuildInfoFileTasks(androidXExtension, androidXKmpExtension)
219         }
220         project.publishInspectionArtifacts()
221         project.configureProjectStructureValidation(androidXExtension)
222         project.configureProjectVersionValidation(androidXExtension)
223         project.validateMultiplatformPluginHasNotBeenApplied()
224 
225         project.tasks.register("printCoordinates", PrintProjectCoordinatesTask::class.java) {
226             it.configureWithAndroidXExtension(androidXExtension)
227         }
228         project.configureConstraintsWithinGroup(androidXExtension)
229         project.validateProjectParser(androidXExtension)
230         project.validateAllArchiveInputsRecognized()
231         project.afterEvaluate {
232             if (androidXExtension.shouldPublishSbom()) {
233                 project.configureSbomPublishing()
234             }
235             if (androidXExtension.shouldPublish()) {
236                 project.validatePublishedMultiplatformHasDefault()
237                 project.addLicensesToPublishedArtifacts(androidXExtension.license)
238                 project.registerValidateRelocatedDependenciesTask()
239             }
240             project.registerValidateMultiplatformSourceSetNamingTask()
241             project.validateLintVersionTestExists(androidXExtension)
242         }
243         project.disallowAccidentalAndroidDependenciesInKmpProject(androidXKmpExtension)
244         TaskUpToDateValidator.setup(project, registry)
245 
246         project.workaroundPrebuiltTakingPrecedenceOverProject()
247         project.configureSamplesProject()
248         project.configureMaxDepVersions(androidXExtension)
249         project.configureUnzipChromeBuildService()
250 
251         project.configureDependencyAnalysisPlugin()
252     }
253 
254     private fun initializeAndroidXExtension(project: Project): AndroidXExtension {
255         val versionService = LibraryVersionsService.registerOrGet(project).get()
256         val listProjectsService = ListProjectsService.registerOrGet(project)
257         return project.extensions
258             .create<AndroidXExtension>(
259                 EXTENSION_NAME,
260                 project,
261                 versionService.libraryVersions,
262                 versionService.libraryGroups.values.toList(),
263                 versionService.libraryGroupsByGroupId,
264                 versionService.overrideLibraryGroupsByProjectPath,
265                 listProjectsService.map { it.allPossibleProjects },
266                 { getHeadShaProvider(project) },
267                 { configurationName: String ->
268                     configureAarAsJarForConfiguration(project, configurationName)
269                 }
270             )
271             .apply {
272                 kotlinTarget.set(
273                     if (project.shouldForceKotlin20Target().get()) KotlinTarget.KOTLIN_2_0
274                     else KotlinTarget.DEFAULT
275                 )
276             }
277     }
278 
279     /**
280      * Disables timestamps and ensures filesystem-independent archive ordering to maximize
281      * cross-machine byte-for-byte reproducibility of artifacts.
282      */
283     private fun Zip.configureForHermeticBuild() {
284         isReproducibleFileOrder = true
285         isPreserveFileTimestamps = false
286     }
287 
288     private fun Copy.configureForHermeticBuild() {
289         duplicatesStrategy = DuplicatesStrategy.FAIL
290     }
291 
292     private fun configureJvmTestTask(project: Project, task: Test) {
293         // Robolectric 1.7 increased heap size requirements, see b/207169653.
294         task.maxHeapSize = "3g"
295 
296         // For non-playground setup use robolectric offline
297         if (!ProjectLayoutType.isPlayground(project)) {
298             task.systemProperty("robolectric.offline", "true")
299             val robolectricDependencies =
300                 File(
301                     project.getPrebuiltsRoot(),
302                     "androidx/external/org/robolectric/android-all-instrumented"
303                 )
304             task.systemProperty(
305                 "robolectric.dependency.dir",
306                 robolectricDependencies.relativeTo(project.projectDir)
307             )
308         }
309     }
310 
311     private fun configureTestTask(
312         project: Project,
313         task: AbstractTestTask,
314         anchorTask: TaskProvider<Task>,
315         androidXExtension: AndroidXExtension,
316     ) {
317         anchorTask.configure { it.dependsOn(task) }
318         val ignoreFailuresProperty =
319             project.providers.gradleProperty(TEST_FAILURES_DO_NOT_FAIL_TEST_TASK)
320         val ignoreFailures = ignoreFailuresProperty.isPresent
321         if (ignoreFailures) {
322             task.ignoreFailures = true
323         }
324         task.inputs.property("ignoreFailures", ignoreFailures)
325 
326         val xmlReportDestDir = project.getHostTestResultDirectory()
327         val testName = "${project.path}:${task.name}"
328         project.addToModuleInfo(testName, buildFeatures.isIsolatedProjectsEnabled())
329         androidXExtension.testModuleNames.add(testName)
330         val archiveName = "$testName.zip"
331         if (project.isDisplayTestOutput()) {
332             // Enable tracing to see results in command line
333             task.testLogging.apply {
334                 events =
335                     hashSetOf(TestLogEvent.FAILED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT)
336                 showExceptions = true
337                 showCauses = true
338                 showStackTraces = true
339                 exceptionFormat = TestExceptionFormat.FULL
340             }
341         } else {
342             task.testLogging.apply {
343                 showExceptions = false
344                 // Disable all output, including the names of the failing tests, by specifying
345                 // that the minimum granularity we're interested in is this very high number
346                 // (which is higher than the current maximum granularity that Gradle offers (3))
347                 minGranularity = 1000
348             }
349             val testTaskName = task.name
350             val capitalizedTestTaskName =
351                 testTaskName.replaceFirstChar {
352                     if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
353                 }
354             val xmlReport = task.reports.junitXml
355             if (xmlReport.required.get()) {
356                 val zipXmlTask =
357                     project.tasks.register(
358                         "zipXmlResultsOf$capitalizedTestTaskName",
359                         Zip::class.java
360                     ) {
361                         it.destinationDirectory.set(xmlReportDestDir)
362                         it.archiveFileName.set(archiveName)
363                         it.from(project.file(xmlReport.outputLocation))
364                         it.include("*.xml")
365                     }
366                 task.finalizedBy(zipXmlTask)
367             }
368         }
369     }
370 
371     /** Configures the project to use the Kotlin version specified by `androidx.kotlinTarget`. */
372     private fun Project.configureKotlinVersion() {
373         val kotlinVersionStringProvider = androidXConfiguration.kotlinBomVersion
374 
375         // Resolve unspecified Kotlin versions to the target version.
376         configurations.configureEach { configuration ->
377             configuration.resolutionStrategy { strategy ->
378                 strategy.eachDependency { details ->
379                     if (
380                         details.requested.group == "org.jetbrains.kotlin" &&
381                             details.requested.version == null
382                     ) {
383                         details.useVersion(kotlinVersionStringProvider.get())
384                     }
385                 }
386             }
387         }
388 
389         fun Provider<String>.toKotlinVersionProvider() = map { version ->
390             KotlinVersion.fromVersion(version.substringBeforeLast('.'))
391         }
392 
393         // Set the Kotlin compiler's API and language version to ensure bytecode is compatible.
394         val kotlinVersionProvider = kotlinVersionStringProvider.toKotlinVersionProvider()
395         tasks.configureEach { task ->
396             if (task is KotlinCompilationTask<*>) {
397                 task.compilerOptions.apiVersion.set(kotlinVersionProvider)
398                 task.compilerOptions.languageVersion.set(kotlinVersionProvider)
399             }
400         }
401 
402         // Specify coreLibrariesVersion for consumption by Kotlin Gradle Plugin. Note that KGP does
403         // not explicitly support varying the version between tasks/configurations for a given
404         // project, so this is not strictly correct. Picking the non-test (e.g. lower) value seems
405         // to work, though.
406         afterEvaluate { evaluatedProject ->
407             evaluatedProject.kotlinExtensionOrNull?.let { kotlinExtension ->
408                 kotlinExtension.coreLibrariesVersion = kotlinVersionStringProvider.get()
409             }
410             if (evaluatedProject.androidXExtension.shouldPublish()) {
411                 tasks.register(
412                     CheckKotlinApiTargetTask.TASK_NAME,
413                     CheckKotlinApiTargetTask::class.java
414                 ) {
415                     it.kotlinTarget.set(kotlinVersionProvider)
416                     it.outputFile.set(layout.buildDirectory.file("kotlinApiTargetCheckReport.txt"))
417                 }
418                 addToBuildOnServer(CheckKotlinApiTargetTask.TASK_NAME)
419             }
420         }
421 
422         // Resolve classpath conflicts caused by kotlin-stdlib-jdk7 and -jdk8 artifacts by amending
423         // the kotlin-stdlib artifact metadata to add same-version constraints.
424         project.dependencies {
425             components { componentMetadata ->
426                 componentMetadata.withModule<KotlinStdlibDependenciesRule>(
427                     "org.jetbrains.kotlin:kotlin-stdlib"
428                 )
429             }
430         }
431     }
432 
433     @CacheableRule
434     internal abstract class KotlinStdlibDependenciesRule : ComponentMetadataRule {
435         override fun execute(context: ComponentMetadataContext) {
436             val module = context.details.id
437             val version = module.version
438             context.details.allVariants { variantMetadata ->
439                 variantMetadata.withDependencyConstraints { constraintsMetadata ->
440                     val reason = "${module.name} is in atomic group ${module.group}"
441                     constraintsMetadata.add("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$version") {
442                         it.because(reason)
443                     }
444                     constraintsMetadata.add("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version") {
445                         it.because(reason)
446                     }
447                 }
448             }
449         }
450     }
451 
452     private fun configureWithKotlinPlugin(
453         project: Project,
454         androidXExtension: AndroidXExtension,
455         plugin: KotlinBasePluginWrapper,
456         androidXMultiplatformExtension: AndroidXMultiplatformExtension
457     ) {
458         val targetsAndroid =
459             project.provider {
460                 project.plugins.hasPlugin(LibraryPlugin::class.java) ||
461                     project.plugins.hasPlugin(AppPlugin::class.java) ||
462                     project.plugins.hasPlugin(TestPlugin::class.java) ||
463                     @Suppress("UnstableApiUsage")
464                     project.plugins.hasPlugin(KotlinMultiplatformAndroidPlugin::class.java)
465             }
466         val defaultJavaTargetVersion =
467             project.provider {
468                 getDefaultTargetJavaVersion(androidXExtension.type, project.name).toString()
469             }
470         val defaultJvmTarget = defaultJavaTargetVersion.map { JvmTarget.fromTarget(it) }
471         if (plugin is KotlinMultiplatformPluginWrapper) {
472             project.extensions.getByType<KotlinMultiplatformExtension>().apply {
473                 targets.withType<KotlinAndroidTarget> {
474                     compilations.configureEach {
475                         it.compileTaskProvider.configure { task ->
476                             task.compilerOptions.jvmTarget.set(defaultJvmTarget)
477                         }
478                     }
479                 }
480                 targets.withType(KotlinJvmTarget::class.java).configureEach { target ->
481                     val defaultTargetVersionForNonAndroidTargets =
482                         project.provider {
483                             getDefaultTargetJavaVersion(
484                                     softwareType = androidXExtension.type,
485                                     projectName = project.name,
486                                     targetName = target.name
487                                 )
488                                 .toString()
489                         }
490                     val defaultJvmTargetForNonAndroidTargets =
491                         defaultTargetVersionForNonAndroidTargets.map { JvmTarget.fromTarget(it) }
492                     target.compilations.configureEach { compilation ->
493                         compilation.compileJavaTaskProvider?.configure { javaCompile ->
494                             javaCompile.targetCompatibility =
495                                 defaultTargetVersionForNonAndroidTargets.get()
496                             javaCompile.sourceCompatibility =
497                                 defaultTargetVersionForNonAndroidTargets.get()
498                         }
499                         compilation.compileTaskProvider.configure { kotlinCompile ->
500                             kotlinCompile.compilerOptions {
501                                 jvmTarget.set(defaultJvmTargetForNonAndroidTargets)
502                                 // Set jdk-release version for non-Android KMP targets
503                                 freeCompilerArgs.add(
504                                     defaultTargetVersionForNonAndroidTargets.map {
505                                         "-Xjdk-release=$it"
506                                     }
507                                 )
508                             }
509                         }
510                     }
511                 }
512             }
513         } else {
514             project.tasks.withType(KotlinJvmCompile::class.java).configureEach { task ->
515                 task.compilerOptions.jvmTarget.set(defaultJvmTarget)
516                 task.compilerOptions.freeCompilerArgs.addAll(
517                     targetsAndroid.zip(defaultJavaTargetVersion) { targetsAndroid, version ->
518                         if (targetsAndroid) {
519                             emptyList<String>()
520                         } else {
521                             // Set jdk-release version for non-Android JVM projects
522                             listOf("-Xjdk-release=$version")
523                         }
524                     }
525                 )
526             }
527         }
528         project.tasks.withType(KotlinCompile::class.java).configureEach { task ->
529             val kotlinCompilerArgs =
530                 project.provider {
531                     val args =
532                         mutableListOf(
533                             "-Xskip-metadata-version-check",
534                             "-Xjvm-default=all",
535                             // These two args can be removed once kotlin 2.1 is used
536                             "-Xjspecify-annotations=strict",
537                             "-Xtype-enhancement-improvements-strict-mode",
538                         )
539                     if (androidXExtension.type.targetsKotlinConsumersOnly) {
540                         // The Kotlin Compiler adds intrinsic assertions which are only relevant
541                         // when the code is consumed by Java users. Therefore we can turn this off
542                         // when code is being consumed by Kotlin users.
543 
544                         // Additional Context:
545                         // https://github.com/JetBrains/kotlin/blob/master/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt#L239
546                         // b/280633711
547                         args +=
548                             listOf(
549                                 "-Xno-param-assertions",
550                                 "-Xno-call-assertions",
551                                 "-Xno-receiver-assertions"
552                             )
553                     }
554 
555                     args
556                 }
557             task.compilerOptions.freeCompilerArgs.addAll(kotlinCompilerArgs)
558         }
559         if (plugin is KotlinMultiplatformPluginWrapper) {
560             KonanPrebuiltsSetup.configureKonanDirectory(project)
561             project.afterEvaluate {
562                 val libraryExtension = project.extensions.findByType<LibraryExtension>()
563                 if (libraryExtension != null) {
564                     libraryExtension.configureAndroidLibraryWithMultiplatformPluginOptions()
565                 } else if (!androidXMultiplatformExtension.hasAndroidMultiplatform()) {
566                     // Kotlin MPP does not apply java plugin anymore, but we still want to configure
567                     // all java-related tasks.
568                     // We only need to do this when project does not have Android plugin, which
569                     // already
570                     // configures Java tasks.
571                     configureWithJavaPlugin(project, androidXExtension)
572                 }
573             }
574             project.configureKmp()
575             project.configureSourceJarForMultiplatform()
576 
577             // Disable any source JAR task(s) added by KotlinMultiplatformPlugin.
578             // https://youtrack.jetbrains.com/issue/KT-55881
579             project.tasks.withType(Jar::class.java).configureEach { jarTask ->
580                 if (jarTask.name == "jvmSourcesJar") {
581                     // We can't set duplicatesStrategy directly on the Jar task since it will get
582                     // overridden when the KotlinMultiplatformPlugin creates child specs, but we
583                     // can set it on a per-file basis.
584                     jarTask.eachFile { fileCopyDetails ->
585                         fileCopyDetails.duplicatesStrategy = DuplicatesStrategy.EXCLUDE
586                     }
587                 }
588             }
589         }
590 
591         project.afterEvaluate {
592             val kotlinExtension = project.kotlinExtensionOrNull
593             kotlinExtension?.explicitApi =
594                 if (androidXExtension.shouldEnforceKotlinStrictApiMode()) {
595                     ExplicitApiMode.Strict
596                 } else {
597                     ExplicitApiMode.Disabled
598                 }
599         }
600     }
601 
602     private fun configureWithAppPlugin(project: Project, androidXExtension: AndroidXExtension) {
603         project.extensions.getByType<ApplicationExtension>().apply {
604             configureAndroidBaseOptions(project, androidXExtension)
605             defaultConfig.targetSdk = project.defaultAndroidConfig.targetSdk
606             val debugSigningConfig = signingConfigs.getByName("debug")
607             // Use a local debug keystore to avoid build server issues.
608             debugSigningConfig.storeFile = project.getKeystore()
609             buildTypes.configureEach { buildType ->
610                 // Sign all the builds (including release) with debug key
611                 buildType.signingConfig = debugSigningConfig
612             }
613             configureAndroidApplicationOptions(project, androidXExtension)
614             excludeVersionFiles(packaging.resources)
615         }
616 
617         project.extensions.getByType<ApplicationAndroidComponentsExtension>().apply {
618             beforeVariants(selector().withBuildType("release")) { variant ->
619                 // Cast is needed because ApplicationAndroidComponentsExtension implements both
620                 // HasUnitTestBuilder and VariantBuilder, and VariantBuilder#enableUnitTest is
621                 // deprecated in favor of HasUnitTestBuilder#enableUnitTest.
622                 // Remove the cast when we upgrade to AGP 9.0.0
623                 (variant as HasUnitTestBuilder).enableUnitTest = false
624             }
625             onVariants {
626                 it.configureTests()
627                 it.configureLocalAsbSigning(project.getKeystore())
628             }
629         }
630 
631         project.configureJavaCompilationWarnings(
632             androidXExtension = androidXExtension,
633             isTestApp = true
634         )
635         project.buildOnServerDependsOnAssembleRelease()
636         project.buildOnServerDependsOnLint()
637     }
638 
639     private fun configureWithTestPlugin(project: Project, androidXExtension: AndroidXExtension) {
640         project.extensions.getByType<TestExtension>().apply {
641             configureAndroidBaseOptions(project, androidXExtension)
642             defaultConfig.targetSdk = project.defaultAndroidConfig.targetSdk
643             val debugSigningConfig = signingConfigs.getByName("debug")
644             // Use a local debug keystore to avoid build server issues.
645             debugSigningConfig.storeFile = project.getKeystore()
646             buildTypes.configureEach { buildType ->
647                 // Sign all the builds (including release) with debug key
648                 buildType.signingConfig = debugSigningConfig
649             }
650             project.configureTestConfigGeneration(
651                 this,
652                 androidXExtension.isIsolatedProjectsEnabled()
653             )
654             project.addAppApkToTestConfigGeneration(androidXExtension)
655             excludeVersionFiles(packaging.resources)
656         }
657 
658         project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
659             onVariants { it.configureLocalAsbSigning(project.getKeystore()) }
660         }
661         project.configureJavaCompilationWarnings(androidXExtension)
662     }
663 
664     private fun configureWithKspPlugin(project: Project, androidXExtension: AndroidXExtension) =
665         project.extensions.getByType<KspExtension>().apply {
666             useKsp2.set(
667                 androidXExtension.kotlinTarget.map {
668                     it.apiVersion == KotlinVersion.KOTLIN_2_0 ||
669                         it.apiVersion == KotlinVersion.KOTLIN_2_1
670                 }
671             )
672         }
673 
674     private fun configureWithKotlinMultiplatformAndroidPlugin(
675         project: Project,
676         kotlinMultiplatformAndroidTarget: KotlinMultiplatformAndroidLibraryTarget,
677         androidXExtension: AndroidXExtension
678     ) {
679         val kotlinMultiplatformAndroidComponentsExtension =
680             project.extensions.getByType<KotlinMultiplatformAndroidComponentsExtension>()
681         kotlinMultiplatformAndroidTarget.configureAndroidBaseOptions(
682             project,
683             kotlinMultiplatformAndroidComponentsExtension
684         )
685         kotlinMultiplatformAndroidComponentsExtension.apply {
686             finalizeDsl {
687                 // Propagate the compileSdk value into minCompileSdk. Must be done after the DSL in
688                 // build.gradle files (that sets compileSdk in the first place) is evaluated.
689 
690                 kotlinMultiplatformAndroidTarget.aarMetadata.minCompileSdk =
691                     kotlinMultiplatformAndroidTarget.compileSdk
692 
693                 project.setUpBlankProguardFileForKmpAarIfNeeded(
694                     kotlinMultiplatformAndroidTarget.optimization.consumerKeepRules
695                 )
696             }
697         }
698         project.disableStrictVersionConstraints()
699 
700         project.configureProjectForApiTasks(AndroidMultiplatformApiTaskConfig, androidXExtension)
701         project.configureProjectForKzipTasks(AndroidMultiplatformApiTaskConfig, androidXExtension)
702 
703         kotlinMultiplatformAndroidComponentsExtension.onVariant { variant ->
704             variant.hostTests.forEach { (_, hostTest) ->
705                 hostTest.configureTestTask { it.configureForRobolectric() }
706             }
707             variant.configureTests()
708 
709             project.configureMultiplatformSourcesForAndroid(
710                 variant.name,
711                 kotlinMultiplatformAndroidTarget,
712                 androidXExtension.samplesProjects
713             )
714 
715             project.validateKotlinModuleFiles(
716                 variant.name,
717                 variant.artifacts.get(SingleArtifact.AAR)
718             )
719         }
720 
721         project.configurePublicResourcesStub(project.multiplatformExtension!!)
722         project.configureVersionFileWriter(project.multiplatformExtension!!, androidXExtension)
723         project.configureJavaCompilationWarnings(androidXExtension)
724 
725         project.configureDependencyVerification(androidXExtension) { taskProvider ->
726             kotlinMultiplatformAndroidTarget.compilations.configureEach {
727                 taskProvider.configure { task -> task.dependsOn(it.compileTaskProvider) }
728             }
729         }
730         project.afterEvaluate {
731             project.addToBuildOnServer("assembleAndroidMain")
732             project.addToBuildOnServer("lint")
733             // Created to be consumed by docs-tip-of-tree
734             project.configurations.create("androidIntermediates") {
735                 it.isVisible = false
736                 it.isCanBeResolved = false
737                 it.attributes.attribute(
738                     Usage.USAGE_ATTRIBUTE,
739                     project.objects.named(Usage.JAVA_RUNTIME)
740                 )
741                 it.attributes.attribute(
742                     Category.CATEGORY_ATTRIBUTE,
743                     project.objects.named<Category>(Category.LIBRARY)
744                 )
745                 it.attributes.attribute(
746                     BuildTypeAttr.ATTRIBUTE,
747                     project.objects.named<BuildTypeAttr>("release")
748                 )
749                 it.outgoing.artifact(project.tasks.named("createFullJarAndroidMain"))
750             }
751         }
752         project.setUpCheckDocsTask(androidXExtension)
753         project.writeBlankPublicTxtToAar(kotlinMultiplatformAndroidComponentsExtension)
754     }
755 
756     private fun Project.writeBlankPublicTxtToAar(
757         componentsExtension: KotlinMultiplatformAndroidComponentsExtension
758     ) {
759         val blankPublicResourceDir =
760             project.getSupportRootFolder().resolve("buildSrc/blank-res-api")
761         componentsExtension.onVariant { variant ->
762             val taskProvider =
763                 tasks.register(
764                     "repackageAarWithResourceApi".appendCapitalized(variant.name),
765                     RepackagingTask::class.java
766                 ) { task ->
767                     task.from(blankPublicResourceDir)
768                     task.from(zipTree(task.aarFile))
769                     task.destinationDirectory.fileProvider(
770                         task.output.locationOnly.map { location -> location.asFile.parentFile }
771                     )
772                     task.archiveFileName.set(
773                         task.output.locationOnly.map { location -> location.asFile.name }
774                     )
775                 }
776             variant.artifacts
777                 .use(taskProvider)
778                 .wiredWithFiles(RepackagingTask::aarFile, RepackagingTask::output)
779                 .toTransform(SingleArtifact.AAR)
780         }
781     }
782 
783     private fun configureProtobufPlugin(project: Project) {
784         project.extensions.getByType(ProtobufExtension::class.java).apply {
785             protoc { it.artifact = project.getLibraryByName("protobufCompiler").toString() }
786             generateProtoTasks {
787                 it.all().configureEach { task ->
788                     // java projects have "java" output enabled, however Android projects do not
789                     // so we need to create it for Android projects.
790                     // https://github.com/google/protobuf-gradle-plugin?tab=readme-ov-file#default-outputs
791                     val java =
792                         if (
793                             project.plugins.hasPlugin("com.android.library") ||
794                                 project.plugins.hasPlugin("com.android.application")
795                         ) {
796                             task.builtins.register("java")
797                         } else task.builtins.named("java")
798                     java.configure { options -> options.option("lite") }
799                 }
800             }
801         }
802     }
803 
804     @Suppress("UnstableApiUsage") // usage of PrivacySandboxSdkExtension b/397703898
805     private fun configureWithPrivacySandboxSdkPlugin(project: Project) {
806         project.extensions.getByType<PrivacySandboxSdkExtension>().apply {
807             configureLocalAsbSigning(experimentalProperties, project.getKeystore())
808         }
809     }
810 
811     private fun configureLocalAsbSigning(
812         experimentalProperties: MutableMap<String, Any>,
813         keyStore: File
814     ) {
815         experimentalProperties[ASB_SIGNING_CONFIG_PROPERTY_NAME] = keyStore.absolutePath
816     }
817 
818     private val ASB_SIGNING_CONFIG_PROPERTY_NAME =
819         "android.privacy_sandbox.local_deployment_signing_store_file"
820 
821     /**
822      * Excludes files telling which versions of androidx libraries were used in test apks, to avoid
823      * invalidating caches as often
824      */
825     private fun excludeVersionFiles(packaging: com.android.build.api.variant.ResourcesPackaging) {
826         packaging.excludes.add("/META-INF/androidx*.version")
827     }
828 
829     /**
830      * Excludes files telling which versions of androidx libraries were used in test apks, to avoid
831      * invalidating caches as often
832      */
833     private fun excludeVersionFiles(packaging: com.android.build.api.dsl.ResourcesPackaging) {
834         packaging.excludes.add("/META-INF/androidx*.version")
835     }
836 
837     private fun Project.buildOnServerDependsOnAssembleRelease() {
838         project.addToBuildOnServer("assembleRelease")
839     }
840 
841     private fun Project.buildOnServerDependsOnLint() {
842         if (!project.usingMaxDepVersions().get()) {
843             project.addToBuildOnServer("lint")
844         }
845     }
846 
847     private fun HasDeviceTests.configureTests() {
848         deviceTests.forEach { (_, deviceTest) ->
849             deviceTest.packaging.resources.apply {
850                 excludeVersionFiles(this)
851 
852                 // Workaround a limitation in AGP that fails to merge these META-INF license files.
853                 pickFirsts.add("/META-INF/AL2.0")
854                 // In addition to working around the above issue, we exclude the LGPL2.1 license as
855                 // we're
856                 // approved to distribute code via AL2.0 and the only dependencies which pull in
857                 // LGPL2.1
858                 // are currently dual-licensed with AL2.0 and LGPL2.1. The affected dependencies
859                 // are:
860                 //   - net.java.dev.jna:jna:5.5.0
861                 excludes.add("/META-INF/LGPL2.1")
862 
863                 // AGP is unable to merge these and multiple artifacts ship this files
864                 // e.g. org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar
865                 //      org/bouncycastle/bcprov-jdk18on/1.78.1/bcprov-jdk18on-1.78.1.jar
866                 pickFirsts.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
867             }
868         }
869     }
870 
871     @Suppress("UnstableApiUsage") // usage of experimentalProperties b/397703898
872     private fun Variant.configureLocalAsbSigning(keyStore: File) {
873         experimentalProperties.put(ASB_SIGNING_CONFIG_PROPERTY_NAME, keyStore.absolutePath)
874     }
875 
876     // Taken from
877     // https://developer.android.com/build/releases/gradle-plugin#api-level-support
878     private fun mapToMinAgpVersion(compileSdk: Int): String {
879         return when (compileSdk) {
880             34 -> "8.1.1"
881             35 -> "8.6.0"
882             36 -> "8.9.1"
883             else -> throw Exception("Unknown compileSdk to minAgpVersion mapping")
884         }
885     }
886 
887     private fun configureWithLibraryPlugin(project: Project, androidXExtension: AndroidXExtension) {
888         val buildTypeForTests = "release"
889         project.extensions.getByType<LibraryExtension>().apply {
890             publishing { singleVariant(DEFAULT_PUBLISH_CONFIG) }
891 
892             configureAndroidBaseOptions(project, androidXExtension)
893             val debugSigningConfig = signingConfigs.getByName("debug")
894             // Use a local debug keystore to avoid build server issues.
895             debugSigningConfig.storeFile = project.getKeystore()
896             buildTypes.configureEach { buildType ->
897                 // Sign all the builds (including release) with debug key
898                 buildType.signingConfig = debugSigningConfig
899             }
900             testBuildType = buildTypeForTests
901             project.configureTestConfigGeneration(
902                 this,
903                 androidXExtension.isIsolatedProjectsEnabled()
904             )
905             project.addAppApkToTestConfigGeneration(androidXExtension)
906         }
907 
908         val libraryAndroidComponentsExtension =
909             project.extensions.getByType<LibraryAndroidComponentsExtension>()
910 
911         libraryAndroidComponentsExtension.apply {
912             finalizeDsl {
913                 // Propagate the compileSdk value into minCompileSdk. Don't propagate
914                 // compileSdkExtension, since only one library actually depends on the extension
915                 // APIs and they can explicitly declare that in their build.gradle. Note that when
916                 // we're using a preview SDK, the value for compileSdk will be null and the
917                 // resulting AAR metadata won't have a minCompileSdk --
918                 // this is okay because AGP automatically embeds forceCompileSdkPreview in the AAR
919                 // metadata and uses it instead of minCompileSdk.
920                 it.compileSdk?.apply {
921                     it.defaultConfig.aarMetadata.minCompileSdk = this
922                     it.defaultConfig.aarMetadata.minAgpVersion = mapToMinAgpVersion(this)
923                 }
924                 it.lint.targetSdk = project.defaultAndroidConfig.targetSdk
925                 it.testOptions.targetSdk = project.defaultAndroidConfig.targetSdk
926                 // Replace with a public API once available, see b/360392255
927                 it.buildTypes.configureEach { buildType ->
928                     if (buildType.name == buildTypeForTests && !project.hasBenchmarkPlugin())
929                         (buildType as TestBuildType).isDebuggable = true
930                     project.setUpBlankProguardFileForAarIfNeeded(buildType)
931                 }
932             }
933             beforeVariants(selector().withBuildType("debug")) { variant -> variant.enable = false }
934             beforeVariants(selector().all()) { variant ->
935                 variant.androidTest.targetSdk = project.defaultAndroidConfig.targetSdk
936             }
937             onVariants { variant ->
938                 variant.configureTests()
939                 variant.enableMicrobenchmarkInternalDefaults(project)
940                 project.validateKotlinModuleFiles(
941                     variant.name,
942                     variant.artifacts.get(SingleArtifact.AAR)
943                 )
944             }
945         }
946 
947         project.disableStrictVersionConstraints()
948         project.configureVersionFileWriter(libraryAndroidComponentsExtension, androidXExtension)
949         project.configureJavaCompilationWarnings(androidXExtension)
950 
951         val prebuiltLibraries = listOf("libtracing_perfetto.so", "libc++_shared.so")
952         val copyPublicResourcesDirTask =
953             project.tasks.register(
954                 "generatePublicResourcesStub",
955                 CopyPublicResourcesDirTask::class.java
956             ) { task ->
957                 task.buildSrcResDir.set(File(project.getSupportRootFolder(), "buildSrc/res"))
958             }
959         libraryAndroidComponentsExtension.onVariants { variant ->
960             if (variant.buildType == DEFAULT_PUBLISH_CONFIG) {
961                 // Standard docs, resource API, and Metalava configuration for AndroidX projects.
962                 project.configureProjectForApiTasks(
963                     LibraryApiTaskConfig(variant),
964                     androidXExtension
965                 )
966                 project.configureProjectForKzipTasks(
967                     LibraryApiTaskConfig(variant),
968                     androidXExtension
969                 )
970             }
971             if (variant.name == DEFAULT_PUBLISH_CONFIG) {
972                 project.configureSourceJarForAndroid(variant, androidXExtension.samplesProjects)
973                 project.configureDependencyVerification(androidXExtension) { taskProvider ->
974                     taskProvider.configure { task -> task.dependsOn("compileReleaseJavaWithJavac") }
975                 }
976             }
977             configurePublicResourcesStub(variant, copyPublicResourcesDirTask)
978             val verifyELFRegionAlignmentTaskProvider =
979                 project.tasks.register(
980                     variant.name + "VerifyELFRegionAlignment",
981                     VerifyELFRegionAlignmentTask::class.java
982                 ) { task ->
983                     task.files.from(
984                         variant.artifacts.get(SingleArtifact.MERGED_NATIVE_LIBS).map { dir ->
985                             dir.asFileTree.files
986                                 .filter { it.extension == "so" }
987                                 .filter { it.path.contains("arm64-v8a") }
988                                 .filterNot { prebuiltLibraries.contains(it.name) }
989                         }
990                     )
991                     task.cacheEvenIfNoOutputs()
992                 }
993             project.addToBuildOnServer(verifyELFRegionAlignmentTaskProvider)
994         }
995 
996         project.setUpCheckDocsTask(androidXExtension)
997 
998         project.buildOnServerDependsOnAssembleRelease()
999         project.buildOnServerDependsOnLint()
1000     }
1001 
1002     private fun configureGradlePluginPlugin(project: Project) {
1003         project.tasks.withType(ValidatePlugins::class.java).configureEach {
1004             it.enableStricterValidation.set(true)
1005             it.failOnWarning.set(true)
1006         }
1007         project.addToBuildOnServer("validatePlugins")
1008         SdkResourceGenerator.generateForHostTest(project)
1009     }
1010 
1011     private fun configureWithJavaPlugin(project: Project, androidXExtension: AndroidXExtension) {
1012         if (
1013             project.multiplatformExtension != null &&
1014                 !project.multiplatformExtension!!.hasJvmTarget()
1015         ) {
1016             return
1017         }
1018         project.configureErrorProneForJava()
1019 
1020         // Force Java 1.8 source- and target-compatibility for all Java libraries.
1021         val javaExtension = project.extensions.getByType<JavaPluginExtension>()
1022         project.afterEvaluate {
1023             javaExtension.apply {
1024                 val defaultTargetJavaVersion =
1025                     getDefaultTargetJavaVersion(androidXExtension.type, project.name)
1026                 sourceCompatibility = defaultTargetJavaVersion
1027                 targetCompatibility = defaultTargetJavaVersion
1028             }
1029             if (!project.plugins.hasPlugin(KotlinBasePluginWrapper::class.java)) {
1030                 project.configureSourceJarForJava(androidXExtension.samplesProjects)
1031             }
1032         }
1033 
1034         project.setUpBlankProguardFileForJarIfNeeded(javaExtension)
1035         project.configureJavaCompilationWarnings(androidXExtension)
1036 
1037         project.hideJavadocTask()
1038         if (
1039             project.multiplatformExtension == null ||
1040                 project.multiplatformExtension!!.hasJavaEnabled()
1041         ) {
1042             project.configureDependencyVerification(androidXExtension) { taskProvider ->
1043                 taskProvider.configure { task ->
1044                     task.dependsOn(project.tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME))
1045                 }
1046             }
1047         }
1048 
1049         val apiTaskConfig =
1050             if (project.multiplatformExtension != null) {
1051                 KmpApiTaskConfig
1052             } else {
1053                 JavaApiTaskConfig
1054             }
1055 
1056         project.configureProjectForApiTasks(apiTaskConfig, androidXExtension)
1057         project.configureProjectForKzipTasks(apiTaskConfig, androidXExtension)
1058         project.setUpCheckDocsTask(androidXExtension)
1059 
1060         if (project.multiplatformExtension == null) {
1061             project.addToBuildOnServer("jar")
1062         } else {
1063             val multiplatformExtension = project.multiplatformExtension!!
1064             multiplatformExtension.targets.forEach {
1065                 if (it.platformType == KotlinPlatformType.jvm) {
1066                     val task = project.tasks.named(it.artifactsTaskName, Jar::class.java)
1067                     project.addToBuildOnServer(task)
1068                 }
1069             }
1070         }
1071     }
1072 
1073     private fun Project.configureProjectStructureValidation(androidXExtension: AndroidXExtension) {
1074         // AndroidXExtension.mavenGroup is not readable until afterEvaluate.
1075         afterEvaluate {
1076             val mavenGroup = androidXExtension.mavenGroup
1077             val isProbablyPublished =
1078                 androidXExtension.type == SoftwareType.PUBLISHED_LIBRARY ||
1079                     androidXExtension.type ==
1080                         SoftwareType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
1081             if (mavenGroup != null && isProbablyPublished && androidXExtension.shouldPublish()) {
1082                 validateProjectMavenGroup(mavenGroup.group)
1083                 validateProjectMavenName(androidXExtension.name.get(), mavenGroup.group)
1084                 validateProjectStructure(mavenGroup.group)
1085             }
1086         }
1087     }
1088 
1089     private fun Project.configureProjectVersionValidation(androidXExtension: AndroidXExtension) {
1090         // AndroidXExtension.mavenGroup is not readable until afterEvaluate.
1091         afterEvaluate { androidXExtension.validateMavenVersion() }
1092     }
1093 
1094     private fun CommonExtension<*, *, *, *, *, *>.configureAndroidBaseOptions(
1095         project: Project,
1096         androidXExtension: AndroidXExtension
1097     ) {
1098         compileOptions.apply {
1099             sourceCompatibility = VERSION_1_8
1100             targetCompatibility = VERSION_1_8
1101         }
1102 
1103         val defaultMinSdk = project.defaultAndroidConfig.minSdk
1104         val defaultCompileSdk = project.defaultAndroidConfig.compileSdk
1105 
1106         // Suppress output of android:compileSdkVersion and related attributes (b/277836549).
1107         androidResources.additionalParameters += "--no-compile-sdk-metadata"
1108 
1109         compileSdk = project.defaultAndroidConfig.compileSdk
1110 
1111         buildToolsVersion = project.defaultAndroidConfig.buildToolsVersion
1112 
1113         defaultConfig.ndk.abiFilters.addAll(SUPPORTED_BUILD_ABIS)
1114         defaultConfig.minSdk = defaultMinSdk
1115         defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1116 
1117         testOptions.animationsDisabled = !project.isMacrobenchmark()
1118         testOptions.unitTests.isReturnDefaultValues = true
1119         testOptions.unitTests.all { task -> task.configureForRobolectric() }
1120 
1121         // Include resources in Robolectric tests as a workaround for b/184641296
1122         testOptions.unitTests.isIncludeAndroidResources = true
1123 
1124         project.afterEvaluate {
1125             val minSdkVersion = defaultConfig.minSdk!!
1126             check(minSdkVersion >= defaultMinSdk) {
1127                 "minSdkVersion $minSdkVersion lower than the default of $defaultMinSdk"
1128             }
1129             check(compileSdk == defaultCompileSdk || project.isCustomCompileSdkAllowed()) {
1130                 "compileSdk must not be explicitly specified, was \"$compileSdk\""
1131             }
1132 
1133             project.enforceBanOnVersionRanges()
1134 
1135             if (androidXExtension.type.compilationTarget != CompilationTarget.DEVICE) {
1136                 throw IllegalStateException(
1137                     "${androidXExtension.type.name} libraries cannot apply the android plugin, as" +
1138                         " they do not target android devices"
1139                 )
1140             }
1141         }
1142 
1143         project.configureErrorProneForAndroid()
1144 
1145         // workaround for b/120487939
1146         project.configurations.configureEach { configuration ->
1147             // Gradle seems to crash on androidtest configurations
1148             // preferring project modules...
1149             if (!configuration.name.lowercase(Locale.US).contains("androidtest")) {
1150                 configuration.resolutionStrategy.preferProjectModules()
1151             }
1152         }
1153 
1154         val componentsExtension =
1155             project.extensions.getByType(AndroidComponentsExtension::class.java)
1156         project.configureFtlRunner(componentsExtension)
1157 
1158         // If a dependency is missing a debug variant, use release instead.
1159         buildTypes.getByName("debug").matchingFallbacks.add("release")
1160 
1161         // AGP warns if we use project.buildDir (or subdirs) for CMake's generated
1162         // build files (ninja build files, CMakeCache.txt, etc.). Use a staging directory that
1163         // lives alongside the project's buildDir.
1164         @Suppress("DEPRECATION")
1165         externalNativeBuild.cmake.buildStagingDirectory =
1166             File(project.buildDir, "../nativeBuildStaging")
1167 
1168         // Align the ELF region of native shared libs 16kb boundary
1169         defaultConfig.externalNativeBuild.cmake.arguments.add(
1170             "-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384"
1171         )
1172     }
1173 
1174     private fun KotlinMultiplatformAndroidLibraryTarget.configureAndroidBaseOptions(
1175         project: Project,
1176         componentsExtension: KotlinMultiplatformAndroidComponentsExtension
1177     ) {
1178         val defaultMinSdkVersion = project.defaultAndroidConfig.minSdk
1179         val defaultCompileSdk = project.defaultAndroidConfig.compileSdk
1180 
1181         compileSdk = defaultCompileSdk
1182         buildToolsVersion = project.defaultAndroidConfig.buildToolsVersion
1183 
1184         minSdk = defaultMinSdkVersion
1185 
1186         lint.targetSdk = project.defaultAndroidConfig.targetSdk
1187         compilations
1188             .withType(KotlinMultiplatformAndroidDeviceTestCompilation::class.java)
1189             .configureEach {
1190                 it.instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1191                 it.animationsDisabled = true
1192             }
1193         compilations
1194             .withType(KotlinMultiplatformAndroidHostTestCompilation::class.java)
1195             .configureEach {
1196                 it.isReturnDefaultValues = true
1197                 // Include resources in Robolectric tests as a workaround for b/184641296
1198                 it.isIncludeAndroidResources = true
1199             }
1200 
1201         // validate that SDK versions haven't been altered during evaluation
1202         project.afterEvaluate {
1203             val minSdkVersion = minSdk!!
1204             check(minSdkVersion >= defaultMinSdkVersion) {
1205                 "minSdkVersion $minSdkVersion lower than the default of $defaultMinSdkVersion"
1206             }
1207             check(compileSdk == defaultCompileSdk || project.isCustomCompileSdkAllowed()) {
1208                 "compileSdk must not be explicitly specified, was \"$compileSdk\""
1209             }
1210             project.enforceBanOnVersionRanges()
1211         }
1212 
1213         project.configureTestConfigGeneration(
1214             this,
1215             componentsExtension,
1216             buildFeatures.isIsolatedProjectsEnabled()
1217         )
1218         project.configureFtlRunner(componentsExtension)
1219     }
1220 
1221     /**
1222      * Adds a module handler replacement rule that treats full Guava (of any version) as an upgrade
1223      * to ListenableFuture-only Guava. This prevents irreconcilable versioning conflicts and/or
1224      * class duplication issues.
1225      */
1226     private fun Project.configureGuavaUpgradeHandler() {
1227         // The full Guava artifact is very large, so they split off a special artifact containing a
1228         // standalone version of the commonly-used ListenableFuture interface. However, they also
1229         // structured the artifacts in a way that causes dependency resolution conflicts:
1230         // - `com.google.guava:listenablefuture:1.0` contains only ListenableFuture
1231         // - `com.google.guava:listenablefuture:9999.0` contains nothing
1232         // - `com.google.guava:guava` contains all of Guava, including ListenableFuture
1233         // If a transitive dependency includes `guava` as implementation-type and we have a direct
1234         // API-type dependency on `listenablefuture:1.0`, then we'll get `listenablefuture:9999.0`
1235         // on the compilation classpath -- which does not have the ListenableFuture class. However,
1236         // if we tell Gradle to upgrade all LF dependencies to Guava then we'll get `guava` as an
1237         // API-type dependency. See b/274621238 for more details.
1238         project.dependencies {
1239             modules { moduleHandler ->
1240                 moduleHandler.module("com.google.guava:listenablefuture") { module ->
1241                     module.replacedBy("com.google.guava:guava")
1242                 }
1243             }
1244         }
1245     }
1246 
1247     private fun Project.disableStrictVersionConstraints() {
1248         // Gradle inserts strict version constraints to ensure that dependency versions are
1249         // identical across main and test source sets. For normal projects, this ensures
1250         // that test bytecode is binary- and behavior-compatible with the main source set's
1251         // bytecode. For AndroidX, though, we require backward compatibility and therefore
1252         // don't need to enforce such constraints.
1253         project.configurations.configureEach { configuration ->
1254             if (!configuration.isTest()) return@configureEach
1255 
1256             configuration.dependencyConstraints.configureEach { dependencyConstraint ->
1257                 val strictVersion = dependencyConstraint.versionConstraint.strictVersion
1258                 if (strictVersion != "") {
1259                     // Migrate strict-type version constraints to required-type to allow upgrades.
1260                     dependencyConstraint.version { versionConstraint ->
1261                         versionConstraint.strictly("")
1262                         versionConstraint.require(strictVersion)
1263                     }
1264                 }
1265             }
1266         }
1267     }
1268 
1269     private fun LibraryExtension.configureAndroidLibraryWithMultiplatformPluginOptions() {
1270         sourceSets.findByName("main")!!.manifest.srcFile("src/androidMain/AndroidManifest.xml")
1271         sourceSets
1272             .findByName("androidTest")!!
1273             .manifest
1274             .srcFile("src/androidInstrumentedTest/AndroidManifest.xml")
1275     }
1276 
1277     private fun Project.configureKmp() {
1278         val kmpExtension =
1279             checkNotNull(project.extensions.findByType<KotlinMultiplatformExtension>()) {
1280                 """
1281             Project ${project.path} applies kotlin multiplatform plugin but we cannot find the
1282             KotlinMultiplatformExtension.
1283             """
1284                     .trimIndent()
1285             }
1286 
1287         kmpExtension.targets.configureEach { kotlinTarget ->
1288             kotlinTarget.compilations.configureEach { compilation ->
1289                 // Configure all KMP targets to allow expect/actual classes that are not stable.
1290                 // (see https://youtrack.jetbrains.com/issue/KT-61573)
1291                 compilation.compileTaskProvider.configure { task ->
1292                     task.compilerOptions.freeCompilerArgs.add("-Xexpect-actual-classes")
1293                     androidXConfiguration.kotlinApiVersion.let {
1294                         task.compilerOptions.apiVersion.set(it)
1295                         task.compilerOptions.languageVersion.set(it)
1296                     }
1297                 }
1298             }
1299         }
1300     }
1301 
1302     private fun ApplicationExtension.configureAndroidApplicationOptions(
1303         project: Project,
1304         androidXExtension: AndroidXExtension
1305     ) {
1306         defaultConfig.apply {
1307             versionCode = 1
1308             versionName = "1.0"
1309         }
1310 
1311         project.configureTestConfigGeneration(this, androidXExtension.isIsolatedProjectsEnabled())
1312         project.addAppApkToTestConfigGeneration(androidXExtension)
1313         project.addAppApkToFtlRunner()
1314     }
1315 
1316     private fun Project.configureDependencyVerification(
1317         androidXExtension: AndroidXExtension,
1318         taskConfigurator: (TaskProvider<VerifyDependencyVersionsTask>) -> Unit
1319     ) {
1320         if (buildFeatures.isIsolatedProjectsEnabled()) return
1321         afterEvaluate {
1322             if (androidXExtension.type.requiresDependencyVerification()) {
1323                 taskConfigurator(project.createVerifyDependencyVersionsTask())
1324             }
1325         }
1326     }
1327 
1328     // If this project wants other project in the same group to have the same version,
1329     // this function configures those constraints.
1330     private fun Project.configureConstraintsWithinGroup(androidXExtension: AndroidXExtension) {
1331         if (
1332             !project.shouldAddGroupConstraints().get() || buildFeatures.isIsolatedProjectsEnabled()
1333         ) {
1334             return
1335         }
1336         project.afterEvaluate {
1337             // make sure that the project has a group
1338             val projectGroup = androidXExtension.mavenGroup ?: return@afterEvaluate
1339             // make sure that this group is configured to use a single version
1340             projectGroup.atomicGroupVersion ?: return@afterEvaluate
1341 
1342             // Under certain circumstances, a project is allowed to override its
1343             // version see ( isGroupVersionOverrideAllowed ), in which case it's
1344             // not participating in the versioning policy yet,
1345             // and we don't assign it any version constraints
1346             if (androidXExtension.mavenVersion != null) {
1347                 return@afterEvaluate
1348             }
1349 
1350             // We don't want to emit the same constraint into our .module file more than once,
1351             // and we don't want to try to apply a constraint to a configuration that doesn't accept
1352             // them,
1353             // so we create a configuration to hold the constraints and make each other constraint
1354             // extend it
1355             val constraintConfiguration = project.configurations.create("groupConstraints")
1356             project.configurations.configureEach { configuration ->
1357                 if (configuration != constraintConfiguration)
1358                     configuration.extendsFrom(constraintConfiguration)
1359             }
1360 
1361             val otherProjectsInSameGroup = androidXExtension.getOtherProjectsInSameGroup()
1362             val constraints = project.dependencies.constraints
1363             val allProjectsExist = buildContainsAllStandardProjects()
1364             for (otherProject in otherProjectsInSameGroup) {
1365                 val otherGradlePath = otherProject.gradlePath
1366                 if (otherGradlePath == ":compose:ui:ui-android-stubs") {
1367                     // exemption for library that doesn't truly get published: b/168127161
1368                     continue
1369                 }
1370                 // We only enable constraints for builds that we intend to be able to publish from.
1371                 //   If a project isn't included in a build we intend to be able to publish from,
1372                 //   the project isn't going to be published.
1373                 // Sometimes this can happen when a project subset is enabled:
1374                 //   The KMP project subset enabled by androidx_multiplatform_mac.sh contains
1375                 //   :benchmark:benchmark-common but not :benchmark:benchmark-benchmark
1376                 //   This is ok because we don't intend to publish that artifact from that build
1377                 val otherProjectShouldExist =
1378                     allProjectsExist || findProject(otherGradlePath) != null
1379                 if (!otherProjectShouldExist) {
1380                     continue
1381                 }
1382                 // We only emit constraints referring to projects that will release
1383                 val otherFilepath =
1384                     getSupportRootFolder().resolve(File(otherProject.filePath, "build.gradle"))
1385                 val parsed =
1386                     if (otherFilepath.exists()) {
1387                         parseBuildFile(otherFilepath)
1388                     } else {
1389                         parseBuildFile(
1390                             getSupportRootFolder()
1391                                 .resolve(File(otherProject.filePath, "build.gradle.kts"))
1392                         )
1393                     }
1394                 if (!parsed.shouldRelease()) {
1395                     continue
1396                 }
1397                 if (parsed.softwareType == SoftwareType.SAMPLES) {
1398                     // a SAMPLES project knows how to publish, but we don't intend to actually
1399                     // publish it
1400                     continue
1401                 }
1402                 // Under certain circumstances, a project is allowed to override its
1403                 // version see ( isGroupVersionOverrideAllowed ), in which case it's
1404                 // not participating in the versioning policy yet and we don't emit
1405                 // version constraints referencing it
1406                 if (parsed.specifiesVersion) {
1407                     continue
1408                 }
1409                 val dependencyConstraint = project(otherGradlePath)
1410                 constraints.add(constraintConfiguration.name, dependencyConstraint) {
1411                     it.because("${project.name} is in atomic group ${projectGroup.group}")
1412                 }
1413             }
1414 
1415             // disallow duplicate constraints
1416             project.configurations.configureEach { config ->
1417                 // Allow duplicate constraints in test configurations. This is partially a
1418                 // workaround for duplication due to downgrading strict-type dependencies to
1419                 // required-type, but also we don't care if tests have duplicate constraints.
1420                 if (config.isTest()) return@configureEach
1421 
1422                 // find all constraints contributed by this Configuration and its ancestors
1423                 val configurationConstraints: MutableSet<String> = mutableSetOf()
1424                 config.hierarchy.forEach { parentConfig ->
1425                     parentConfig.dependencyConstraints.configureEach { dependencyConstraint ->
1426                         dependencyConstraint.apply {
1427                             if (
1428                                 versionConstraint.requiredVersion != "" &&
1429                                     versionConstraint.requiredVersion != "unspecified"
1430                             ) {
1431                                 val key =
1432                                     "${dependencyConstraint.group}:${dependencyConstraint.name}"
1433                                 if (configurationConstraints.contains(key)) {
1434                                     throw GradleException(
1435                                         "Constraint on $key was added multiple times in " +
1436                                             "$config (version = " +
1437                                             "${versionConstraint.requiredVersion}).\n\n" +
1438                                             "This is unnecessary and can also trigger " +
1439                                             "https://github.com/gradle/gradle/issues/24037 in " +
1440                                             "builds trying to use the resulting artifacts."
1441                                     )
1442                                 }
1443                                 configurationConstraints.add(key)
1444                             }
1445                         }
1446                     }
1447                 }
1448             }
1449         }
1450     }
1451 
1452     /**
1453      * Tells whether this build contains the usual set of all projects (`./gradlew projects`)
1454      * Sometimes developers request to include fewer projects because this may run more quickly
1455      */
1456     private fun Project.buildContainsAllStandardProjects(): Boolean {
1457         if (getProjectSubset() != null) return false
1458         if (ProjectLayoutType.isPlayground(this)) return false
1459         return true
1460     }
1461 
1462     companion object {
1463         const val FINALIZE_TEST_CONFIGS_WITH_APKS_TASK = "finalizeTestConfigsWithApks"
1464         const val ZIP_TEST_CONFIGS_WITH_APKS_TASK = "zipTestConfigsWithApks"
1465 
1466         const val TASK_GROUP_API = "API"
1467 
1468         const val EXTENSION_NAME = "androidx"
1469 
1470         // b/366238650
1471         val SUPPORTED_BUILD_ABIS = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
1472 
1473         /** Fail the build if a non-Studio task runs longer than expected */
1474         const val TASK_TIMEOUT_MINUTES = 60L
1475     }
1476 }
1477 
getDefaultTargetJavaVersionnull1478 internal fun getDefaultTargetJavaVersion(
1479     softwareType: SoftwareType,
1480     projectName: String? = null,
1481     targetName: String? = null
1482 ): JavaVersion {
1483     return when {
1484         // TODO(b/353328300): Move room-compiler-processing to Java 17 once Dagger is ready.
1485         projectName != null && projectName.contains("room-compiler-processing") -> VERSION_11
1486         projectName != null && projectName.contains("desktop") -> VERSION_11
1487         targetName != null && (targetName == "desktop" || targetName == "jvmStubs") -> VERSION_11
1488         softwareType.compilationTarget == CompilationTarget.HOST -> VERSION_17
1489         else -> VERSION_1_8
1490     }
1491 }
1492 
Projectnull1493 private fun Project.validateLintVersionTestExists(androidXExtension: AndroidXExtension) {
1494     if (!androidXExtension.type.isLint()) {
1495         return
1496     }
1497     kotlinExtensionOrNull?.let { extension ->
1498         val projectFiles = extension.sourceSets.flatMap { it.kotlin.files }
1499         // if the project doesn't define a registry it doesn't make sense to test versions
1500         if (projectFiles.none { it.name.contains("Registry") }) {
1501             return
1502         }
1503         projectFiles.find { it.name == "ApiLintVersionsTest.kt" }
1504             ?: throw GradleException("Lint projects should include ApiLintVersionsTest.kt")
1505     }
1506 }
1507 
1508 /** Returns whether the configuration is used for testing. */
Configurationnull1509 private fun Configuration.isTest(): Boolean = name.lowercase().contains("test")
1510 
1511 /** Returns whether the configuration is part of publication. */
1512 internal fun Configuration.isPublished(): Boolean =
1513     !isTest() && !name.lowercase().contains("metadata") && !name.endsWith("CInterop")
1514 
1515 /**
1516  * Hides a project's Javadoc tasks from the output of `./gradlew tasks` by setting their group to
1517  * `null`.
1518  *
1519  * AndroidX projects do not use the Javadoc task for docs generation, so we don't want them
1520  * cluttering up the task overview.
1521  */
1522 private fun Project.hideJavadocTask() {
1523     tasks.withType(Javadoc::class.java).configureEach {
1524         if (it.name == "javadoc") {
1525             it.group = null
1526         }
1527     }
1528 }
1529 
1530 val Project.androidExtension: AndroidComponentsExtension<*, *, *>
1531     get() =
1532         extensions.findByType<LibraryAndroidComponentsExtension>()
1533             ?: extensions.findByType<ApplicationAndroidComponentsExtension>()
1534             ?: throw IllegalArgumentException("Failed to find any registered Android extension")
1535 
1536 val Project.multiplatformExtension
1537     get() = extensions.findByType(KotlinMultiplatformExtension::class.java)
1538 
1539 val Project.kotlinExtensionOrNull: KotlinProjectExtension?
1540     get() = extensions.findByType()
1541 
1542 val Project.androidXExtension: AndroidXExtension
1543     get() = extensions.getByType()
1544 
1545 /**
1546  * Configures all non-Studio tasks in a project (see b/153193718 for background) to time out after
1547  * [TASK_TIMEOUT_MINUTES].
1548  */
configureTaskTimeoutsnull1549 internal fun Project.configureTaskTimeouts() {
1550     // A set of tasks that sometimes take >60 minutes. b/383874664
1551     val slowTasks =
1552         setOf(
1553             ":compose:ui:ui:compileReleaseAndroidTestKotlinAndroid",
1554             ":compose:foundation:foundation:compileReleaseAndroidTestKotlinAndroid",
1555             ":compose:foundation:foundation:integration-tests:lazy-tests:compileReleaseAndroidTestKotlin"
1556         )
1557     tasks.configureEach { t ->
1558         // skip adding a timeout for some tasks that both take a long time and
1559         // that we can count on the user to monitor
1560         if (t !is StudioTask) {
1561             t.timeout.set(
1562                 Duration.ofMinutes(if (t.path in slowTasks) 80L else TASK_TIMEOUT_MINUTES)
1563             )
1564         }
1565     }
1566 }
1567 
1568 private class JavaCompileArgumentProvider(
1569     private val isTestApp: Boolean,
1570     private val failOnDeprecationWarnings: Provider<Boolean>,
1571     private val usingMaxDepVersions: Provider<Boolean>,
1572 ) : CommandLineArgumentProvider {
asArgumentsnull1573     override fun asArguments(): List<String> {
1574         // JDK 21 considers Java 8 an obsolete source and target value. Disable this warning.
1575         val args = mutableListOf("-Xlint:-options")
1576         // If we're running a hypothetical test build confirming that tip-of-tree versions
1577         // are compatible, then we're not concerned about warnings
1578         if (!usingMaxDepVersions.get() && !isTestApp) {
1579             args.add("-Xlint:unchecked")
1580             if (failOnDeprecationWarnings.get()) {
1581                 args.add("-Xlint:deprecation")
1582             }
1583         }
1584         return args
1585     }
1586 }
1587 
Projectnull1588 private fun Project.configureJavaCompilationWarnings(
1589     androidXExtension: AndroidXExtension,
1590     isTestApp: Boolean = false,
1591 ) {
1592     project.tasks.withType(JavaCompile::class.java).configureEach { task ->
1593         task.options.compilerArgumentProviders.add(
1594             JavaCompileArgumentProvider(
1595                 isTestApp = isTestApp,
1596                 failOnDeprecationWarnings = androidXExtension.failOnDeprecationWarnings,
1597                 usingMaxDepVersions = usingMaxDepVersions()
1598             )
1599         )
1600     }
1601 }
1602 
Projectnull1603 fun Project.hasBenchmarkPlugin(): Boolean {
1604     return this.plugins.hasPlugin(BenchmarkPlugin::class.java)
1605 }
1606 
Projectnull1607 fun Project.isMacrobenchmark(): Boolean {
1608     return this.path.endsWith("macrobenchmark")
1609 }
1610 
1611 /**
1612  * Returns a string that is a valid filename and loosely based on the project name The value
1613  * returned for each project will be distinct
1614  */
Stringnull1615 fun String.asFilenamePrefix(): String {
1616     return this.substring(1).replace(':', '-')
1617 }
1618 
1619 /**
1620  * Sets the specified [task] as a dependency of the top-level `check` task, ensuring that it runs as
1621  * part of `./gradlew check`.
1622  */
addToCheckTasknull1623 fun <T : Task> Project.addToCheckTask(task: TaskProvider<T>) {
1624     project.tasks.named("check").configure { it.dependsOn(task) }
1625 }
1626 
Projectnull1627 fun Project.validateMultiplatformPluginHasNotBeenApplied() {
1628     if (plugins.hasPlugin(KotlinMultiplatformPluginWrapper::class.java)) {
1629         throw GradleException(
1630             "The Kotlin multiplatform plugin should only be applied by the AndroidX plugin."
1631         )
1632     }
1633 }
1634 
1635 /** Verifies we don't accidentially write "implementation" instead of "commonMainImplementation" */
Projectnull1636 fun Project.disallowAccidentalAndroidDependenciesInKmpProject(
1637     androidXKmpExtension: AndroidXMultiplatformExtension
1638 ) {
1639     project.afterEvaluate {
1640         if (androidXKmpExtension.supportedPlatforms.isNotEmpty()) {
1641             val androidConfiguration = project.configurations.findByName("implementation")
1642             if (androidConfiguration != null) {
1643                 if (
1644                     androidConfiguration.dependencies.isNotEmpty() ||
1645                         androidConfiguration.dependencyConstraints.isNotEmpty()
1646                 ) {
1647                     throw GradleException(
1648                         "The 'implementation' Configuration should not be used in a " +
1649                             "multiplatform project: this Configuration is declared by the " +
1650                             "Android plugin rather than the kmp plugin. Did you mean " +
1651                             "'commonMainImplementation'?"
1652                     )
1653                 }
1654             }
1655         }
1656     }
1657 }
1658 
1659 /** Verifies that ProjectParser computes the correct values for this project */
Projectnull1660 fun Project.validateProjectParser(androidXExtension: AndroidXExtension) {
1661     // If configuration fails, we don't want to validate the ProjectParser
1662     // (otherwise it could report a confusing, unnecessary error)
1663     project.gradle.taskGraph.whenReady {
1664         val parsed = project.parse()
1665         val errorPrefix = "ProjectParser error parsing ${project.path}."
1666         check(androidXExtension.type == parsed.softwareType) {
1667             "$errorPrefix Incorrectly computed libraryType = ${parsed.softwareType} " +
1668                 "instead of ${androidXExtension.type}"
1669         }
1670         check(androidXExtension.shouldPublish() == parsed.shouldPublish()) {
1671             "$errorPrefix Incorrectly computed shouldPublish() = ${parsed.shouldPublish()} " +
1672                 "instead of ${androidXExtension.shouldPublish()}"
1673         }
1674         check(androidXExtension.shouldRelease() == parsed.shouldRelease()) {
1675             "$errorPrefix Incorrectly computed shouldRelease() = ${parsed.shouldRelease()} " +
1676                 "instead of ${androidXExtension.shouldRelease()}"
1677         }
1678         check(androidXExtension.projectDirectlySpecifiesMavenVersion == parsed.specifiesVersion) {
1679             "$errorPrefix Incorrectly computed specifiesVersion = ${parsed.specifiesVersion} " +
1680                 " instead of ${androidXExtension.projectDirectlySpecifiesMavenVersion}"
1681         }
1682     }
1683 }
1684 
1685 /** Validates the Maven version against Jetpack guidelines. */
AndroidXExtensionnull1686 fun AndroidXExtension.validateMavenVersion() {
1687     val mavenGroup = mavenGroup
1688     val mavenVersion = mavenVersion
1689     val forcedVersion = mavenGroup?.atomicGroupVersion
1690     if (forcedVersion != null && forcedVersion == mavenVersion) {
1691         throw GradleException(
1692             """
1693             Unnecessary override of same-group library version
1694 
1695             Project version is already set to $forcedVersion by same-version group
1696             ${mavenGroup.group}.
1697 
1698             To fix this error, remove "mavenVersion = ..." from your build.gradle
1699             configuration.
1700             """
1701                 .trimIndent()
1702         )
1703     }
1704 }
1705 
1706 /** Workaround for https://github.com/gradle/gradle/issues/27407 */
Projectnull1707 fun Project.workaroundPrebuiltTakingPrecedenceOverProject() {
1708     project.configurations.configureEach { configuration ->
1709         configuration.resolutionStrategy.preferProjectModules()
1710     }
1711 }
1712 
Projectnull1713 private fun Project.configureUnzipChromeBuildService() {
1714     if (ProjectLayoutType.isPlayground(this)) {
1715         return
1716     }
1717     gradle.sharedServices.registerIfAbsent("unzipChrome", UnzipChromeBuildService::class.java) {
1718         it.parameters.browserDir.set(File(getPrebuiltsRoot(), "androidx/chrome-for-testing/"))
1719         it.parameters.unzipToDir.set(getOutDirectory().resolve("chrome-bin"))
1720     }
1721 }
1722 
Testnull1723 private fun Test.configureForRobolectric() {
1724     // https://github.com/robolectric/robolectric/issues/7456
1725     jvmArgs =
1726         listOf(
1727             "--add-opens=java.base/java.lang=ALL-UNNAMED",
1728             "--add-opens=java.base/java.util=ALL-UNNAMED",
1729             "--add-opens=java.base/java.io=ALL-UNNAMED",
1730         )
1731     // Robolectric 1.7 increased heap size requirements, see b/207169653.
1732     maxHeapSize = "3g"
1733 }
1734 
Projectnull1735 private fun Project.enforceBanOnVersionRanges() {
1736     configurations.configureEach { configuration ->
1737         configuration.resolutionStrategy.eachDependency { dep ->
1738             val target = dep.target
1739             val version = target.version
1740             // Enforce the ban on declaring dependencies with version ranges.
1741             // Note: In playground, this ban is exempted to allow unresolvable prebuilts
1742             // to automatically get bumped to snapshot versions via version range
1743             // substitution.
1744             if (
1745                 version != null &&
1746                     Version.isDependencyRange(version) &&
1747                     project.rootProject.rootDir == project.getSupportRootFolder()
1748             ) {
1749                 throw IllegalArgumentException(
1750                     "Dependency ${dep.target} declares its version as " +
1751                         "version range ${dep.target.version} however the use of " +
1752                         "version ranges is not allowed, please update the " +
1753                         "dependency to list a fixed version."
1754                 )
1755             }
1756         }
1757     }
1758 }
1759 
hasAndroidMultiplatformPluginnull1760 internal fun Project.hasAndroidMultiplatformPlugin(): Boolean =
1761     extensions.findByType(AndroidXMultiplatformExtension::class.java)?.hasAndroidMultiplatform()
1762         ?: false
1763 
1764 @Suppress("DEPRECATION")
1765 internal fun KotlinMultiplatformExtension.hasJavaEnabled(): Boolean =
1766     targets.withType(KotlinJvmTarget::class.java).singleOrNull()?.withJavaEnabled ?: false
1767 
1768 internal fun KotlinMultiplatformExtension.hasJvmTarget(): Boolean =
1769     targets.withType(KotlinJvmTarget::class.java).isEmpty().not()
1770 
1771 internal fun String.camelCase() = replaceFirstChar {
1772     if (it.isLowerCase()) it.titlecase() else it.toString()
1773 }
1774