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