1 /*
2 * Copyright 2019 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.Version
20 import androidx.build.checkapi.ApiBaselinesLocation
21 import androidx.build.checkapi.ApiLocation
22 import java.io.File
23 import javax.inject.Inject
24 import org.gradle.api.file.FileCollection
25 import org.gradle.api.provider.Property
26 import org.gradle.api.tasks.CacheableTask
27 import org.gradle.api.tasks.Input
28 import org.gradle.api.tasks.InputFiles
29 import org.gradle.api.tasks.OutputFile
30 import org.gradle.api.tasks.OutputFiles
31 import org.gradle.api.tasks.PathSensitive
32 import org.gradle.api.tasks.PathSensitivity
33 import org.gradle.api.tasks.TaskAction
34 import org.gradle.workers.WorkerExecutor
35
36 @CacheableTask
37 internal abstract class UpdateApiLintBaselineTask
38 @Inject
39 constructor(workerExecutor: WorkerExecutor) : SourceMetalavaTask(workerExecutor) {
40 init {
41 group = "API"
42 description =
43 "Updates an API lint baseline file (api/api_lint.ignore) to match the " +
44 "current set of violations. Only use a baseline " +
45 "if you are in a library without Android dependencies, or when enabling a new " +
46 "lint check, and it is prohibitively expensive / not possible to fix the errors " +
47 "generated by enabling this lint check. "
48 }
49
getOutputApiLintBaselinenull50 @OutputFile fun getOutputApiLintBaseline(): File = baselines.get().apiLintFile
51
52 @TaskAction
53 fun updateBaseline() {
54 check(bootClasspath.files.isNotEmpty()) { "Android boot classpath not set." }
55 val baselineFile = baselines.get().apiLintFile
56 val checkArgs =
57 getGenerateApiArgs(
58 createProjectXmlFile(),
59 sourcePaths.files.filter { it.exists() },
60 null,
61 GenerateApiMode.PublicApi,
62 ApiLintMode.CheckBaseline(baselineFile, targetsJavaConsumers.get()),
63 // API version history doesn't need to be generated
64 emptyList(),
65 manifestPath.orNull?.asFile?.absolutePath
66 )
67 val args = checkArgs + getCommonBaselineUpdateArgs(baselineFile)
68
69 runWithArgs(args)
70 }
71 }
72
73 @CacheableTask
74 abstract class IgnoreApiChangesTask @Inject constructor(workerExecutor: WorkerExecutor) :
75 MetalavaTask(workerExecutor) {
<lambda>null76 init {
77 description =
78 "Updates an API tracking baseline file (api/X.Y.Z.ignore) to match the " +
79 "current set of violations"
80 }
81
82 // The API that the library is supposed to be compatible with
83 @get:Input abstract val referenceApi: Property<ApiLocation>
84
85 @get:Input abstract val api: Property<ApiLocation>
86
87 // The baseline files (api/*.*.*.ignore) to update
88 @get:Input abstract val baselines: Property<ApiBaselinesLocation>
89
90 // Version for the current API surface.
91 @get:Input abstract val version: Property<Version>
92
93 @[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
getTaskInputsnull94 fun getTaskInputs(): List<File> {
95 val referenceApiLocation = referenceApi.get()
96 return listOf(referenceApiLocation.publicApiFile, referenceApiLocation.restrictedApiFile)
97 }
98
99 // Declaring outputs prevents Gradle from rerunning this task if the inputs haven't changed
100 @OutputFiles
getTaskOutputsnull101 fun getTaskOutputs(): List<File>? {
102 val apiBaselinesLocation = baselines.get()
103 return listOf(apiBaselinesLocation.publicApiFile, apiBaselinesLocation.restrictedApiFile)
104 }
105
106 @TaskAction
execnull107 fun exec() {
108 check(bootClasspath.files.isNotEmpty()) { "Android boot classpath not set." }
109
110 val apiLocation = api.get()
111 val referenceApiLocation = referenceApi.get()
112 val freezeApis = shouldFreezeApis(referenceApiLocation.version(), version.get())
113 updateBaseline(
114 apiLocation.publicApiFile,
115 referenceApiLocation.publicApiFile,
116 baselines.get().publicApiFile,
117 false,
118 freezeApis
119 )
120 if (referenceApiLocation.restrictedApiFile.exists()) {
121 updateBaseline(
122 apiLocation.restrictedApiFile,
123 referenceApiLocation.restrictedApiFile,
124 baselines.get().restrictedApiFile,
125 true,
126 freezeApis
127 )
128 }
129 }
130
131 // Updates the contents of baselineFile to specify an exception for every API present in apiFile
132 // but not
133 // present in the current source path
updateBaselinenull134 private fun updateBaseline(
135 api: File,
136 prevApi: File,
137 baselineFile: File,
138 processRestrictedApis: Boolean,
139 freezeApis: Boolean,
140 ) {
141 val args = getCommonBaselineUpdateArgs(bootClasspath, dependencyClasspath, baselineFile)
142 args +=
143 listOf(
144 "--baseline",
145 baselineFile.toString(),
146 "--check-compatibility:api:released",
147 prevApi.toString(),
148 "--source-files",
149 api.toString()
150 )
151 if (freezeApis) {
152 args += listOf("--error-category", "Compatibility")
153 }
154 if (processRestrictedApis) {
155 args +=
156 listOf(
157 "--show-annotation",
158 "androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope." +
159 "LIBRARY_GROUP)",
160 "--show-annotation",
161 "androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope." +
162 "LIBRARY_GROUP_PREFIX)",
163 "--show-unannotated"
164 )
165 }
166 runWithArgs(args)
167 }
168 }
169
getCommonBaselineUpdateArgsnull170 private fun getCommonBaselineUpdateArgs(
171 bootClasspath: FileCollection,
172 dependencyClasspath: FileCollection,
173 baselineFile: File
174 ): MutableList<String> {
175 val args =
176 mutableListOf(
177 "--classpath",
178 (bootClasspath.files + dependencyClasspath.files).joinToString(File.pathSeparator)
179 )
180 args += getCommonBaselineUpdateArgs(baselineFile)
181 return args
182 }
183
getCommonBaselineUpdateArgsnull184 private fun getCommonBaselineUpdateArgs(baselineFile: File): List<String> {
185 // Create the baseline file if it does exist, as Metalava cannot handle non-existent files.
186 baselineFile.createNewFile()
187 return mutableListOf(
188 "--update-baseline",
189 baselineFile.toString(),
190 "--pass-baseline-updates",
191 "--delete-empty-baselines",
192 "--format=v4"
193 )
194 }
195