blob: 64b595c53d2c5890c874d43c59455ca4593c2a76 [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.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 com.android.build.gradle.api.LibraryVariant
import com.android.build.gradle.tasks.ProcessLibraryManifest
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.compile.JavaCompile
import java.io.File
const val CREATE_STUB_API_JAR_TASK = "createStubApiJar"
object MetalavaTasks {
// flatMap is marked unstable, but it's required for lazy eval
@Suppress("UnstableApiUsage")
fun setupProject(
project: Project,
javaCompileInputs: JavaCompileInputs,
extension: AndroidXExtension,
processManifest: ProcessLibraryManifest? = null,
baselinesApiLocation: ApiBaselinesLocation,
builtApiLocation: ApiLocation,
outputApiLocations: List<ApiLocation>
) {
val metalavaConfiguration = project.getMetalavaConfiguration()
// 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 generateApi = project.tasks.register("generateApi", GenerateApiTask::class.java) {
task ->
task.group = "API"
task.description = "Generates API files from source"
task.apiLocation.set(builtApiLocation)
task.configuration = metalavaConfiguration
task.generateRestrictToLibraryGroupAPIs = generateRestrictToLibraryGroupAPIs
task.baselines.set(baselinesApiLocation)
task.dependsOn(metalavaConfiguration)
processManifest?.let {
task.manifestPath.set(processManifest.manifestOutputFile)
}
applyInputs(javaCompileInputs, task)
}
// 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.configuration = metalavaConfiguration
task.referenceApi.set(lastReleasedApiFile)
task.baselines.set(baselinesApiLocation)
task.dependsOn(metalavaConfiguration)
task.api.set(builtApiLocation)
task.dependencyClasspath = javaCompileInputs.dependencyClasspath
task.bootClasspath = javaCompileInputs.bootClasspath
task.cacheEvenIfNoOutputs()
task.dependsOn(generateApi)
}
ignoreApiChanges = project.tasks.register(
"ignoreApiChanges",
IgnoreApiChangesTask::class.java
) { task ->
task.configuration = metalavaConfiguration
task.referenceApi.set(checkApiRelease!!.flatMap { it.referenceApi })
task.baselines.set(checkApiRelease!!.flatMap { it.baselines })
task.api.set(builtApiLocation)
task.dependencyClasspath = javaCompileInputs.dependencyClasspath
task.bootClasspath = javaCompileInputs.bootClasspath
task.dependsOn(generateApi)
}
}
project.tasks.register(
"updateApiLintBaseline",
UpdateApiLintBaselineTask::class.java
) { task ->
task.configuration = metalavaConfiguration
task.baselines.set(baselinesApiLocation)
processManifest?.let {
task.manifestPath.set(processManifest.manifestOutputFile)
}
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
generateApi.get().mustRunAfter(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.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) }
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)
}
private fun applyInputs(inputs: JavaCompileInputs, task: MetalavaTask) {
task.sourcePaths = inputs.sourcePaths.files
task.dependsOn(inputs.sourcePaths)
task.dependencyClasspath = inputs.dependencyClasspath
task.bootClasspath = inputs.bootClasspath
}
@Suppress("unused")
private fun setupStubs(
project: Project,
javaCompileInputs: JavaCompileInputs,
variant: LibraryVariant
) {
if (hasKotlinCode(project, variant)) return
val apiStubsDirectory = File(project.buildDir, "stubs/api")
val docsStubsDirectory = File(project.buildDir, "stubs/docs")
val generateApiStubClasses = project.tasks.register(
"generateApiStubClasses",
GenerateApiStubClassesTask::class.java
) { task ->
task.apiStubsDirectory.set(apiStubsDirectory)
task.docStubsDirectory.set(docsStubsDirectory)
task.configuration = project.getMetalavaConfiguration()
applyInputs(javaCompileInputs, task)
}
val apiStubClassesDirectory = File(project.buildDir, "stubs/api_classes")
val compileStubClasses = project.tasks.register(
"compileApiStubClasses",
JavaCompile::class.java
) { task ->
@Suppress("DEPRECATION") val compileTask = variant.javaCompile
task.source = project.files(apiStubsDirectory).asFileTree
task.destinationDir = apiStubClassesDirectory
task.classpath = compileTask.classpath
task.options.compilerArgs = compileTask.options.compilerArgs
task.options.bootstrapClasspath = compileTask.options.bootstrapClasspath
task.sourceCompatibility = compileTask.sourceCompatibility
task.targetCompatibility = compileTask.targetCompatibility
task.dependsOn(generateApiStubClasses)
task.dependsOn(compileTask)
}
val apiStubsJar = project.tasks.register(
CREATE_STUB_API_JAR_TASK,
Zip::class.java
) { task ->
task.from(apiStubClassesDirectory)
task.destinationDirectory.set(project.buildDir)
task.archiveFileName.set("api.jar")
task.dependsOn(compileStubClasses)
}
project.addToBuildOnServer(apiStubsJar)
/*
TODO: Enable packaging api.jar inside aars.
project.tasks.withType(BundleAar::class.java) { task ->
task.dependsOn(packageStubs)
task.from(File(project.buildDir, "api.jar"))
}
*/
}
private fun hasKotlinCode(project: Project, variant: LibraryVariant): Boolean {
return project.files(variant.sourceSets.flatMap { it.javaDirectories })
.asFileTree
.files
.filter { it.extension == "kt" }
.isNotEmpty()
}
}