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