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.metalava 18 19 import androidx.build.AndroidXExtension 20 import androidx.build.addFilterableTasks 21 import androidx.build.addToBuildOnServer 22 import androidx.build.addToCheckTask 23 import androidx.build.checkapi.ApiBaselinesLocation 24 import androidx.build.checkapi.ApiLocation 25 import androidx.build.checkapi.CompilationInputs 26 import androidx.build.checkapi.MultiplatformCompilationInputs 27 import androidx.build.checkapi.SourceSetInputs 28 import androidx.build.checkapi.getRequiredCompatibilityApiLocation 29 import androidx.build.uptodatedness.cacheEvenIfNoOutputs 30 import androidx.build.version 31 import org.gradle.api.Project 32 import org.gradle.api.artifacts.Configuration 33 import org.gradle.api.file.RegularFile 34 import org.gradle.api.provider.Provider 35 import org.gradle.api.tasks.TaskProvider 36 import org.jetbrains.kotlin.gradle.dsl.KotlinVersion 37 38 internal object MetalavaTasks { 39 40 fun setupProject( 41 project: Project, 42 compilationInputs: CompilationInputs, 43 generateApiDependencies: Configuration, 44 extension: AndroidXExtension, 45 androidManifest: Provider<RegularFile>?, 46 baselinesApiLocation: ApiBaselinesLocation, 47 builtApiLocation: ApiLocation, 48 outputApiLocations: List<ApiLocation> 49 ) { 50 val metalavaClasspath = project.getMetalavaClasspath() 51 val version = project.version() 52 53 // Policy: If the artifact belongs to an atomic (e.g. same-version) group, we don't enforce 54 // binary compatibility for APIs annotated with @RestrictTo(LIBRARY_GROUP). This is 55 // implemented by excluding APIs with this annotation from the restricted API file. 56 val generateRestrictToLibraryGroupAPIs = !extension.mavenGroup!!.requireSameVersion 57 val kotlinSourceLevel: Provider<KotlinVersion> = extension.kotlinApiVersion 58 val targetsJavaConsumers = !extension.type.targetsKotlinConsumersOnly 59 val generateApi = 60 project.tasks.register("generateApi", GenerateApiTask::class.java) { task -> 61 task.group = "API" 62 task.description = "Generates API files from source" 63 task.apiLocation.set(builtApiLocation) 64 task.metalavaClasspath.from(metalavaClasspath) 65 task.generateRestrictToLibraryGroupAPIs = generateRestrictToLibraryGroupAPIs 66 task.baselines.set(baselinesApiLocation) 67 task.targetsJavaConsumers.set(targetsJavaConsumers) 68 task.k2UastEnabled.set(extension.metalavaK2UastEnabled) 69 task.kotlinSourceLevel.set(kotlinSourceLevel) 70 71 // Arguments needed for generating the API levels JSON 72 task.projectApiDirectory = project.layout.projectDirectory.dir("api") 73 task.currentVersion.set(version) 74 75 applyInputs(compilationInputs, task, generateApiDependencies, androidManifest) 76 // If we will be updating the api lint baselines, then we should do that before 77 // using it to validate the generated api 78 task.mustRunAfter("updateApiLintBaseline") 79 } 80 project.registerVersionMetadataComponent(generateApi) 81 82 // Policy: If the artifact has previously been released, e.g. has a beta or later API file 83 // checked in, then we must verify "release compatibility" against the work-in-progress 84 // API file. 85 var checkApiRelease: TaskProvider<CheckApiCompatibilityTask>? = null 86 var ignoreApiChanges: TaskProvider<IgnoreApiChangesTask>? = null 87 project.getRequiredCompatibilityApiLocation()?.let { lastReleasedApiFile -> 88 checkApiRelease = 89 project.tasks.register("checkApiRelease", CheckApiCompatibilityTask::class.java) { 90 task -> 91 task.metalavaClasspath.from(metalavaClasspath) 92 task.referenceApi.set(lastReleasedApiFile) 93 task.baselines.set(baselinesApiLocation) 94 task.api.set(builtApiLocation) 95 task.version.set(version) 96 task.dependencyClasspath = compilationInputs.dependencyClasspath 97 task.bootClasspath = compilationInputs.bootClasspath 98 task.k2UastEnabled.set(extension.metalavaK2UastEnabled) 99 task.kotlinSourceLevel.set(kotlinSourceLevel) 100 task.cacheEvenIfNoOutputs() 101 task.dependsOn(generateApi) 102 } 103 104 ignoreApiChanges = 105 project.tasks.register("ignoreApiChanges", IgnoreApiChangesTask::class.java) { task 106 -> 107 task.metalavaClasspath.from(metalavaClasspath) 108 task.referenceApi.set(checkApiRelease!!.flatMap { it.referenceApi }) 109 task.baselines.set(checkApiRelease!!.flatMap { it.baselines }) 110 task.api.set(builtApiLocation) 111 task.version.set(version) 112 task.dependencyClasspath = compilationInputs.dependencyClasspath 113 task.bootClasspath = compilationInputs.bootClasspath 114 task.k2UastEnabled.set(extension.metalavaK2UastEnabled) 115 task.kotlinSourceLevel.set(kotlinSourceLevel) 116 task.dependsOn(generateApi) 117 } 118 } 119 120 val updateApiLintBaseline = 121 project.tasks.register( 122 "updateApiLintBaseline", 123 UpdateApiLintBaselineTask::class.java 124 ) { task -> 125 task.metalavaClasspath.from(metalavaClasspath) 126 task.baselines.set(baselinesApiLocation) 127 task.targetsJavaConsumers.set(targetsJavaConsumers) 128 task.k2UastEnabled.set(extension.metalavaK2UastEnabled) 129 task.kotlinSourceLevel.set(kotlinSourceLevel) 130 applyInputs(compilationInputs, task, generateApiDependencies, androidManifest) 131 } 132 133 // Policy: All changes to API surfaces for which compatibility is enforced must be 134 // explicitly confirmed by running the updateApi task. To enforce this, the implementation 135 // checks the "work-in-progress" built API file against the checked in current API file. 136 val checkApi = 137 project.tasks.register("checkApi", CheckApiEquivalenceTask::class.java) { task -> 138 task.group = "API" 139 task.description = 140 "Checks that the API generated from source code matches the " + 141 "checked in API file" 142 task.builtApi.set(generateApi.flatMap { it.apiLocation }) 143 task.cacheEvenIfNoOutputs() 144 task.checkedInApis.set(outputApiLocations) 145 task.dependsOn(generateApi) 146 checkApiRelease?.let { task.dependsOn(checkApiRelease) } 147 } 148 149 val regenerateOldApis = 150 project.tasks.register("regenerateOldApis", RegenerateOldApisTask::class.java) { task -> 151 task.group = "API" 152 task.description = 153 "Regenerates historic API .txt files using the " + 154 "corresponding prebuilt and the latest Metalava" 155 task.kotlinSourceLevel.set(kotlinSourceLevel) 156 task.generateRestrictToLibraryGroupAPIs = generateRestrictToLibraryGroupAPIs 157 } 158 159 // ignoreApiChanges depends on the output of this task for the "last released" API 160 // surface. Make sure it always runs *after* the regenerateOldApis task. 161 ignoreApiChanges?.configure { it.mustRunAfter(regenerateOldApis) } 162 163 // checkApiRelease validates the output of this task, so make sure it always runs 164 // *after* the regenerateOldApis task. 165 checkApiRelease?.configure { it.mustRunAfter(regenerateOldApis) } 166 167 val updateApi = 168 project.tasks.register("updateApi", UpdateApiTask::class.java) { task -> 169 task.group = "API" 170 task.description = "Updates the checked in API files to match source code API" 171 task.inputApiLocation.set(generateApi.flatMap { it.apiLocation }) 172 task.outputApiLocations.set(checkApi.flatMap { it.checkedInApis }) 173 task.dependsOn(generateApi) 174 175 // If a developer (accidentally) makes a non-backwards compatible change to an API, 176 // the developer will want to be informed of it as soon as possible. So, whenever a 177 // developer updates an API, if backwards compatibility checks are enabled in the 178 // library, then we want to check that the changes are backwards compatible. 179 checkApiRelease?.let { task.dependsOn(it) } 180 } 181 182 // ignoreApiChanges depends on the output of this task for the "current" API surface. 183 // Make sure it always runs *after* the updateApi task. 184 ignoreApiChanges?.configure { it.mustRunAfter(updateApi) } 185 186 val regenerateApis = 187 project.tasks.register("regenerateApis") { task -> 188 task.group = "API" 189 task.description = 190 "Regenerates current and historic API .txt files using the corresponding " + 191 "prebuilt and the latest Metalava, then updates API ignore files" 192 task.dependsOn(regenerateOldApis) 193 task.dependsOn(updateApi) 194 ignoreApiChanges?.let { task.dependsOn(it) } 195 } 196 197 project.addToCheckTask(checkApi) 198 project.addToBuildOnServer(checkApi) 199 project.addFilterableTasks( 200 ignoreApiChanges, 201 updateApiLintBaseline, 202 checkApi, 203 regenerateOldApis, 204 updateApi, 205 regenerateApis, 206 generateApi 207 ) 208 } 209 210 private fun applyInputs( 211 inputs: CompilationInputs, 212 task: SourceMetalavaTask, 213 generateApiDependencies: Configuration, 214 androidManifest: Provider<RegularFile>?, 215 ) { 216 task.sourcePaths = inputs.sourcePaths 217 task.compiledSources = generateApiDependencies 218 task.dependencyClasspath = inputs.dependencyClasspath 219 task.bootClasspath = inputs.bootClasspath 220 androidManifest?.let { task.manifestPath.set(it) } 221 if (inputs is MultiplatformCompilationInputs) { 222 task.sourceSets.set(inputs.sourceSets) 223 } else { 224 // Represent a non-multiplatform project as one source set. 225 task.sourceSets.set( 226 listOf( 227 SourceSetInputs( 228 sourceSetName = "main", 229 dependsOnSourceSets = emptyList(), 230 sourcePaths = inputs.sourcePaths, 231 dependencyClasspath = inputs.dependencyClasspath 232 ) 233 ) 234 ) 235 } 236 } 237 } 238