• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.apilevels.ApiGenerator
20 import com.android.tools.metalava.apilevels.ApiHistoryUpdater
21 import com.android.tools.metalava.apilevels.ApiVersion
22 import com.android.tools.metalava.apilevels.ExtVersion
23 import com.android.tools.metalava.apilevels.GenerateApiHistoryConfig
24 import com.android.tools.metalava.apilevels.MatchedPatternFile
25 import com.android.tools.metalava.apilevels.MissingClassAction
26 import com.android.tools.metalava.apilevels.PatternNode
27 import com.android.tools.metalava.apilevels.SdkExtensionInfo
28 import com.android.tools.metalava.apilevels.VersionedApi
29 import com.android.tools.metalava.apilevels.VersionedJarApi
30 import com.android.tools.metalava.apilevels.VersionedSignatureApi
31 import com.android.tools.metalava.apilevels.VersionedSourceApi
32 import com.android.tools.metalava.cli.common.EarlyOptions
33 import com.android.tools.metalava.cli.common.ExecutionEnvironment
34 import com.android.tools.metalava.cli.common.RequiresOtherGroups
35 import com.android.tools.metalava.cli.common.SignatureFileLoader
36 import com.android.tools.metalava.cli.common.cliError
37 import com.android.tools.metalava.cli.common.existingFile
38 import com.android.tools.metalava.cli.common.fileForPathInner
39 import com.android.tools.metalava.cli.common.map
40 import com.android.tools.metalava.cli.common.newFile
41 import com.android.tools.metalava.model.Codebase
42 import com.android.tools.metalava.model.CodebaseFragment
43 import com.android.tools.metalava.model.api.surface.ApiSurfaces
44 import com.github.ajalt.clikt.parameters.groups.OptionGroup
45 import com.github.ajalt.clikt.parameters.options.OptionWithValues
46 import com.github.ajalt.clikt.parameters.options.convert
47 import com.github.ajalt.clikt.parameters.options.default
48 import com.github.ajalt.clikt.parameters.options.flag
49 import com.github.ajalt.clikt.parameters.options.multiple
50 import com.github.ajalt.clikt.parameters.options.option
51 import com.github.ajalt.clikt.parameters.options.split
52 import java.io.File
53 
54 // XML API version related arguments.
55 const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
56 
57 const val ARG_REMOVE_MISSING_CLASS_REFERENCES_IN_API_LEVELS =
58     "--remove-missing-class-references-in-api-levels"
59 
60 const val ARG_CURRENT_VERSION = "--current-version"
61 const val ARG_FIRST_VERSION = "--first-version"
62 const val ARG_CURRENT_CODENAME = "--current-codename"
63 
64 const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
65 
66 const val ARG_SDK_INFO_FILE = "--sdk-extensions-info"
67 
68 const val ARG_API_VERSION_SIGNATURE_PATTERN = "--api-version-signature-pattern"
69 
70 // JSON API version related arguments
71 const val ARG_GENERATE_API_VERSION_HISTORY = "--generate-api-version-history"
72 const val ARG_API_VERSION_SIGNATURE_FILES = "--api-version-signature-files"
73 
74 /**
75  * Factory for creating a [VersionedApi] from an [ApiHistoryUpdater] and a list of
76  * [MatchedPatternFile].
77  */
78 private typealias VersionedApiFactory =
79     (ApiHistoryUpdater, List<MatchedPatternFile>) -> VersionedApi
80 
81 class ApiLevelsGenerationOptions(
82     private val executionEnvironment: ExecutionEnvironment = ExecutionEnvironment(),
83     private val earlyOptions: EarlyOptions = EarlyOptions(),
84     private val apiSurfacesProvider: () -> ApiSurfaces? = { null },
85 ) :
86     OptionGroup(
87         name = "Api Levels Generation",
88         help =
89             """
90                 Options controlling the API levels file, e.g. `api-versions.xml` file.
91             """
92                 .trimIndent()
93     ),
94     RequiresOtherGroups {
95 
96     /** Make sure that the [earlyOptions] is correctly initialized when testing. */
97     override val requiredGroups: List<OptionGroup>
98         get() = listOf(earlyOptions)
99 
100     /** API level XML file to generate. */
101     val generateApiLevelXml: File? by
102         option(
103                 ARG_GENERATE_API_LEVELS,
104                 metavar = "<xmlfile>",
105                 help =
106                     """
107                         Reads android.jar SDK files and generates an XML file recording the API
108                         level for each class, method and field. The $ARG_CURRENT_VERSION must also
109                         be provided and must be greater than or equal to 27.
110                     """
111                         .trimIndent(),
112             )
113             .newFile()
114 
115     /** Whether references to missing classes should be removed from the api levels file. */
116     private val removeMissingClassReferencesInApiLevels: Boolean by
117         option(
118                 ARG_REMOVE_MISSING_CLASS_REFERENCES_IN_API_LEVELS,
119                 help =
120                     """
121                         Removes references to missing classes when generating the API levels XML
122                         file. This can happen when generating the XML file for the non-updatable
123                         portions of the module-lib sdk, as those non-updatable portions can
124                         reference classes that are part of an updatable apex.
125                     """
126                         .trimIndent(),
127             )
128             .flag()
129 
130     /** Convert an option value to an [ApiVersion]. */
<lambda>null131     fun OptionWithValues<String?, String, String>.apiVersion() = convert {
132         ApiVersion.fromString(it)
133     }
134 
135     /**
136      * The first api version of the codebase; typically 1 but can be higher for example for the
137      * System API.
138      */
139     private val firstApiVersion: ApiVersion by
140         option(
141                 ARG_FIRST_VERSION,
142                 metavar = "<api-version>",
143                 help =
144                     """
145                         Sets the first API version to include in the API history file. See
146                         $ARG_CURRENT_VERSION for acceptable `<api-version>`s.
147                     """
148                         .trimIndent()
149             )
150             .apiVersion()
151             .default(ApiVersion.fromLevel(1))
152 
153     /**
154      * The last api level.
155      *
156      * This is one more than [currentApiVersion] if this is a developer preview build.
157      */
158     private val lastApiVersion
159         get() = currentApiVersion + if (isDeveloperPreviewBuild) 1 else 0
160 
161     /** The [ApiVersion] of the codebase, or null if not known/specified */
162     private val optionalCurrentApiVersion: ApiVersion? by
163         option(
164                 ARG_CURRENT_VERSION,
165                 metavar = "<api-version>",
166                 help =
167                     """
168                         Sets the current API version of the current source code. This supports a
169                         single integer level, `major.minor`, `major.minor.patch` and
170                         `major.minor.patch-quality` formats. Where `major`, `minor` and `patch` are
171                         all non-negative integers and `quality` is an alphanumeric string.
172                     """
173                         .trimIndent(),
174             )
175             .apiVersion()
176 
177     /**
178      * Get the current API version.
179      *
180      * This must only be called if needed as it will fail if [ARG_CURRENT_VERSION] has not been
181      * specified.
182      */
183     internal val currentApiVersion: ApiVersion
184         get() =
185             optionalCurrentApiVersion
186                 ?: cliError("$ARG_GENERATE_API_LEVELS requires $ARG_CURRENT_VERSION")
187 
188     /**
189      * The codename of the codebase: non-null string if this is a developer preview build, null if
190      * this is a release build.
191      */
192     private val currentCodeName: String? by
193         option(
194                 ARG_CURRENT_CODENAME,
195                 metavar = "<version-codename>",
196                 help =
197                     """
198                         Sets the code name for the current source code.
199                     """
200                         .trimIndent(),
201             )
<lambda>null202             .map { if (it == "REL") null else it }
203 
204     /**
205      * True if [currentCodeName] is specified, false otherwise.
206      *
207      * If this is `true` then the API defined in the sources will be added to the API levels file
208      * with an API level of [currentApiVersion]` - 1`.
209      */
210     private val isDeveloperPreviewBuild
211         get() = currentCodeName != null
212 
213     /** The list of patterns used to find matching jars in the set of files visible to Metalava. */
214     private val androidJarPatterns: List<String> by
215         option(
216                 ARG_ANDROID_JAR_PATTERN,
217                 metavar = "<historical-api-pattern>",
218                 help =
219                     """
220                         Pattern to use to locate Android JAR files. Must end with `.jar`.
221 
222                         See `metalava help historical-api-patterns` for more information.
223                     """
224                         .trimIndent(),
225             )
226             .multiple(default = emptyList())
227 
228     /**
229      * The list of patterns used to find matching signature files in the set of files visible to
230      * Metalava.
231      */
232     private val signaturePatterns by
233         option(
234                 ARG_API_VERSION_SIGNATURE_PATTERN,
235                 metavar = "<historical-api-pattern>",
236                 help =
237                     """
238                         Pattern to use to locate signature files. Typically ends with `.txt`.
239 
240                         See `metalava help historical-api-patterns` for more information.
241                     """
242                         .trimIndent(),
243             )
244             .multiple()
245 
246     /**
247      * Rules to filter out some extension SDK APIs from the API, and assign extensions to the APIs
248      * that are kept
249      */
250     private val sdkInfoFile: File? by
251         option(
252                 ARG_SDK_INFO_FILE,
253                 metavar = "<sdk-info-file>",
254                 help =
255                     """
256                         Points to map of extension SDK APIs to include, if any. The file is a plain
257                         text file and describes, per extension SDK, what APIs from that extension
258                         to include in the file created via $ARG_GENERATE_API_LEVELS. The format of
259                         each line is one of the following:
260                         \"<module-name> <pattern> <ext-name> [<ext-name> [...]]\", where
261                         <module-name> is the name of the mainline module this line refers to,
262                         <pattern> is a common Java name prefix of the APIs this line refers to, and
263                         <ext-name> is a list of extension SDK names in which these SDKs first
264                         appeared, or \"<ext-name> <ext-id> <type>\", where <ext-name> is the name of
265                         an SDK, <ext-id> its numerical ID and <type> is one of \"platform\" (the
266                         Android platform SDK), \"platform-ext\" (an extension to the Android
267                         platform SDK), \"standalone\" (a separate SDK). Fields are separated by
268                         whitespace. A mainline module may be listed multiple times.
269                         The special pattern \"*\" refers to all APIs in the given mainline module.
270                         Lines beginning with # are comments.
271 
272                         If specified then the $ARG_ANDROID_JAR_PATTERN must include at least one
273                         pattern that uses `{version:extension}` and `{module}` placeholders and that
274                         pattern must match at least one file.
275                     """
276                         .trimIndent(),
277             )
278             .existingFile()
279 
280     /**
281      * Get label for [version].
282      *
283      * If a codename has been specified and [version] is greater than the current API version (which
284      * defaults to `null` when not set) then use the codename as the label, otherwise use the
285      * version itself.
286      */
getApiVersionLabelnull287     fun getApiVersionLabel(version: ApiVersion): String {
288         val codename = currentCodeName
289         val current = optionalCurrentApiVersion
290         return if (current == null || codename == null || version <= current) version.toString()
291         else codename
292     }
293 
294     /**
295      * Check whether [version] should be included in documentation.
296      *
297      * If [isDeveloperPreviewBuild] is `true` then allow any [ApiVersion] as the documentation is
298      * not going to be published outside Android, so it is safe to include all [ApiVersion]s,
299      * including the next one.
300      *
301      * If no [currentApiVersion] has been provided then allow any [ApiVersion] level as there is no
302      * way to determine whether the [ApiVersion] is a future API or not.
303      *
304      * Otherwise, it is a release build so ignore any [ApiVersion]s after the current one.
305      */
includeApiVersionInDocumentationnull306     fun includeApiVersionInDocumentation(version: ApiVersion): Boolean {
307         if (isDeveloperPreviewBuild) return true
308         val current = optionalCurrentApiVersion ?: return true
309         return version <= current
310     }
311 
312     /**
313      * Find all historical files that matches the patterns in [patterns] and are in the range from
314      * [firstApiVersion] to [lastApiVersion].
315      *
316      * @param dir the directory to scan.
317      * @param patterns the patterns that determine the files that will be found.
318      */
findHistoricalFilesnull319     private fun findHistoricalFiles(dir: File, patterns: List<String>): List<MatchedPatternFile> {
320         // Find all the historical files for versions within the required range.
321         val patternNode = PatternNode.parsePatterns(patterns)
322         val versionRange = firstApiVersion.rangeTo(lastApiVersion)
323         val apiSurfaceByName = apiSurfacesProvider()?.byName
324         val scanConfig =
325             PatternNode.ScanConfig(
326                 dir = dir,
327                 apiVersionFilter = versionRange::contains,
328                 apiSurfaceByName = apiSurfaceByName,
329             )
330         return patternNode.scan(scanConfig)
331     }
332 
333     /**
334      * Create [VersionedJarApi]s for each historical file in [matchedFiles].
335      *
336      * @param matchedFiles a list of files that matched the historical API patterns.
337      */
constructVersionedApisForHistoricalFilesnull338     private fun constructVersionedApisForHistoricalFiles(
339         matchedFiles: List<MatchedPatternFile>,
340         versionedApiFactory: VersionedApiFactory,
341     ): List<VersionedApi> {
342         // TODO(b/383288863): Check to make sure that there is one VersionedApi for every major
343         //  version in the range.
344 
345         val byVersion = matchedFiles.groupBy { it.version }
346 
347         // Convert the MatchedPatternFiles into VersionedApis.
348         return byVersion.map { (apiVersion, files) ->
349             val updater = ApiHistoryUpdater.forApiVersion(apiVersion)
350             versionedApiFactory(updater, files)
351         }
352     }
353 
354     /**
355      * Create a [VersionedApi] from [updater] and [files].
356      *
357      * It requires [files] to contain a single entry.
358      */
createVersionedJarApinull359     private fun createVersionedJarApi(
360         updater: ApiHistoryUpdater,
361         files: List<MatchedPatternFile>,
362     ): VersionedApi {
363         val version = updater.apiVersion
364         val jar =
365             files.singleOrNull()?.file
366                 ?: error(
367                     "Expected only one jar file for version $version but found ${files.size}:\n${files.joinToString("\n") {"    $it"}}"
368                 )
369         verbosePrint { "Found API $version at $jar" }
370         return VersionedJarApi(jar, updater)
371     }
372 
373     /** Print string returned by [message] if verbose output has been requested. */
verbosePrintnull374     private inline fun verbosePrint(message: () -> String) {
375         if (earlyOptions.verbosity.verbose) {
376             executionEnvironment.stdout.println(message())
377         }
378     }
379 
380     /**
381      * Get the [GenerateApiHistoryConfig] for Android.
382      *
383      * This has some Android specific code, e.g. structure of SDK extensions.
384      */
forAndroidConfignull385     fun forAndroidConfig(
386         signatureFileLoader: SignatureFileLoader,
387         codebaseFragmentProvider: () -> CodebaseFragment,
388     ) =
389         generateApiLevelXml?.let { outputFile ->
390             // Scan for all the files that could contribute to the API history.
391             val currentDir = fileForPathInner(".")
392             val (patterns, matchedFiles, versionedApiFactory) =
393                 if (signaturePatterns.isEmpty()) {
394                     Triple(
395                         androidJarPatterns,
396                         findHistoricalFiles(currentDir, androidJarPatterns),
397                         ::createVersionedJarApi,
398                     )
399                 } else if (androidJarPatterns.isNotEmpty()) {
400                     cliError(
401                         "Cannot combine $ARG_API_VERSION_SIGNATURE_PATTERN with $ARG_ANDROID_JAR_PATTERN"
402                     )
403                 } else {
404                     fun createVersionedSignatureApi(
405                         updater: ApiHistoryUpdater,
406                         files: List<MatchedPatternFile>,
407                     ) = VersionedSignatureApi(signatureFileLoader, files.map { it.file }, updater)
408 
409                     Triple(
410                         signaturePatterns,
411                         findHistoricalFiles(currentDir, signaturePatterns),
412                         ::createVersionedSignatureApi,
413                     )
414                 }
415 
416             // Split the files into extension api files and primary api files.
417             val (extensionApiFiles, primaryApiFiles) = matchedFiles.partition { it.extension }
418 
419             // Get a VersionedApi for each of the released API files.
420             val versionedHistoricalApis =
421                 constructVersionedApisForHistoricalFiles(primaryApiFiles, versionedApiFactory)
422 
423             val currentSdkVersion = currentApiVersion
424             if (currentSdkVersion.major <= 26) {
425                 cliError("Suspicious $ARG_CURRENT_VERSION $currentSdkVersion, expected at least 27")
426             }
427 
428             val nextSdkVersion = currentSdkVersion + 1
429             val lastFinalizedVersion = versionedHistoricalApis.lastOrNull()?.apiVersion
430 
431             // Compute the version to use for the current codebase, or null if the current codebase
432             // should not be added to the API history. If a non-null version is selected it will
433             // always be after the last historical version.
434             val codebaseSdkVersion =
435                 when {
436                     // The current codebase is a developer preview so use the next, in the
437                     // process of being finalized version.
438                     isDeveloperPreviewBuild -> nextSdkVersion
439 
440                     // If no finalized versions were provided or the last finalized version is less
441                     // than the current version then use the current version as the version of the
442                     // codebase.
443                     lastFinalizedVersion == null || lastFinalizedVersion < currentSdkVersion ->
444                         currentSdkVersion
445 
446                     // Else do not include the current codebase.
447                     else -> null
448                 }
449 
450             // Get the optional SDK extension arguments.
451             val sdkExtensionsArguments =
452                 if (sdkInfoFile != null) {
453                     // The not finalized SDK version is the version after the last historical
454                     // version. That is either the version used for the current codebase or the
455                     // next version.
456                     val notFinalizedSdkVersion = codebaseSdkVersion ?: nextSdkVersion
457                     ApiGenerator.SdkExtensionsArguments(
458                         sdkInfoFile!!,
459                         notFinalizedSdkVersion,
460                     )
461                 } else {
462                     null
463                 }
464 
465             // Create a list of VersionedApis that need to be incorporated into the Api history.
466             val versionedApis = buildList {
467                 addAll(versionedHistoricalApis)
468 
469                 // Add a VersionedSourceApi for the current codebase if required.
470                 if (codebaseSdkVersion != null) {
471                     add(
472                         VersionedSourceApi(
473                             codebaseFragmentProvider,
474                             codebaseSdkVersion,
475                         )
476                     )
477                 }
478 
479                 // Add any VersionedApis for SDK extensions. These must be added after all
480                 // VersionedApis for SDK versions as their behavior depends on whether an API was
481                 // defined in an SDK version.
482                 if (sdkExtensionsArguments != null) {
483                     require(extensionApiFiles.isNotEmpty()) {
484                         "no extension api files found by ${patterns.joinToString()}"
485                     }
486                     addVersionedExtensionApis(
487                         this,
488                         sdkExtensionsArguments.notFinalizedSdkVersion,
489                         extensionApiFiles,
490                         sdkExtensionsArguments.sdkExtensionInfo,
491                         versionedApiFactory,
492                     )
493                 }
494             }
495 
496             GenerateApiHistoryConfig(
497                 versionedApis = versionedApis,
498                 outputFile = outputFile,
499                 sdkExtensionsArguments = sdkExtensionsArguments,
500                 missingClassAction =
501                     if (removeMissingClassReferencesInApiLevels) MissingClassAction.REMOVE
502                     else MissingClassAction.REPORT,
503                 // Use internal names.
504                 useInternalNames = true,
505             )
506         }
507 
508     /**
509      * Add [VersionedApi] instances to [list] for each of the [extensionApiFiles].
510      *
511      * Some APIs only exist in extension SDKs and not in the Android SDK, but for backwards
512      * compatibility with tools that expect the Android SDK to be the only SDK, metalava needs to
513      * assign such APIs some Android SDK API version. This uses [versionNotInAndroidSdk].
514      *
515      * @param versionNotInAndroidSdk fallback API level for APIs not in the Android SDK
516      * @param extensionApiFiles extension api files.
517      * @param sdkExtensionInfo the [SdkExtensionInfo] read from sdk-extension-info.xml file.
518      * @param versionedApiFactory factory for creating [VersionedApi]s.
519      */
addVersionedExtensionApisnull520     private fun addVersionedExtensionApis(
521         list: MutableList<VersionedApi>,
522         versionNotInAndroidSdk: ApiVersion,
523         extensionApiFiles: List<MatchedPatternFile>,
524         sdkExtensionInfo: SdkExtensionInfo,
525         versionedApiFactory: VersionedApiFactory,
526     ) {
527         val byModule = extensionApiFiles.groupBy({ it.module!! })
528         // Iterate over the mainline modules and their different versions.
529         for ((mainlineModule, moduleFiles) in byModule) {
530             // Get the extensions information for the mainline module. If no information exists for
531             // a particular module then the module is ignored.
532             val moduleMap = sdkExtensionInfo.extensionsMapForJarOrEmpty(mainlineModule)
533             if (moduleMap.isEmpty())
534                 continue // TODO(b/259115852): remove this (though it is an optimization too).
535 
536             val byVersion = moduleFiles.groupBy { it.version }
537             byVersion.mapTo(list) { (version, files) ->
538                 val extVersion = ExtVersion.fromLevel(version.major)
539                 val updater =
540                     ApiHistoryUpdater.forExtVersion(
541                         versionNotInAndroidSdk,
542                         extVersion,
543                         mainlineModule,
544                     )
545                 versionedApiFactory(updater, files)
546             }
547         }
548     }
549 
550     /** API version history file to generate */
551     private val generateApiVersionHistory by
552         option(
553                 ARG_GENERATE_API_VERSION_HISTORY,
554                 metavar = "<output-file>",
555                 help =
556                     """
557                         Reads API signature files and generates a JSON or XML file depending on the
558                         extension, which must be one of `json` or `xml` respectively. The JSON file
559                         will record the API version in which each class, method, and field. was
560                         added in and (if applicable) deprecated in. The XML file will include that
561                         information and more but will be optimized to exclude information from
562                         class members which is the same as the containing class.
563                     """
564                         .trimIndent(),
565             )
566             .newFile()
567 
568     /**
569      * Ordered list of signatures for each past API version, when generating
570      * [generateApiVersionHistory].
571      */
572     private val apiVersionSignatureFiles by
573         option(
574                 ARG_API_VERSION_SIGNATURE_FILES,
575                 metavar = "<files>",
576                 help =
577                     """
578                         An ordered list of text API signature files. The oldest API version should
579                         be first, the newest last. This should not include a signature file for the
580                         current API version, which will be parsed from the provided source files.
581                         Not required to generate API version JSON if the current version is the only
582                         version.
583                     """
584                         .trimIndent(),
585             )
586             .existingFile()
587             .split(File.pathSeparator)
588             .default(emptyList(), defaultForHelp = "")
589 
590     /**
591      * Construct the [GenerateApiHistoryConfig] from the options.
592      *
593      * If no relevant command line options were provided then this will return `null`, otherwise it
594      * will validate the options and if all is well construct and return a
595      * [GenerateApiHistoryConfig] object.
596      *
597      * @param signatureFileLoader used for loading [Codebase]s from signature files.
598      * @param codebaseFragmentProvider provides access to the [CodebaseFragment] for the API defined
599      *   in the sources. This will only be called if a [GenerateApiHistoryConfig] needs to be
600      *   created.
601      */
fromSignatureFilesConfignull602     fun fromSignatureFilesConfig(
603         signatureFileLoader: SignatureFileLoader,
604         codebaseFragmentProvider: () -> CodebaseFragment,
605     ): GenerateApiHistoryConfig? {
606         val apiVersionsFile = generateApiVersionHistory
607         return if (apiVersionsFile != null) {
608             val (sourceVersion, matchedPatternFiles) =
609                 sourceVersionAndMatchedPatternFilesFromSignaturePatterns()
610 
611             // Create VersionedApis for the signature files and the source codebase.
612             val versionedApis = buildList {
613                 matchedPatternFiles.mapTo(this) {
614                     val updater = ApiHistoryUpdater.forApiVersion(it.version)
615                     VersionedSignatureApi(signatureFileLoader, listOf(it.file), updater)
616                 }
617                 // Add a VersionedSourceApi for the source code.
618                 add(VersionedSourceApi(codebaseFragmentProvider, sourceVersion))
619             }
620 
621             GenerateApiHistoryConfig(
622                 versionedApis = versionedApis,
623                 outputFile = apiVersionsFile,
624                 // None are available when generating from signature files.
625                 sdkExtensionsArguments = null,
626                 // Keep any references to missing classes.
627                 missingClassAction = MissingClassAction.KEEP,
628                 // Do not use internal names.
629                 useInternalNames = false,
630             )
631         } else {
632             null
633         }
634     }
635 
636     /**
637      * Get the source [ApiVersion] and list of [MatchedPatternFile]s from [signaturePatterns] as
638      * well as [optionalCurrentApiVersion] and [apiVersionSignatureFiles].
639      */
sourceVersionAndMatchedPatternFilesFromSignaturePatternsnull640     private fun sourceVersionAndMatchedPatternFilesFromSignaturePatterns():
641         Pair<ApiVersion, List<MatchedPatternFile>> {
642 
643         val sourceVersion =
644             optionalCurrentApiVersion
645                 ?: cliError(
646                     "Must specify $ARG_CURRENT_VERSION with $ARG_API_VERSION_SIGNATURE_PATTERN"
647                 )
648 
649         val historicalSignatureFiles = apiVersionSignatureFiles
650         if (historicalSignatureFiles.isEmpty()) return sourceVersion to emptyList()
651 
652         val patternNode = PatternNode.parsePatterns(signaturePatterns)
653         val matchedFiles =
654             patternNode.scan(
655                 PatternNode.ScanConfig(
656                     dir = File(".").canonicalFile,
657                     fileProvider = PatternNode.LimitedFileSystemProvider(historicalSignatureFiles)
658                 )
659             )
660 
661         if (matchedFiles.size != historicalSignatureFiles.size) {
662             val matched = matchedFiles.map { it.file }.toSet()
663             val unmatched = historicalSignatureFiles.filter { it !in matched }
664             cliError(
665                 "$ARG_API_VERSION_SIGNATURE_FILES: The following files were unmatched by a signature pattern:\n${unmatched.joinToString("\n") {"    $it"}}"
666             )
667         }
668 
669         return sourceVersion to matchedFiles
670     }
671 }
672