1 /*
2 * Copyright (C) 2024 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 com.android.tools.metalava
18
19 import com.android.tools.metalava.cli.common.MetalavaCliException
20 import com.android.tools.metalava.config.ApiSurfaceConfig
21 import com.android.tools.metalava.config.ApiSurfacesConfig
22 import com.android.tools.metalava.model.annotation.AnnotationFilter
23 import com.android.tools.metalava.model.api.surface.ApiSurface
24 import com.android.tools.metalava.model.api.surface.ApiSurfaces
25 import com.github.ajalt.clikt.parameters.groups.OptionGroup
26 import com.github.ajalt.clikt.parameters.options.defaultLazy
27 import com.github.ajalt.clikt.parameters.options.multiple
28 import com.github.ajalt.clikt.parameters.options.option
29 import com.github.ajalt.clikt.parameters.options.switch
30
31 const val ARG_API_SURFACE = "--api-surface"
32 const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
33 const val ARG_SHOW_ANNOTATION = "--show-annotation"
34 const val ARG_SHOW_SINGLE_ANNOTATION = "--show-single-annotation"
35 const val ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION = "--show-for-stub-purposes-annotation"
36
37 const val ARG_HIDE_ANNOTATION = "--hide-annotation"
38
39 /** The name of the group, can be used in help text to refer to the options in this group. */
40 const val API_SELECTION_OPTIONS_GROUP = "Api Selection"
41
42 /**
43 * Options related to selecting which parts of the source files will be part of the generated API.
44 *
45 * @param apiSurfacesConfigProvider Provides the [ApiSurfacesConfig] that was provided in an
46 * [ARG_CONFIG_FILE], if any. This must only be called after all the options have been parsed.
47 * @param checkSurfaceConsistencyProvider Returns `true` if the configured [ApiSurfaces] should be
48 * checked for consistency with the [showUnannotated] property.
49 */
50 class ApiSelectionOptions(
<lambda>null51 private val apiSurfacesConfigProvider: () -> ApiSurfacesConfig? = { null },
<lambda>null52 private val checkSurfaceConsistencyProvider: () -> Boolean = { true },
53 ) :
54 OptionGroup(
55 name = API_SELECTION_OPTIONS_GROUP,
56 help =
57 """
58 Options that select which parts of the source files will be part of the generated
59 API.
60 """
61 .trimIndent()
62 ) {
63
64 private val apiSurface by
65 option(
66 ARG_API_SURFACE,
67 metavar = "<surface>",
68 help =
69 """
70 The API surface currently being generated. Must correspond to an <api-surface>
71 element in a $ARG_CONFIG_FILE.
72 """,
73 )
74
75 val showUnannotated by
76 option(help = "Include un-annotated public APIs in the signature file as well.")
77 .switch(ARG_SHOW_UNANNOTATED to true)
<lambda>null78 .defaultLazy(defaultForHelp = "true if no --show*-annotation options specified") {
79 // If the caller has not explicitly requested that unannotated classes and members
80 // should be shown in the output then only show them if no show annotations were
81 // provided.
82 allShowAnnotations.isEmpty()
83 }
84
85 private val showAnnotationValues by
86 option(
87 ARG_SHOW_ANNOTATION,
88 help =
89 """
90 Unhide any hidden elements that are also annotated with the given
91 annotation.
92 """
93 .trimIndent(),
94 metavar = "<annotation-filter>",
95 )
96 .multiple()
97
98 private val showSingleAnnotationValues by
99 option(
100 ARG_SHOW_SINGLE_ANNOTATION,
101 help =
102 """
103 Like $ARG_SHOW_ANNOTATION, but does not apply to members; these must also be
104 explicitly annotated.
105 """
106 .trimIndent(),
107 metavar = "<annotation-filter>",
108 )
109 .multiple()
110
111 private val showForStubPurposesAnnotationValues by
112 option(
113 ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION,
114 help =
115 """
116 Like $ARG_SHOW_ANNOTATION, but elements annotated with it are assumed to be
117 "implicitly" included in the API surface, and they'll be included in certain
118 kinds of output such as stubs, but not in others, such as the signature file
119 and API lint.
120 """
121 .trimIndent(),
122 metavar = "<annotation-filter>",
123 )
124 .multiple()
125
126 private val hideAnnotationValues by
127 option(
128 ARG_HIDE_ANNOTATION,
129 help = "Treat any elements annotated with the given annotation as hidden.",
130 metavar = "<annotation-filter>",
131 )
132 .multiple()
133
134 /**
135 * Whether to include APIs with annotations (intended for documentation purposes). This includes
136 * [showAnnotations], [showSingleAnnotations] and [showForStubPurposesAnnotations].
137 */
138 internal val allShowAnnotations by
<lambda>null139 lazy(LazyThreadSafetyMode.NONE) {
140 AnnotationFilter.create(
141 showAnnotationValues +
142 showSingleAnnotationValues +
143 showForStubPurposesAnnotationValues
144 )
145 }
146
147 /**
148 * A filter that will match annotations which will cause an annotated item (and its enclosed
149 * items unless overridden by a closer annotation) to be included in the API surface.
150 *
151 * @see [allShowAnnotations]
152 */
153 internal val showAnnotations by
<lambda>null154 lazy(LazyThreadSafetyMode.NONE) { AnnotationFilter.create(showAnnotationValues) }
155
156 /**
157 * Like [showAnnotations], but does not work recursively.
158 *
159 * @see [allShowAnnotations]
160 */
161 internal val showSingleAnnotations by
<lambda>null162 lazy(LazyThreadSafetyMode.NONE) { AnnotationFilter.create(showSingleAnnotationValues) }
163
164 /**
165 * Annotations that defines APIs that are implicitly included in the API surface. These APIs
166 * will be included in certain kinds of output such as stubs, but others (e.g. API lint and the
167 * API signature file) ignore them.
168 *
169 * @see [allShowAnnotations]
170 */
171 internal val showForStubPurposesAnnotations by
<lambda>null172 lazy(LazyThreadSafetyMode.NONE) {
173 AnnotationFilter.create(showForStubPurposesAnnotationValues)
174 }
175
176 /** Annotations that mark items which should be treated as hidden. */
177 internal val hideAnnotations by
<lambda>null178 lazy(LazyThreadSafetyMode.NONE) { AnnotationFilter.create(hideAnnotationValues) }
179
180 val apiSurfaces by
<lambda>null181 lazy(LazyThreadSafetyMode.NONE) {
182 val apiSurfacesConfig = apiSurfacesConfigProvider()
183 val checkSurfaceConsistency = checkSurfaceConsistencyProvider()
184 createApiSurfaces(
185 showUnannotated,
186 apiSurface,
187 apiSurfacesConfig,
188 checkSurfaceConsistency,
189 )
190 }
191
192 companion object {
193 /**
194 * Create [ApiSurfaces] and associated [ApiSurface] objects from these options.
195 *
196 * @param showUnannotated true if unannotated items should be included in the API, false
197 * otherwise.
198 * @param targetApiSurface the optional name of the target API surface to be created. If
199 * supplied it MUST reference an [ApiSurfaceConfig] in [apiSurfacesConfig].
200 * @param apiSurfacesConfig the optional [ApiSurfacesConfig].
201 * @param checkSurfaceConsistency if `true` and [targetApiSurface] is not-null then check
202 * the consistency between the configured surfaces and the [ApiSelectionOptions].
203 */
createApiSurfacesnull204 private fun createApiSurfaces(
205 showUnannotated: Boolean,
206 targetApiSurface: String?,
207 apiSurfacesConfig: ApiSurfacesConfig?,
208 checkSurfaceConsistency: Boolean,
209 ): ApiSurfaces {
210 // A base API surface is needed if and only if the main API surface being generated
211 // extends another API surface. That is not currently explicitly specified on the
212 // command line so has to be inferred from the existing arguments. There are four main
213 // supported cases:
214 //
215 // * Public which does not extend another API surface so does not need a base. This
216 // happens by default unless one or more `--show*annotation` options were specified.
217 // In that case it behaves as if `--show-unannotated` was specified.
218 //
219 // * Restricted API in AndroidX which is basically public + other and does not need a
220 // base. This happens when `--show-unannotated` was provided (the public part) as well
221 // as `--show-annotation RestrictTo(...)` (the other part).
222 //
223 // * System delta on public in Android build. This happens when --show-unannotated was
224 // not specified (so the public part is not included in signature files at least) but
225 // `--show-annotation SystemApi` was.
226 //
227 // * Test API delta on system (or similar) in Android build. This happens when
228 // `--show-unannotated` was not specified (so the public part is not included),
229 // `--show-for-stub-purposes-only SystemApi` was (so system API is included in the
230 // stubs but not the signature files) and `--show-annotation TestApi` was.
231 //
232 // There are other combinations of the `--show*` options which are not used, and it is
233 // not clear whether they make any sense so this does not cover them.
234 //
235 // This does not need a base if --show-unannotated was specified, or it defaulted to
236 // behaving as if it was.
237 val needsBase = !showUnannotated
238
239 // If no --api-surface option was provided, then create the ApiSurfaces from the command
240 // line options.
241 if (targetApiSurface == null) {
242 return ApiSurfaces.create(
243 needsBase = needsBase,
244 )
245 }
246
247 // Otherwise, create it from the configured API surfaces.
248 if (apiSurfacesConfig == null || apiSurfacesConfig.apiSurfaceList.isEmpty()) {
249 throw MetalavaCliException(
250 "$ARG_API_SURFACE requires at least one <api-surface> to have been configured in a --config-file"
251 )
252 }
253
254 val targetApiSurfaceConfig =
255 apiSurfacesConfig.getByNameOrError(targetApiSurface) {
256 "$ARG_API_SURFACE (`$it`) does not match an <api-surface> in a --config-file"
257 }
258
259 val extendedSurface = targetApiSurfaceConfig.extends
260 val extendsSurface = extendedSurface != null
261
262 // If show annotations should not be ignored then perform a consistency check to ensure
263 // that the configuration and command line options are compatible.
264 if (checkSurfaceConsistency) {
265 if (extendsSurface != needsBase) {
266 val reason =
267 if (extendsSurface)
268 "extends $extendedSurface which requires that it not show unannotated items but $ARG_SHOW_UNANNOTATED is true"
269 else
270 "does not extend another surface which requires that it show unannotated items but $ARG_SHOW_UNANNOTATED is false"
271 throw MetalavaCliException(
272 """Configuration of `<api-surface name="$targetApiSurface">` is inconsistent with command line options because `$targetApiSurface` $reason"""
273 )
274 }
275 }
276
277 // Create the ApiSurfaces from the configured API surfaces.
278 return apiSurfacesFromConfig(
279 apiSurfacesConfig.contributesTo(targetApiSurfaceConfig),
280 targetApiSurface
281 )
282 }
283 }
284 }
285
286 /**
287 * Create [ApiSurfaces] from a collection of [ApiSurfaceConfig]s.
288 *
289 * The [ApiSurfaceConfig]s must be in order such that every [ApiSurfaceConfig] comes before any
290 * [ApiSurfaceConfig] that extends it. It must also be complete such that the collection must
291 * contain every [ApiSurfaceConfig] that is extended by another in the collection.
292 */
apiSurfacesFromConfignull293 internal fun apiSurfacesFromConfig(
294 surfaceConfigs: Collection<ApiSurfaceConfig>,
295 targetApiSurface: String?
296 ) =
297 ApiSurfaces.build {
298 // Add ApiSurface instances in order so that surfaces referenced by another (i.e.
299 // through `extends`) come before the surfaces that reference them. This ensures
300 // that the `extends` can be resolved to an existing `ApiSurface`.
301 for (surfaceConfig in surfaceConfigs) {
302 createSurface(
303 name = surfaceConfig.name,
304 extends = surfaceConfig.extends,
305 isMain = surfaceConfig.name == targetApiSurface,
306 )
307 }
308 }
309