blob: c0841f8b30f4ac9da4328ba91823e1a9ffd8d6b8 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.internal.tasks
import com.android.SdkConstants.AAR_FORMAT_VERSION_PROPERTY
import com.android.SdkConstants.AAR_METADATA_VERSION_PROPERTY
import com.android.SdkConstants.MIN_COMPILE_SDK_PROPERTY
import com.android.build.gradle.internal.component.BaseCreationConfig
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.android.repository.Revision
import com.android.sdklib.SdkVersionInfo
import com.google.common.annotations.VisibleForTesting
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.component.LibraryBinaryIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import java.io.File
import java.util.Properties
import javax.inject.Inject
/** A task that reads the dependencies' AAR metadata files and checks for compatibility */
@CacheableTask
abstract class CheckAarMetadataTask : NonIncrementalTask() {
// Dummy output allows this task to be up-to-date, and it provides a means of making other tasks
// depend on this task.
@get:OutputDirectory
abstract val dummyOutputDirectory: DirectoryProperty
// Use a property to hold the [WorkerExecutor] so unit tests can reset it if necessary.
@get:Internal
abstract val workerExecutorProperty: Property<WorkerExecutor>
@VisibleForTesting
@get:Internal
internal lateinit var aarMetadataArtifacts: ArtifactCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
val aarMetadataFiles: FileCollection
get() = aarMetadataArtifacts.artifactFiles
@get:Input
abstract val aarFormatVersion: Property<String>
@get:Input
abstract val aarMetadataVersion: Property<String>
@get:Input
abstract val compileSdkVersion: Property<String>
override fun doTaskAction() {
for (artifact in aarMetadataArtifacts.artifacts) {
workerExecutorProperty.get().noIsolation().submit(
CheckAarMetadataWorkAction::class.java
) {
it.aarMetadataFile.set(artifact.file)
it.displayName.set(
when (val id = artifact.id.componentIdentifier) {
is LibraryBinaryIdentifier -> id.projectPath
is ModuleComponentIdentifier -> "${id.group}:${id.module}:${id.version}"
is ProjectComponentIdentifier -> id.projectPath
else -> id.displayName
}
)
it.aarFormatVersion.set(aarFormatVersion)
it.aarMetadataVersion.set(aarMetadataVersion)
it.compileSdkVersion.set(compileSdkVersion)
}
}
}
class CreationAction(
creationConfig: BaseCreationConfig
) : VariantTaskCreationAction<CheckAarMetadataTask, BaseCreationConfig>(creationConfig) {
override val name: String
get() = computeTaskName("check", "AarMetadata")
override val type: Class<CheckAarMetadataTask>
get() = CheckAarMetadataTask::class.java
override fun handleProvider(taskProvider: TaskProvider<CheckAarMetadataTask>) {
super.handleProvider(taskProvider)
creationConfig.artifacts
.setInitialProvider(taskProvider, CheckAarMetadataTask::dummyOutputDirectory)
.on(InternalArtifactType.AAR_METADATA_CHECK)
}
override fun configure(task: CheckAarMetadataTask) {
super.configure(task)
task.workerExecutorProperty.setDisallowChanges(task.workerExecutor)
task.aarMetadataArtifacts =
creationConfig.variantDependencies.getArtifactCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.ALL,
AndroidArtifacts.ArtifactType.AAR_METADATA
)
task.aarFormatVersion.setDisallowChanges(AarMetadataTask.AAR_FORMAT_VERSION)
task.aarMetadataVersion.setDisallowChanges(AarMetadataTask.AAR_METADATA_VERSION)
task.compileSdkVersion.setDisallowChanges(
creationConfig.globalScope.extension.compileSdkVersion
?: throw RuntimeException("compileSdkVersion is not specified.")
)
}
}
}
/** [WorkAction] to check AAR metadata files */
abstract class CheckAarMetadataWorkAction @Inject constructor(
private val checkAarMetadataWorkParameters: CheckAarMetadataWorkParameters
): WorkAction<CheckAarMetadataWorkParameters> {
override fun execute() {
val aarMetadataFile = checkAarMetadataWorkParameters.aarMetadataFile.get().asFile
val aarMetadataReader = AarMetadataReader(aarMetadataFile)
// check aarFormatVersion
val aarFormatVersion = aarMetadataReader.aarFormatVersion
if (aarFormatVersion == null) {
throw RuntimeException(
"""
A dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH}) does
not specify an $AAR_FORMAT_VERSION_PROPERTY value, which is a required value.
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
} else {
try {
val majorAarVersion = Revision.parseRevision(aarFormatVersion).major
val maxMajorAarVersion =
Revision.parseRevision(checkAarMetadataWorkParameters.aarFormatVersion.get())
.major
if (majorAarVersion > maxMajorAarVersion) {
throw RuntimeException(
"""
The $AAR_FORMAT_VERSION_PROPERTY ($aarFormatVersion) specified in a
dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH})
is not compatible with this version of the Android Gradle Plugin.
Please upgrade to a newer version of the Android Gradle Plugin.
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
}
} catch (e: NumberFormatException) {
throw RuntimeException(
"""
A dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH})
has an invalid $AAR_FORMAT_VERSION_PROPERTY value.
${e.message}
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
}
}
// check aarMetadataVersion
val aarMetadataVersion = aarMetadataReader.aarMetadataVersion
if (aarMetadataVersion == null) {
throw RuntimeException(
"""
A dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH}) does
not specify an $AAR_METADATA_VERSION_PROPERTY value, which is a required value.
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
} else {
try {
val majorAarMetadataVersion = Revision.parseRevision(aarMetadataVersion).major
val maxMajorAarMetadataVersion =
Revision.parseRevision(checkAarMetadataWorkParameters.aarMetadataVersion.get())
.major
if (majorAarMetadataVersion > maxMajorAarMetadataVersion) {
throw RuntimeException(
"""
The $AAR_METADATA_VERSION_PROPERTY ($aarMetadataVersion) specified in a
dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH})
is not compatible with this version of the Android Gradle Plugin.
Please upgrade to a newer version of the Android Gradle Plugin.
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
}
} catch (e: NumberFormatException) {
throw RuntimeException(
"""
A dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH})
has an invalid $AAR_METADATA_VERSION_PROPERTY value.
${e.message}
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
}
}
// check compileSdkVersion
val minCompileSdk = aarMetadataReader.minCompileSdk
if (minCompileSdk != null) {
val minCompileSdkInt = minCompileSdk.toIntOrNull()
if (minCompileSdkInt == null) {
throw RuntimeException(
"""
A dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH})
has an invalid $MIN_COMPILE_SDK_PROPERTY value. $MIN_COMPILE_SDK_PROPERTY
must be an integer.
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
} else {
val compileSdkVersion = checkAarMetadataWorkParameters.compileSdkVersion.get()
val compileSdkVersionInt = getApiIntFromString(compileSdkVersion)
if (minCompileSdkInt > compileSdkVersionInt) {
throw RuntimeException(
"""
The $MIN_COMPILE_SDK_PROPERTY ($minCompileSdk) specified in a
dependency's AAR metadata (${AarMetadataTask.AAR_METADATA_ENTRY_PATH})
is greater than this module's compileSdkVersion ($compileSdkVersion).
Dependency: ${checkAarMetadataWorkParameters.displayName.get()}.
AAR metadata file: ${aarMetadataFile.absolutePath}.
""".trimIndent()
)
}
}
}
}
/**
* Return the [Int] API version, given a string representation, or
* [SdkVersionInfo.HIGHEST_KNOWN_API] + 1 if an unknown version.
*/
private fun getApiIntFromString(sdkVersion: String): Int =
sdkVersion.removePrefix("android-").toIntOrNull()
?: SdkVersionInfo.getApiByPreviewName(sdkVersion, true)
}
/** [WorkParameters] for [CheckAarMetadataWorkAction] */
abstract class CheckAarMetadataWorkParameters: WorkParameters {
abstract val aarMetadataFile: RegularFileProperty
abstract val displayName: Property<String>
abstract val aarFormatVersion: Property<String>
abstract val aarMetadataVersion: Property<String>
abstract val compileSdkVersion: Property<String>
}
private data class AarMetadataReader(val file: File) {
val aarFormatVersion: String?
val aarMetadataVersion: String?
val minCompileSdk: String?
init {
val properties = Properties()
file.inputStream().use { properties.load(it) }
aarFormatVersion = properties.getProperty(AAR_FORMAT_VERSION_PROPERTY)
aarMetadataVersion = properties.getProperty(AAR_METADATA_VERSION_PROPERTY)
minCompileSdk = properties.getProperty(MIN_COMPILE_SDK_PROPERTY)
}
}