• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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