blob: 38f77323ad8c483ba1a3169ddd6229bea6a9d864 [file] [log] [blame]
/*
* Copyright 2018 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.AndroidXExtension
import androidx.build.LibraryType
import androidx.build.addFilterableTasks
import androidx.build.addToBuildOnServer
import androidx.build.addToCheckTask
import androidx.build.checkapi.ApiBaselinesLocation
import androidx.build.checkapi.ApiLocation
import androidx.build.checkapi.getRequiredCompatibilityApiLocation
import androidx.build.java.JavaCompileInputs
import androidx.build.uptodatedness.cacheEvenIfNoOutputs
import androidx.build.version
import org.gradle.api.Project
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
object MetalavaTasks {
fun setupProject(
project: Project,
javaCompileInputs: JavaCompileInputs,
extension: AndroidXExtension,
androidManifest: Provider<RegularFile>?,
baselinesApiLocation: ApiBaselinesLocation,
builtApiLocation: ApiLocation,
outputApiLocations: List<ApiLocation>
) {
val metalavaClasspath = project.getMetalavaClasspath()
val version = project.version()
// Policy: If the artifact belongs to an atomic (e.g. same-version) group, we don't enforce
// binary compatibility for APIs annotated with @RestrictTo(LIBRARY_GROUP). This is
// implemented by excluding APIs with this annotation from the restricted API file.
val generateRestrictToLibraryGroupAPIs = !extension.mavenGroup!!.requireSameVersion
val kotlinSourceLevel: Provider<KotlinVersion> = extension.kotlinApiVersion
val targetsJavaConsumers = (extension.type != LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY &&
extension.type != LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY
)
val generateApi =
project.tasks.register("generateApi", GenerateApiTask::class.java) { task ->
task.group = "API"
task.description = "Generates API files from source"
task.apiLocation.set(builtApiLocation)
task.metalavaClasspath.from(metalavaClasspath)
task.generateRestrictToLibraryGroupAPIs = generateRestrictToLibraryGroupAPIs
task.baselines.set(baselinesApiLocation)
task.targetsJavaConsumers = targetsJavaConsumers
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
task.kotlinSourceLevel.set(kotlinSourceLevel)
// Arguments needed for generating the API levels JSON
task.projectApiDirectory = project.layout.projectDirectory.dir("api")
task.currentVersion.set(version)
androidManifest?.let { task.manifestPath.set(it) }
applyInputs(javaCompileInputs, task)
// If we will be updating the api lint baselines, then we should do that before
// using it to validate the generated api
task.mustRunAfter("updateApiLintBaseline")
}
project.registerVersionMetadataComponent(generateApi)
// Policy: If the artifact has previously been released, e.g. has a beta or later API file
// checked in, then we must verify "release compatibility" against the work-in-progress
// API file.
var checkApiRelease: TaskProvider<CheckApiCompatibilityTask>? = null
var ignoreApiChanges: TaskProvider<IgnoreApiChangesTask>? = null
project.getRequiredCompatibilityApiLocation()?.let { lastReleasedApiFile ->
checkApiRelease =
project.tasks.register("checkApiRelease", CheckApiCompatibilityTask::class.java) {
task ->
task.metalavaClasspath.from(metalavaClasspath)
task.referenceApi.set(lastReleasedApiFile)
task.baselines.set(baselinesApiLocation)
task.api.set(builtApiLocation)
task.version.set(version)
task.dependencyClasspath = javaCompileInputs.dependencyClasspath
task.bootClasspath = javaCompileInputs.bootClasspath
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
task.kotlinSourceLevel.set(kotlinSourceLevel)
task.cacheEvenIfNoOutputs()
task.dependsOn(generateApi)
}
ignoreApiChanges =
project.tasks.register("ignoreApiChanges", IgnoreApiChangesTask::class.java) { task
->
task.metalavaClasspath.from(metalavaClasspath)
task.referenceApi.set(checkApiRelease!!.flatMap { it.referenceApi })
task.baselines.set(checkApiRelease!!.flatMap { it.baselines })
task.api.set(builtApiLocation)
task.version.set(version)
task.dependencyClasspath = javaCompileInputs.dependencyClasspath
task.bootClasspath = javaCompileInputs.bootClasspath
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
task.kotlinSourceLevel.set(kotlinSourceLevel)
task.dependsOn(generateApi)
}
}
val updateApiLintBaseline =
project.tasks.register(
"updateApiLintBaseline",
UpdateApiLintBaselineTask::class.java
) { task ->
task.metalavaClasspath.from(metalavaClasspath)
task.baselines.set(baselinesApiLocation)
task.targetsJavaConsumers.set(targetsJavaConsumers)
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
task.kotlinSourceLevel.set(kotlinSourceLevel)
androidManifest?.let { task.manifestPath.set(it) }
applyInputs(javaCompileInputs, task)
}
// Policy: All changes to API surfaces for which compatibility is enforced must be
// explicitly confirmed by running the updateApi task. To enforce this, the implementation
// checks the "work-in-progress" built API file against the checked in current API file.
val checkApi =
project.tasks.register("checkApi", CheckApiEquivalenceTask::class.java) { task ->
task.group = "API"
task.description =
"Checks that the API generated from source code matches the " +
"checked in API file"
task.builtApi.set(generateApi.flatMap { it.apiLocation })
task.cacheEvenIfNoOutputs()
task.checkedInApis.set(outputApiLocations)
task.dependsOn(generateApi)
checkApiRelease?.let { task.dependsOn(checkApiRelease) }
}
val regenerateOldApis =
project.tasks.register("regenerateOldApis", RegenerateOldApisTask::class.java) { task ->
task.group = "API"
task.description =
"Regenerates historic API .txt files using the " +
"corresponding prebuilt and the latest Metalava"
task.kotlinSourceLevel.set(kotlinSourceLevel)
task.generateRestrictToLibraryGroupAPIs = generateRestrictToLibraryGroupAPIs
}
// ignoreApiChanges depends on the output of this task for the "last released" API
// surface. Make sure it always runs *after* the regenerateOldApis task.
ignoreApiChanges?.configure { it.mustRunAfter(regenerateOldApis) }
// checkApiRelease validates the output of this task, so make sure it always runs
// *after* the regenerateOldApis task.
checkApiRelease?.configure { it.mustRunAfter(regenerateOldApis) }
val updateApi =
project.tasks.register("updateApi", UpdateApiTask::class.java) { task ->
task.group = "API"
task.description = "Updates the checked in API files to match source code API"
task.inputApiLocation.set(generateApi.flatMap { it.apiLocation })
task.outputApiLocations.set(checkApi.flatMap { it.checkedInApis })
task.dependsOn(generateApi)
// If a developer (accidentally) makes a non-backwards compatible change to an API,
// the developer will want to be informed of it as soon as possible. So, whenever a
// developer updates an API, if backwards compatibility checks are enabled in the
// library, then we want to check that the changes are backwards compatible.
checkApiRelease?.let { task.dependsOn(it) }
}
// ignoreApiChanges depends on the output of this task for the "current" API surface.
// Make sure it always runs *after* the updateApi task.
ignoreApiChanges?.configure { it.mustRunAfter(updateApi) }
val regenerateApis =
project.tasks.register("regenerateApis") { task ->
task.group = "API"
task.description =
"Regenerates current and historic API .txt files using the corresponding " +
"prebuilt and the latest Metalava, then updates API ignore files"
task.dependsOn(regenerateOldApis)
task.dependsOn(updateApi)
ignoreApiChanges?.let { task.dependsOn(it) }
}
project.addToCheckTask(checkApi)
project.addToBuildOnServer(checkApi)
project.addFilterableTasks(
ignoreApiChanges,
updateApiLintBaseline,
checkApi,
regenerateOldApis,
updateApi,
regenerateApis,
generateApi
)
}
private fun applyInputs(inputs: JavaCompileInputs, task: MetalavaTask) {
task.sourcePaths = inputs.sourcePaths
task.dependsOn(inputs.sourcePaths)
task.dependencyClasspath = inputs.dependencyClasspath
task.bootClasspath = inputs.bootClasspath
}
}