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