blob: 29bb09fa22eb6db107a709195293b364f0ab570f [file] [log] [blame]
/*
* Copyright 2019 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 androidx.build.metalava
import androidx.build.Version
import androidx.build.checkapi.ApiBaselinesLocation
import androidx.build.checkapi.ApiLocation
import java.io.File
import javax.inject.Inject
import org.gradle.api.file.FileCollection
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.OutputFile
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class UpdateApiLintBaselineTask @Inject constructor(workerExecutor: WorkerExecutor) :
MetalavaTask(workerExecutor) {
init {
group = "API"
description =
"Updates an API lint baseline file (api/api_lint.ignore) to match the " +
"current set of violations. Only use a baseline " +
"if you are in a library without Android dependencies, or when enabling a new " +
"lint check, and it is prohibitively expensive / not possible to fix the errors " +
"generated by enabling this lint check. "
}
@get:Input abstract val baselines: Property<ApiBaselinesLocation>
@get:Input abstract val targetsJavaConsumers: Property<Boolean>
@OutputFile fun getApiLintBaseline(): File = baselines.get().apiLintFile
@TaskAction
fun updateBaseline() {
check(bootClasspath.files.isNotEmpty()) { "Android boot classpath not set." }
val baselineFile = baselines.get().apiLintFile
val checkArgs =
getGenerateApiArgs(
bootClasspath,
dependencyClasspath,
sourcePaths.files.filter { it.exists() },
null,
GenerateApiMode.PublicApi,
ApiLintMode.CheckBaseline(baselineFile, targetsJavaConsumers.get()),
// API version history doesn't need to be generated
emptyList(),
manifestPath.orNull?.asFile?.absolutePath
)
val args = checkArgs + getCommonBaselineUpdateArgs(baselineFile)
runWithArgs(args)
}
}
@CacheableTask
abstract class IgnoreApiChangesTask @Inject constructor(workerExecutor: WorkerExecutor) :
MetalavaTask(workerExecutor) {
init {
description =
"Updates an API tracking baseline file (api/X.Y.Z.ignore) to match the " +
"current set of violations"
}
// The API that the library is supposed to be compatible with
@get:Input abstract val referenceApi: Property<ApiLocation>
@get:Input abstract val api: Property<ApiLocation>
// The baseline files (api/*.*.*.ignore) to update
@get:Input abstract val baselines: Property<ApiBaselinesLocation>
// Version for the current API surface.
@get:Input abstract val version: Property<Version>
@[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
fun getTaskInputs(): List<File> {
val referenceApiLocation = referenceApi.get()
return listOf(referenceApiLocation.publicApiFile, referenceApiLocation.restrictedApiFile)
}
// Declaring outputs prevents Gradle from rerunning this task if the inputs haven't changed
@OutputFiles
fun getTaskOutputs(): List<File>? {
val apiBaselinesLocation = baselines.get()
return listOf(apiBaselinesLocation.publicApiFile, apiBaselinesLocation.restrictedApiFile)
}
@TaskAction
fun exec() {
check(bootClasspath.files.isNotEmpty()) { "Android boot classpath not set." }
val apiLocation = api.get()
val referenceApiLocation = referenceApi.get()
val freezeApis = shouldFreezeApis(referenceApiLocation.version(), version.get())
updateBaseline(
apiLocation.publicApiFile,
referenceApiLocation.publicApiFile,
baselines.get().publicApiFile,
false,
freezeApis
)
if (referenceApiLocation.restrictedApiFile.exists()) {
updateBaseline(
apiLocation.restrictedApiFile,
referenceApiLocation.restrictedApiFile,
baselines.get().restrictedApiFile,
true,
freezeApis
)
}
}
// Updates the contents of baselineFile to specify an exception for every API present in apiFile
// but not
// present in the current source path
private fun updateBaseline(
api: File,
prevApi: File,
baselineFile: File,
processRestrictedApis: Boolean,
freezeApis: Boolean,
) {
val args = getCommonBaselineUpdateArgs(bootClasspath, dependencyClasspath, baselineFile)
args +=
listOf(
"--baseline",
baselineFile.toString(),
"--check-compatibility:api:released",
prevApi.toString(),
"--source-files",
api.toString()
)
if (freezeApis) {
args += listOf("--error-category", "Compatibility")
}
if (processRestrictedApis) {
args +=
listOf(
"--show-annotation",
"androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope." +
"LIBRARY_GROUP)",
"--show-annotation",
"androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope." +
"LIBRARY_GROUP_PREFIX)",
"--show-unannotated"
)
}
runWithArgs(args)
}
}
private fun getCommonBaselineUpdateArgs(
bootClasspath: FileCollection,
dependencyClasspath: FileCollection,
baselineFile: File
): MutableList<String> {
val args =
mutableListOf(
"--classpath",
(bootClasspath.files + dependencyClasspath.files).joinToString(File.pathSeparator)
)
args += getCommonBaselineUpdateArgs(baselineFile)
return args
}
private fun getCommonBaselineUpdateArgs(baselineFile: File): List<String> {
// Create the baseline file if it does exist, as Metalava cannot handle non-existent files.
baselineFile.createNewFile()
return mutableListOf(
"--update-baseline",
baselineFile.toString(),
"--pass-baseline-updates",
"--delete-empty-baselines",
"--format=v4"
)
}