1 /* <lambda>null2 * Copyright (C) 2017 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.SdkConstants 20 import com.android.sdklib.SdkVersionInfo 21 import com.android.tools.metalava.CompatibilityCheck.CheckRequest 22 import com.android.tools.metalava.doclava1.Errors 23 import com.android.utils.SdkUtils.wrap 24 import com.google.common.base.CharMatcher 25 import com.google.common.base.Splitter 26 import com.google.common.io.Files 27 import com.intellij.pom.java.LanguageLevel 28 import java.io.File 29 import java.io.IOException 30 import java.io.OutputStreamWriter 31 import java.io.PrintWriter 32 import java.io.StringWriter 33 import java.util.Locale 34 import kotlin.reflect.KMutableProperty1 35 import kotlin.reflect.full.memberProperties 36 import kotlin.text.Charsets.UTF_8 37 38 /** Global options for the metadata extraction tool */ 39 var options = Options(emptyArray()) 40 41 private const val MAX_LINE_WIDTH = 90 42 43 const val ARG_COMPAT_OUTPUT = "--compatible-output" 44 const val ARG_FORMAT = "--format" 45 const val ARG_HELP = "--help" 46 const val ARG_VERSION = "--version" 47 const val ARG_QUIET = "--quiet" 48 const val ARG_VERBOSE = "--verbose" 49 const val ARG_CLASS_PATH = "--classpath" 50 const val ARG_SOURCE_PATH = "--source-path" 51 const val ARG_SOURCE_FILES = "--source-files" 52 const val ARG_API = "--api" 53 const val ARG_XML_API = "--api-xml" 54 const val ARG_CONVERT_TO_JDIFF = "--convert-to-jdiff" 55 const val ARG_CONVERT_NEW_TO_JDIFF = "--convert-new-to-jdiff" 56 const val ARG_CONVERT_TO_V1 = "--convert-to-v1" 57 const val ARG_CONVERT_TO_V2 = "--convert-to-v2" 58 const val ARG_CONVERT_NEW_TO_V1 = "--convert-new-to-v1" 59 const val ARG_CONVERT_NEW_TO_V2 = "--convert-new-to-v2" 60 const val ARG_PRIVATE_API = "--private-api" 61 const val ARG_DEX_API = "--dex-api" 62 const val ARG_PRIVATE_DEX_API = "--private-dex-api" 63 const val ARG_SDK_VALUES = "--sdk-values" 64 const val ARG_REMOVED_API = "--removed-api" 65 const val ARG_REMOVED_DEX_API = "--removed-dex-api" 66 const val ARG_MERGE_QUALIFIER_ANNOTATIONS = "--merge-qualifier-annotations" 67 const val ARG_MERGE_INCLUSION_ANNOTATIONS = "--merge-inclusion-annotations" 68 const val ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS = "--validate-nullability-from-merged-stubs" 69 const val ARG_VALIDATE_NULLABILITY_FROM_LIST = "--validate-nullability-from-list" 70 const val ARG_NULLABILITY_WARNINGS_TXT = "--nullability-warnings-txt" 71 const val ARG_NULLABILITY_ERRORS_NON_FATAL = "--nullability-errors-non-fatal" 72 const val ARG_INPUT_API_JAR = "--input-api-jar" 73 const val ARG_EXACT_API = "--exact-api" 74 const val ARG_STUBS = "--stubs" 75 const val ARG_DOC_STUBS = "--doc-stubs" 76 const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list" 77 const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list" 78 const val ARG_PROGUARD = "--proguard" 79 const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations" 80 const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations" 81 const val ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS = "--exclude-documentation-from-stubs" 82 const val ARG_HIDE_PACKAGE = "--hide-package" 83 const val ARG_MANIFEST = "--manifest" 84 const val ARG_MIGRATE_NULLNESS = "--migrate-nullness" 85 const val ARG_CHECK_COMPATIBILITY = "--check-compatibility" 86 const val ARG_CHECK_COMPATIBILITY_API_CURRENT = "--check-compatibility:api:current" 87 const val ARG_CHECK_COMPATIBILITY_API_RELEASED = "--check-compatibility:api:released" 88 const val ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT = "--check-compatibility:removed:current" 89 const val ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED = "--check-compatibility:removed:released" 90 const val ARG_ALLOW_COMPATIBLE_DIFFERENCES = "--allow-compatible-differences" 91 const val ARG_NO_NATIVE_DIFF = "--no-native-diff" 92 const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls" 93 const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls" 94 const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values" 95 const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats" 96 const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of" 97 const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to" 98 const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to" 99 const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors" 100 const val ARG_LINTS_AS_ERRORS = "--lints-as-errors" 101 const val ARG_SHOW_ANNOTATION = "--show-annotation" 102 const val ARG_SHOW_SINGLE_ANNOTATION = "--show-single-annotation" 103 const val ARG_HIDE_ANNOTATION = "--hide-annotation" 104 const val ARG_SHOW_UNANNOTATED = "--show-unannotated" 105 const val ARG_COLOR = "--color" 106 const val ARG_NO_COLOR = "--no-color" 107 const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages" 108 const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report" 109 const val ARG_NO_BANNER = "--no-banner" 110 const val ARG_ERROR = "--error" 111 const val ARG_WARNING = "--warning" 112 const val ARG_LINT = "--lint" 113 const val ARG_HIDE = "--hide" 114 const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes" 115 const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes" 116 const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes" 117 const val ARG_APPLY_API_LEVELS = "--apply-api-levels" 118 const val ARG_GENERATE_API_LEVELS = "--generate-api-levels" 119 const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern" 120 const val ARG_CURRENT_VERSION = "--current-version" 121 const val ARG_CURRENT_CODENAME = "--current-codename" 122 const val ARG_CURRENT_JAR = "--current-jar" 123 const val ARG_CHECK_KOTLIN_INTEROP = "--check-kotlin-interop" 124 const val ARG_API_LINT = "--api-lint" 125 const val ARG_PUBLIC = "--public" 126 const val ARG_PROTECTED = "--protected" 127 const val ARG_PACKAGE = "--package" 128 const val ARG_PRIVATE = "--private" 129 const val ARG_HIDDEN = "--hidden" 130 const val ARG_NO_DOCS = "--no-docs" 131 const val ARG_JAVA_SOURCE = "--java-source" 132 const val ARG_REGISTER_ARTIFACT = "--register-artifact" 133 const val ARG_INCLUDE_ANNOTATIONS = "--include-annotations" 134 const val ARG_COPY_ANNOTATIONS = "--copy-annotations" 135 const val ARG_INCLUDE_ANNOTATION_CLASSES = "--include-annotation-classes" 136 const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations" 137 const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention" 138 const val ARG_INCLUDE_SIG_VERSION = "--include-signature-version" 139 const val ARG_UPDATE_API = "--only-update-api" 140 const val ARG_CHECK_API = "--only-check-api" 141 const val ARG_PASS_BASELINE_UPDATES = "--pass-baseline-updates" 142 const val ARG_DEX_API_MAPPING = "--dex-api-mapping" 143 const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation" 144 const val ARG_BASELINE = "--baseline" 145 const val ARG_UPDATE_BASELINE = "--update-baseline" 146 const val ARG_MERGE_BASELINE = "--merge-baseline" 147 const val ARG_STUB_PACKAGES = "--stub-packages" 148 const val ARG_STUB_IMPORT_PACKAGES = "--stub-import-packages" 149 const val ARG_DELETE_EMPTY_BASELINES = "--delete-empty-baselines" 150 const val ARG_SUBTRACT_API = "--subtract-api" 151 const val ARG_TYPEDEFS_IN_SIGNATURES = "--typedefs-in-signatures" 152 153 class Options( 154 private val args: Array<String>, 155 /** Writer to direct output to */ 156 var stdout: PrintWriter = PrintWriter(OutputStreamWriter(System.out)), 157 /** Writer to direct error messages to */ 158 var stderr: PrintWriter = PrintWriter(OutputStreamWriter(System.err)) 159 ) { 160 161 /** Internal list backing [sources] */ 162 private val mutableSources: MutableList<File> = mutableListOf() 163 /** Internal list backing [sourcePath] */ 164 private val mutableSourcePath: MutableList<File> = mutableListOf() 165 /** Internal list backing [classpath] */ 166 private val mutableClassPath: MutableList<File> = mutableListOf() 167 /** Internal list backing [showAnnotations] */ 168 private val mutableShowAnnotations: MutableList<String> = mutableListOf() 169 /** Internal list backing [showSingleAnnotations] */ 170 private val mutableShowSingleAnnotations: MutableList<String> = mutableListOf() 171 /** Internal list backing [hideAnnotations] */ 172 private val mutableHideAnnotations: MutableList<String> = mutableListOf() 173 /** Internal list backing [stubImportPackages] */ 174 private val mutableStubImportPackages: MutableSet<String> = mutableSetOf() 175 /** Internal list backing [mergeQualifierAnnotations] */ 176 private val mutableMergeQualifierAnnotations: MutableList<File> = mutableListOf() 177 /** Internal list backing [mergeInclusionAnnotations] */ 178 private val mutableMergeInclusionAnnotations: MutableList<File> = mutableListOf() 179 /** Internal list backing [annotationCoverageOf] */ 180 private val mutableAnnotationCoverageOf: MutableList<File> = mutableListOf() 181 /** Internal list backing [hidePackages] */ 182 private val mutableHidePackages: MutableList<String> = mutableListOf() 183 /** Internal list backing [skipEmitPackages] */ 184 private val mutableSkipEmitPackages: MutableList<String> = mutableListOf() 185 /** Internal list backing [convertToXmlFiles] */ 186 private val mutableConvertToXmlFiles: MutableList<ConvertFile> = mutableListOf() 187 188 /** Ignored flags we've already warned about - store here such that we don't keep reporting them */ 189 private val alreadyWarned: MutableSet<String> = mutableSetOf() 190 191 /** 192 * Set of arguments to invoke documentation generation tool (arg 0) with, unless --no-docs is also 193 * supplied 194 */ 195 var invokeDocumentationToolArguments: Array<String> = emptyArray() 196 197 /** 198 * Whether to suppress documentation generation, even if a documentation generator has 199 * been configured via ${#ARG_GENERATE_DOCUMENTATION} 200 */ 201 var noDocs = false 202 203 /** API to subtract from signature and stub generation. Corresponds to [ARG_SUBTRACT_API]. */ 204 var subtractApi: File? = null 205 206 /** 207 * Validator for nullability annotations, if validation is enabled. 208 */ 209 var nullabilityAnnotationsValidator: NullabilityAnnotationsValidator? = null 210 211 /** 212 * Whether nullability validation errors should be considered fatal. 213 */ 214 var nullabilityErrorsFatal = true 215 216 /** 217 * A file to write non-fatal nullability validation issues to. If null, all issues are treated 218 * as fatal or else logged as warnings, depending on the value of [nullabilityErrorsFatal]. 219 */ 220 var nullabilityWarningsTxt: File? = null 221 222 /** 223 * Whether to validate nullability for all the classes where we are merging annotations from 224 * external java stub files. If true, [nullabilityAnnotationsValidator] must be set. 225 */ 226 var validateNullabilityFromMergedStubs = false 227 228 /** 229 * A file containing a list of classes whose nullability annotations should be validated. If 230 * set, [nullabilityAnnotationsValidator] must also be set. 231 */ 232 var validateNullabilityFromList: File? = null 233 234 /** 235 * Whether to include element documentation (javadoc and KDoc) is in the generated stubs. 236 * (Copyright notices are not affected by this, they are always included. Documentation stubs 237 * (--doc-stubs) are not affected.) 238 */ 239 var includeDocumentationInStubs = true 240 241 /** 242 * Whether metalava is invoked as part of updating the API files. When this is true, metalava 243 * should *cancel* various other flags that are also being passed in, such as --check-compatibility. 244 * This is there to ease integration in the build system: for a given target, the build system will 245 * pass all the applicable flags (--stubs, --api, --check-compatibility, --generate-documentation, etc), 246 * and this integration is re-used for the update-api facility where we *only* want to generate the 247 * signature files. This avoids having duplicate metalava invocation logic where potentially newly 248 * added flags are missing in one of the invocations etc. 249 */ 250 var onlyUpdateApi = false 251 252 /** 253 * Whether metalava is invoked as part of running the checkapi target. When this is true, metalava 254 * should *cancel* various other flags that are also being passed in, such as updating signature 255 * files. 256 * 257 * This is there to ease integration in the build system: for a given target, the build system will 258 * pass all the applicable flags (--stubs, --api, --check-compatibility, --generate-documentation, etc), 259 * and this integration is re-used for the checkapi facility where we *only* want to run compatibility 260 * checks. This avoids having duplicate metalava invocation logic where potentially newly 261 * added flags are missing in one of the invocations etc. 262 */ 263 var onlyCheckApi = false 264 265 /** 266 * Whether signature files should emit in "compat" mode, preserving the various 267 * quirks of the previous signature file format -- this will for example use a non-standard 268 * modifier ordering, it will call enums interfaces, etc. See the [Compatibility] class 269 * for more fine grained control (which is not (currently) exposed as individual command line 270 * flags. 271 */ 272 var compatOutput = useCompatMode(args) 273 274 /** Whether nullness annotations should be displayed as ?/!/empty instead of with @NonNull/@Nullable. */ 275 var outputKotlinStyleNulls = false // requires v3 276 277 /** Whether default values should be included in signature files */ 278 var outputDefaultValues = !compatOutput 279 280 /** The output format version being used */ 281 var outputFormat: FileFormat = if (compatOutput) FileFormat.V1 else FileFormat.V2 282 283 /** 284 * Whether reading signature files should assume the input is formatted as Kotlin-style nulls 285 * (e.g. ? means nullable, ! means unknown, empty means not null). 286 * 287 * If not specified, it depends on the format of the signature file (v1 and v2 does not use 288 * Kotlin style nulls; v3 does.) 289 */ 290 var inputKotlinStyleNulls: Boolean? = null 291 292 /** If true, treat all warnings as errors */ 293 var warningsAreErrors: Boolean = false 294 295 /** If true, treat all API lint warnings as errors */ 296 var lintsAreErrors: Boolean = false 297 298 /** The list of source roots */ 299 val sourcePath: List<File> = mutableSourcePath 300 301 /** The list of dependency jars */ 302 val classpath: List<File> = mutableClassPath 303 304 /** All source files to parse */ 305 var sources: List<File> = mutableSources 306 307 /** Whether to include APIs with annotations (intended for documentation purposes) */ 308 var showAnnotations: List<String> = mutableShowAnnotations 309 310 /** 311 * Like [showAnnotations], but does not work recursively. Note that 312 * these annotations are *also* show annotations and will be added to the above list; 313 * this is a subset. 314 */ 315 val showSingleAnnotations: List<String> = mutableShowSingleAnnotations 316 317 /** 318 * Whether to include unannotated elements if {@link #showAnnotations} is set. 319 * Note: This only applies to signature files, not stub files. 320 */ 321 var showUnannotated = false 322 323 /** Whether to validate the API for best practices */ 324 var checkApi = false 325 326 /** If non null, an API file to use to hide for controlling what parts of the API are new */ 327 var checkApiBaselineApiFile: File? = null 328 329 /** Whether to validate the API for Kotlin interop */ 330 var checkKotlinInterop = false 331 332 /** Packages to include (if null, include all) */ 333 var stubPackages: PackageFilter? = null 334 335 /** Packages to import (if empty, include all) */ 336 var stubImportPackages: Set<String> = mutableStubImportPackages 337 338 /** Packages to exclude/hide */ 339 var hidePackages: List<String> = mutableHidePackages 340 341 /** Packages that we should skip generating even if not hidden; typically only used by tests */ 342 var skipEmitPackages: List<String> = mutableSkipEmitPackages 343 344 var showAnnotationOverridesVisibility: Boolean = false 345 346 /** Annotations to hide */ 347 var hideAnnotations: List<String> = mutableHideAnnotations 348 349 /** Whether to report warnings and other diagnostics along the way */ 350 var quiet = false 351 352 /** Whether to report extra diagnostics along the way (note that verbose isn't the same as not quiet) */ 353 var verbose = false 354 355 /** If set, a directory to write stub files to. Corresponds to the --stubs/-stubs flag. */ 356 var stubsDir: File? = null 357 358 /** If set, a directory to write documentation stub files to. Corresponds to the --stubs/-stubs flag. */ 359 var docStubsDir: File? = null 360 361 /** If set, a source file to write the stub index (list of source files) to. Can be passed to 362 * other tools like javac/javadoc using the special @-syntax. */ 363 var stubsSourceList: File? = null 364 365 /** If set, a source file to write the doc stub index (list of source files) to. Can be passed to 366 * other tools like javac/javadoc using the special @-syntax. */ 367 var docStubsSourceList: File? = null 368 369 /** Proguard Keep list file to write */ 370 var proguard: File? = null 371 372 /** If set, a file to write an API file to. Corresponds to the --api/-api flag. */ 373 var apiFile: File? = null 374 375 /** Like [apiFile], but with JDiff xml format. */ 376 var apiXmlFile: File? = null 377 378 /** If set, a file to write the private API file to. Corresponds to the --private-api/-privateApi flag. */ 379 var privateApiFile: File? = null 380 381 /** If set, a file to write the DEX signatures to. Corresponds to [ARG_DEX_API]. */ 382 var dexApiFile: File? = null 383 384 /** If set, a file to write all DEX signatures and file locations to. Corresponds to [ARG_DEX_API_MAPPING]. */ 385 var dexApiMappingFile: File? = null 386 387 /** If set, a file to write the private DEX signatures to. Corresponds to --private-dex-api. */ 388 var privateDexApiFile: File? = null 389 390 /** Path to directory to write SDK values to */ 391 var sdkValueDir: File? = null 392 393 /** If set, a file to write extracted annotations to. Corresponds to the --extract-annotations flag. */ 394 var externalAnnotations: File? = null 395 396 /** For [ARG_COPY_ANNOTATIONS], the source directory to read stub annotations from */ 397 var privateAnnotationsSource: File? = null 398 399 /** For [ARG_COPY_ANNOTATIONS], the target directory to write converted stub annotations from */ 400 var privateAnnotationsTarget: File? = null 401 402 /** 403 * For [ARG_INCLUDE_ANNOTATION_CLASSES], the directory to copy stub annotation source files into the 404 * stubs folder from 405 */ 406 var copyStubAnnotationsFrom: File? = null 407 408 /** 409 * For [ARG_INCLUDE_SOURCE_RETENTION], true if we want to include source-retention annotations 410 * both in the set of files emitted by [ARG_INCLUDE_ANNOTATION_CLASSES] and into the stubs 411 * themselves 412 */ 413 var includeSourceRetentionAnnotations = false 414 415 /** For [ARG_REWRITE_ANNOTATIONS], the jar or bytecode folder to rewrite annotations in */ 416 var rewriteAnnotations: List<File>? = null 417 418 /** A manifest file to read to for example look up available permissions */ 419 var manifest: File? = null 420 421 /** If set, a file to write a dex API file to. Corresponds to the --removed-dex-api/-removedDexApi flag. */ 422 var removedApiFile: File? = null 423 424 /** If set, a file to write an API file to. Corresponds to the --removed-api/-removedApi flag. */ 425 var removedDexApiFile: File? = null 426 427 /** Whether output should be colorized */ 428 var color = System.getenv("TERM")?.startsWith("xterm") ?: System.getenv("COLORTERM") != null ?: false 429 430 /** Whether to omit Java and Kotlin runtime library packages from annotation coverage stats */ 431 var omitRuntimePackageStats = false 432 433 /** Whether to generate annotations into the stubs */ 434 var generateAnnotations = false 435 436 /** 437 * A signature file to migrate nullness data from 438 */ 439 var migrateNullsFrom: File? = null 440 441 /** Private backing list for [compatibilityChecks]] */ 442 private val mutableCompatibilityChecks: MutableList<CheckRequest> = mutableListOf() 443 444 /** The list of compatibility checks to run */ 445 val compatibilityChecks: List<CheckRequest> = mutableCompatibilityChecks 446 447 /** 448 * When checking signature files, whether compatible differences in signature 449 * files are allowed. This is normally not allowed (since it means the next 450 * engineer adding an incompatible change will suddenly see the cumulative 451 * differences show up in their diffs when checking in signature files), 452 * but is useful from the test suite etc. Controlled by 453 * [ARG_ALLOW_COMPATIBLE_DIFFERENCES]. 454 */ 455 var allowCompatibleDifferences = false 456 457 /** If false, attempt to use the native diff utility on the system */ 458 var noNativeDiff = false 459 460 /** Existing external annotation files to merge in */ 461 var mergeQualifierAnnotations: List<File> = mutableMergeQualifierAnnotations 462 var mergeInclusionAnnotations: List<File> = mutableMergeInclusionAnnotations 463 464 /** Set of jars and class files for existing apps that we want to measure coverage of */ 465 var annotationCoverageOf: List<File> = mutableAnnotationCoverageOf 466 467 /** File to write the annotation class coverage report to, if any */ 468 var annotationCoverageClassReport: File? = null 469 470 /** File to write the annotation member coverage report to, if any */ 471 var annotationCoverageMemberReport: File? = null 472 473 /** An optional <b>jar</b> file to load classes from instead of from source. 474 * This is similar to the [classpath] attribute except we're explicitly saying 475 * that this is the complete set of classes and that we <b>should</b> generate 476 * signatures/stubs from them or use them to diff APIs with (whereas [classpath] 477 * is only used to resolve types.) */ 478 var apiJar: File? = null 479 480 /** Whether to emit coverage statistics for annotations in the API surface */ 481 var dumpAnnotationStatistics = false 482 483 /** Only used for tests: Normally we want to treat classes not found as source (e.g. supplied via 484 * classpath) as hidden, but for the unit tests (where we're not passing in 485 * a complete API surface) this makes the tests more cumbersome. 486 * This option lets the testing infrastructure treat these classes differently. 487 * To see the what this means in practice, try turning it back on for the tests 488 * and see what it does to the results :) 489 */ 490 var hideClasspathClasses = true 491 492 /** Only used for tests: Whether during code filtering we allow referencing super classes 493 * etc that are unknown (because they're not included in the codebase) */ 494 var allowReferencingUnknownClasses = true 495 496 /** Reverse of [allowReferencingUnknownClasses]: Require all classes to be known. This 497 * is used when compiling the main SDK itself (which includes definitions for everything, 498 * including java.lang.Object.) */ 499 var noUnknownClasses = false 500 501 /** 502 * mapping from API level to android.jar files, if computing API levels 503 */ 504 var apiLevelJars: Array<File>? = null 505 506 /** The api level of the codebase, or -1 if not known/specified */ 507 var currentApiLevel = -1 508 509 /** The codename of the codebase, if it's a preview, or null if not specified */ 510 var currentCodeName: String? = null 511 512 /** API level XML file to generate */ 513 var generateApiLevelXml: File? = null 514 515 /** Reads API XML file to apply into documentation */ 516 var applyApiLevelsXml: File? = null 517 518 /** Level to include for javadoc */ 519 var docLevel = DocLevel.PROTECTED 520 521 /** Whether to include the signature file format version header in signature files */ 522 var includeSignatureFormatVersion: Boolean = !compatOutput 523 524 /** A baseline to check against */ 525 var baseline: Baseline? = null 526 527 /** Whether all baseline files need to be updated */ 528 var updateBaseline = false 529 530 /** If updating baselines, don't fail the build */ 531 var passBaselineUpdates = false 532 533 /** If updating baselines and the baseline is empty, delete the file */ 534 var deleteEmptyBaselines = false 535 536 /** Whether the baseline should only contain errors */ 537 var baselineErrorsOnly = false 538 539 /** 540 * Whether to omit locations for warnings and errors. This is not a flag exposed to users 541 * or listed in help; this is intended for the unit test suite, used for example for the 542 * test which checks compatibility between signature and API files where the paths vary. 543 */ 544 var omitLocations = false 545 546 /** Directory to write signature files to, if any. */ 547 var androidJarSignatureFiles: File? = null 548 549 /** 550 * The language level to use for Java files, set with [ARG_JAVA_SOURCE] 551 */ 552 var javaLanguageLevel: LanguageLevel = LanguageLevel.JDK_1_8 553 554 /** Map from XML API descriptor file to corresponding artifact id name */ 555 val artifactRegistrations = ArtifactTagger() 556 557 /** List of signature files to export as JDiff files */ 558 val convertToXmlFiles: List<ConvertFile> = mutableConvertToXmlFiles 559 560 enum class TypedefMode { 561 NONE, 562 REFERENCE, 563 INLINE 564 } 565 566 /** How to handle typedef annotations in signature files; corresponds to $ARG_TYPEDEFS_IN_SIGNATURES */ 567 var typedefMode = TypedefMode.NONE 568 569 /** File conversion tasks */ 570 data class ConvertFile( 571 val fromApiFile: File, 572 val outputFile: File, 573 val baseApiFile: File? = null, 574 val strip: Boolean = false, 575 val outputFormat: FileFormat = FileFormat.JDIFF 576 ) 577 578 /** Temporary folder to use instead of the JDK default, if any */ 579 var tempFolder: File? = null 580 581 init { 582 // Pre-check whether --color/--no-color is present and use that to decide how 583 // to emit the banner even before we emit errors 584 if (args.contains(ARG_NO_COLOR)) { 585 color = false 586 } else if (args.contains(ARG_COLOR) || args.contains("-android")) { 587 color = true 588 } 589 // empty args: only when building initial default Options (options field 590 // at the top of this file; replaced once the driver runs and passes in 591 // a real argv. Don't print a banner when initializing the default options.) 592 if (args.isNotEmpty() && !args.contains(ARG_QUIET) && !args.contains(ARG_NO_BANNER) && 593 !args.contains(ARG_VERSION) 594 ) { 595 if (color) { 596 stdout.print(colorized(BANNER.trimIndent(), TerminalColor.BLUE)) 597 } else { 598 stdout.println(BANNER.trimIndent()) 599 } 600 stdout.println() 601 stdout.flush() 602 } 603 604 var androidJarPatterns: MutableList<String>? = null 605 var currentJar: File? = null 606 var updateBaselineFile: File? = null 607 var baselineFile: File? = null 608 var mergeBaseline = false 609 var delayedCheckApiFiles = false 610 var skipGenerateAnnotations = false 611 612 var index = 0 613 while (index < args.size) { 614 val arg = args[index] 615 616 when (arg) { 617 ARG_HELP, "-h", "-?" -> { 618 helpAndQuit(color) 619 } 620 621 ARG_QUIET -> { 622 quiet = true; verbose = false 623 } 624 625 ARG_VERBOSE -> { 626 verbose = true; quiet = false 627 } 628 629 ARG_VERSION -> { 630 throw DriverException(stdout = "$PROGRAM_NAME version: ${Version.VERSION}") 631 } 632 633 ARG_COMPAT_OUTPUT -> compatOutput = true 634 635 // For now we don't distinguish between bootclasspath and classpath 636 ARG_CLASS_PATH, "-classpath", "-bootclasspath" -> { 637 val path = getValue(args, ++index) 638 mutableClassPath.addAll(stringToExistingDirsOrJars(path)) 639 } 640 641 ARG_SOURCE_PATH, "--sources", "--sourcepath", "-sourcepath" -> { 642 val path = getValue(args, ++index) 643 if (path.isBlank()) { 644 // Don't compute absolute path; we want to skip this file later on. 645 // For current directory one should use ".", not "". 646 mutableSourcePath.add(File("")) 647 } else { 648 if (path.endsWith(SdkConstants.DOT_JAVA)) { 649 throw DriverException( 650 "$arg should point to a source root directory, not a source file ($path)" 651 ) 652 } 653 mutableSourcePath.addAll(stringToExistingDirsOrJars(path)) 654 } 655 } 656 657 ARG_SOURCE_FILES -> { 658 val listString = getValue(args, ++index) 659 listString.split(",").forEach { path -> 660 mutableSources.addAll(stringToExistingFiles(path)) 661 } 662 } 663 664 ARG_SUBTRACT_API -> { 665 if (subtractApi != null) { 666 throw DriverException(stderr = "Only one $ARG_SUBTRACT_API can be supplied") 667 } 668 subtractApi = stringToExistingFile(getValue(args, ++index)) 669 } 670 671 // TODO: Remove the legacy --merge-annotations flag once it's no longer used to update P docs 672 ARG_MERGE_QUALIFIER_ANNOTATIONS, "--merge-zips", "--merge-annotations" -> mutableMergeQualifierAnnotations.addAll( 673 stringToExistingDirsOrFiles( 674 getValue(args, ++index) 675 ) 676 ) 677 678 ARG_MERGE_INCLUSION_ANNOTATIONS -> mutableMergeInclusionAnnotations.addAll( 679 stringToExistingDirsOrFiles( 680 getValue(args, ++index) 681 ) 682 ) 683 684 ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS -> { 685 validateNullabilityFromMergedStubs = true 686 nullabilityAnnotationsValidator = 687 nullabilityAnnotationsValidator ?: NullabilityAnnotationsValidator() 688 } 689 ARG_VALIDATE_NULLABILITY_FROM_LIST -> { 690 validateNullabilityFromList = stringToExistingFile(getValue(args, ++index)) 691 nullabilityAnnotationsValidator = 692 nullabilityAnnotationsValidator ?: NullabilityAnnotationsValidator() 693 } 694 ARG_NULLABILITY_WARNINGS_TXT -> 695 nullabilityWarningsTxt = stringToNewFile(getValue(args, ++index)) 696 ARG_NULLABILITY_ERRORS_NON_FATAL -> 697 nullabilityErrorsFatal = false 698 699 "-sdkvalues", ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index)) 700 ARG_API, "-api" -> apiFile = stringToNewFile(getValue(args, ++index)) 701 ARG_XML_API -> apiXmlFile = stringToNewFile(getValue(args, ++index)) 702 ARG_DEX_API, "-dexApi" -> dexApiFile = stringToNewFile(getValue(args, ++index)) 703 ARG_DEX_API_MAPPING, "-apiMapping" -> dexApiMappingFile = stringToNewFile(getValue(args, ++index)) 704 705 ARG_PRIVATE_API, "-privateApi" -> privateApiFile = stringToNewFile(getValue(args, ++index)) 706 ARG_PRIVATE_DEX_API, "-privateDexApi" -> privateDexApiFile = stringToNewFile(getValue(args, ++index)) 707 708 ARG_REMOVED_API, "-removedApi" -> removedApiFile = stringToNewFile(getValue(args, ++index)) 709 ARG_REMOVED_DEX_API, "-removedDexApi" -> removedDexApiFile = stringToNewFile(getValue(args, ++index)) 710 711 ARG_EXACT_API, "-exactApi" -> { 712 getValue(args, ++index) // prevent next arg from tripping up parser 713 unimplemented(arg) // Not yet implemented (because it seems to no longer be hooked up in doclava1) 714 } 715 716 ARG_MANIFEST, "-manifest" -> manifest = stringToExistingFile(getValue(args, ++index)) 717 718 ARG_SHOW_ANNOTATION, "-showAnnotation" -> mutableShowAnnotations.add(getValue(args, ++index)) 719 720 ARG_SHOW_SINGLE_ANNOTATION -> { 721 val annotation = getValue(args, ++index) 722 mutableShowSingleAnnotations.add(annotation) 723 // These should also be counted as show annotations 724 mutableShowAnnotations.add(annotation) 725 } 726 727 ARG_SHOW_UNANNOTATED, "-showUnannotated" -> showUnannotated = true 728 729 "--showAnnotationOverridesVisibility" -> { 730 unimplemented(arg) 731 showAnnotationOverridesVisibility = true 732 } 733 734 ARG_HIDE_ANNOTATION, "--hideAnnotations", "-hideAnnotation" -> 735 mutableHideAnnotations.add(getValue(args, ++index)) 736 737 ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index)) 738 ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index)) 739 ARG_STUBS_SOURCE_LIST -> stubsSourceList = stringToNewFile(getValue(args, ++index)) 740 ARG_DOC_STUBS_SOURCE_LIST -> docStubsSourceList = stringToNewFile(getValue(args, ++index)) 741 742 ARG_EXCLUDE_ANNOTATIONS -> generateAnnotations = false 743 744 ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS -> includeDocumentationInStubs = false 745 746 // Note that this only affects stub generation, not signature files. 747 // For signature files, clear the compatibility mode 748 // (--annotations-in-signatures) 749 ARG_INCLUDE_ANNOTATIONS -> generateAnnotations = true 750 751 // Flag used by test suite to avoid including locations in 752 // the output when diffing against golden files 753 "--omit-locations" -> omitLocations = true 754 755 ARG_PROGUARD, "-proguard" -> proguard = stringToNewFile(getValue(args, ++index)) 756 757 ARG_HIDE_PACKAGE, "-hidePackage" -> mutableHidePackages.add(getValue(args, ++index)) 758 759 ARG_STUB_PACKAGES, "-stubpackages" -> { 760 val packages = getValue(args, ++index) 761 val filter = stubPackages ?: run { 762 val newFilter = PackageFilter() 763 stubPackages = newFilter 764 newFilter 765 } 766 filter.addPackages(packages) 767 } 768 769 ARG_STUB_IMPORT_PACKAGES, "-stubimportpackages" -> { 770 val packages = getValue(args, ++index) 771 for (pkg in packages.split(File.pathSeparatorChar)) { 772 mutableStubImportPackages.add(pkg) 773 mutableHidePackages.add(pkg) 774 } 775 } 776 777 "--skip-emit-packages" -> { 778 val packages = getValue(args, ++index) 779 mutableSkipEmitPackages += packages.split(File.pathSeparatorChar) 780 } 781 782 ARG_TYPEDEFS_IN_SIGNATURES -> { 783 val type = getValue(args, ++index) 784 typedefMode = when (type) { 785 "ref" -> TypedefMode.REFERENCE 786 "inline" -> TypedefMode.INLINE 787 "none" -> TypedefMode.NONE 788 else -> throw DriverException( 789 stderr = "$ARG_TYPEDEFS_IN_SIGNATURES must be one of ref, inline, none; was $type") 790 } 791 } 792 793 ARG_BASELINE -> { 794 val relative = getValue(args, ++index) 795 assert(baselineFile == null) { "Only one baseline is allowed; found both $baselineFile and $relative" } 796 baselineFile = stringToExistingFile(relative) 797 } 798 799 ARG_UPDATE_BASELINE, ARG_MERGE_BASELINE -> { 800 updateBaseline = true 801 mergeBaseline = arg == ARG_MERGE_BASELINE 802 if (index < args.size - 1) { 803 val nextArg = args[index + 1] 804 if (!nextArg.startsWith("-")) { 805 val file = stringToNewOrExistingFile(nextArg) 806 index++ 807 updateBaselineFile = file 808 } 809 } 810 } 811 ARG_PASS_BASELINE_UPDATES -> passBaselineUpdates = true 812 ARG_DELETE_EMPTY_BASELINES -> deleteEmptyBaselines = true 813 814 ARG_PUBLIC, "-public" -> docLevel = DocLevel.PUBLIC 815 ARG_PROTECTED, "-protected" -> docLevel = DocLevel.PROTECTED 816 ARG_PACKAGE, "-package" -> docLevel = DocLevel.PACKAGE 817 ARG_PRIVATE, "-private" -> docLevel = DocLevel.PRIVATE 818 ARG_HIDDEN, "-hidden" -> docLevel = DocLevel.HIDDEN 819 820 ARG_INPUT_API_JAR -> apiJar = stringToExistingFile(getValue(args, ++index)) 821 822 ARG_EXTRACT_ANNOTATIONS -> externalAnnotations = stringToNewFile(getValue(args, ++index)) 823 ARG_COPY_ANNOTATIONS -> { 824 privateAnnotationsSource = stringToExistingDir(getValue(args, ++index)) 825 privateAnnotationsTarget = stringToNewDir(getValue(args, ++index)) 826 } 827 ARG_REWRITE_ANNOTATIONS -> rewriteAnnotations = stringToExistingDirsOrJars(getValue(args, ++index)) 828 ARG_INCLUDE_ANNOTATION_CLASSES -> copyStubAnnotationsFrom = stringToExistingDir(getValue(args, ++index)) 829 ARG_INCLUDE_SOURCE_RETENTION -> includeSourceRetentionAnnotations = true 830 831 "--previous-api" -> { 832 migrateNullsFrom = stringToExistingFile(getValue(args, ++index)) 833 reporter.report( 834 Errors.DEPRECATED_OPTION, null as File?, 835 "--previous-api is deprecated; instead " + 836 "use $ARG_MIGRATE_NULLNESS $migrateNullsFrom" 837 ) 838 } 839 840 ARG_MIGRATE_NULLNESS -> { 841 // See if the next argument specifies the nullness API codebase 842 if (index < args.size - 1) { 843 val nextArg = args[index + 1] 844 if (!nextArg.startsWith("-")) { 845 val file = fileForPath(nextArg) 846 if (file.isFile) { 847 index++ 848 migrateNullsFrom = file 849 } 850 } 851 } 852 } 853 854 "--current-api" -> { 855 val file = stringToExistingFile(getValue(args, ++index)) 856 mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV)) 857 reporter.report( 858 Errors.DEPRECATED_OPTION, null as File?, 859 "--current-api is deprecated; instead " + 860 "use $ARG_CHECK_COMPATIBILITY_API_CURRENT" 861 ) 862 } 863 864 ARG_CHECK_COMPATIBILITY -> { 865 // See if the next argument specifies the compatibility check. 866 // Synonymous with ARG_CHECK_COMPATIBILITY_API_CURRENT, though 867 // for backwards compatibility with earlier versions and usages 868 // can also works in conjunction with ARG_CURRENT_API where the 869 // usage was to use ARG_CURRENT_API to point to the API file and 870 // then specify ARG_CHECK_COMPATIBILITY (without an argument) to 871 // indicate that the current api should also be checked for 872 // compatibility. 873 if (index < args.size - 1) { 874 val nextArg = args[index + 1] 875 if (!nextArg.startsWith("-")) { 876 val file = fileForPath(nextArg) 877 if (file.isFile) { 878 index++ 879 mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV)) 880 } 881 } 882 } 883 } 884 885 ARG_CHECK_COMPATIBILITY_API_CURRENT -> { 886 val file = stringToExistingFile(getValue(args, ++index)) 887 mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV)) 888 } 889 890 ARG_CHECK_COMPATIBILITY_API_RELEASED -> { 891 val file = stringToExistingFile(getValue(args, ++index)) 892 mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.RELEASED)) 893 } 894 895 ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT -> { 896 val file = stringToExistingFile(getValue(args, ++index)) 897 mutableCompatibilityChecks.add(CheckRequest(file, ApiType.REMOVED, ReleaseType.DEV)) 898 } 899 900 ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED -> { 901 val file = stringToExistingFile(getValue(args, ++index)) 902 mutableCompatibilityChecks.add(CheckRequest(file, ApiType.REMOVED, ReleaseType.RELEASED)) 903 } 904 905 ARG_ALLOW_COMPATIBLE_DIFFERENCES -> allowCompatibleDifferences = true 906 ARG_NO_NATIVE_DIFF -> noNativeDiff = true 907 908 // Compat flag for the old API check command, invoked from build/make/core/definitions.mk: 909 "--check-api-files" -> { 910 if (index < args.size - 1 && args[index + 1].startsWith("-")) { 911 // Work around bug where --check-api-files is invoked with all 912 // the other metalava args before the 4 files; this will be 913 // fixed by https://android-review.googlesource.com/c/platform/build/+/874473 914 delayedCheckApiFiles = true 915 } else { 916 val stableApiFile = stringToExistingFile(getValue(args, ++index)) 917 val apiFileToBeTested = stringToExistingFile(getValue(args, ++index)) 918 val stableRemovedApiFile = stringToExistingFile(getValue(args, ++index)) 919 val removedApiFileToBeTested = stringToExistingFile(getValue(args, ++index)) 920 mutableCompatibilityChecks.add( 921 CheckRequest( 922 stableApiFile, 923 ApiType.PUBLIC_API, 924 ReleaseType.RELEASED, 925 apiFileToBeTested 926 ) 927 ) 928 mutableCompatibilityChecks.add( 929 CheckRequest( 930 stableRemovedApiFile, 931 ApiType.REMOVED, 932 ReleaseType.RELEASED, 933 removedApiFileToBeTested 934 ) 935 ) 936 } 937 } 938 939 ARG_ANNOTATION_COVERAGE_STATS -> dumpAnnotationStatistics = true 940 ARG_ANNOTATION_COVERAGE_OF -> mutableAnnotationCoverageOf.addAll( 941 stringToExistingDirsOrJars( 942 getValue(args, ++index) 943 ) 944 ) 945 ARG_WRITE_CLASS_COVERAGE_TO -> { 946 annotationCoverageClassReport = stringToNewFile(getValue(args, ++index)) 947 } 948 ARG_WRITE_MEMBER_COVERAGE_TO -> { 949 annotationCoverageMemberReport = stringToNewFile(getValue(args, ++index)) 950 } 951 952 ARG_ERROR, "-error" -> Errors.setErrorLevel(getValue(args, ++index), Severity.ERROR, true) 953 ARG_WARNING, "-warning" -> Errors.setErrorLevel(getValue(args, ++index), Severity.WARNING, true) 954 ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT, true) 955 ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN, true) 956 957 ARG_WARNINGS_AS_ERRORS -> warningsAreErrors = true 958 ARG_LINTS_AS_ERRORS -> lintsAreErrors = true 959 "-werror" -> { 960 // Temporarily disabled; this is used in various builds but is pretty much 961 // never what we want. 962 // warningsAreErrors = true 963 } 964 "-lerror" -> { 965 // Temporarily disabled; this is used in various builds but is pretty much 966 // never what we want. 967 // lintsAreErrors = true 968 } 969 970 ARG_API_LINT -> { 971 checkApi = true 972 if (index < args.size - 1) { 973 val nextArg = args[index + 1] 974 if (!nextArg.startsWith("-")) { 975 val file = stringToExistingFile(nextArg) 976 if (file.isFile) { 977 index++ 978 checkApiBaselineApiFile = file 979 } 980 } 981 } 982 } 983 984 ARG_CHECK_KOTLIN_INTEROP -> checkKotlinInterop = true 985 986 ARG_COLOR -> color = true 987 ARG_NO_COLOR -> color = false 988 ARG_NO_BANNER -> { 989 // Already processed above but don't flag it here as invalid 990 } 991 992 ARG_OMIT_COMMON_PACKAGES, "$ARG_OMIT_COMMON_PACKAGES=yes" -> compatibility.omitCommonPackages = true 993 "$ARG_OMIT_COMMON_PACKAGES=no" -> compatibility.omitCommonPackages = false 994 995 ARG_SKIP_JAVA_IN_COVERAGE_REPORT -> omitRuntimePackageStats = true 996 997 ARG_UNHIDE_CLASSPATH_CLASSES -> hideClasspathClasses = false 998 ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES -> allowReferencingUnknownClasses = true 999 ARG_NO_UNKNOWN_CLASSES -> noUnknownClasses = true 1000 1001 // Extracting API levels 1002 ARG_ANDROID_JAR_PATTERN -> { 1003 val list = androidJarPatterns ?: run { 1004 val list = arrayListOf<String>() 1005 androidJarPatterns = list 1006 list 1007 } 1008 list.add(getValue(args, ++index)) 1009 } 1010 ARG_CURRENT_VERSION -> { 1011 currentApiLevel = Integer.parseInt(getValue(args, ++index)) 1012 if (currentApiLevel <= 26) { 1013 throw DriverException("Suspicious currentApi=$currentApiLevel, expected at least 27") 1014 } 1015 } 1016 ARG_CURRENT_CODENAME -> { 1017 currentCodeName = getValue(args, ++index) 1018 } 1019 ARG_CURRENT_JAR -> { 1020 currentJar = stringToExistingFile(getValue(args, ++index)) 1021 } 1022 ARG_GENERATE_API_LEVELS -> { 1023 generateApiLevelXml = stringToNewFile(getValue(args, ++index)) 1024 } 1025 ARG_APPLY_API_LEVELS -> { 1026 applyApiLevelsXml = if (args.contains(ARG_GENERATE_API_LEVELS)) { 1027 // If generating the API file at the same time, it doesn't have 1028 // to already exist 1029 stringToNewFile(getValue(args, ++index)) 1030 } else { 1031 stringToExistingFile(getValue(args, ++index)) 1032 } 1033 } 1034 1035 ARG_NO_DOCS, "-nodocs" -> noDocs = true 1036 1037 ARG_UPDATE_API, "--update-api" -> onlyUpdateApi = true 1038 ARG_CHECK_API -> onlyCheckApi = true 1039 1040 ARG_GENERATE_DOCUMENTATION -> { 1041 // Digest all the remaining arguments. 1042 // Allow "STUBS_DIR" to reference the stubs directory. 1043 var prev = "" 1044 invokeDocumentationToolArguments = args.slice(++index until args.size).mapNotNull { 1045 var argument = it 1046 // When generating documentation, use the doc stubs directory rather than the 1047 // original source path 1048 val docStubsDir = docStubsDir 1049 if (docStubsDir != null && (prev == ARG_SOURCE_PATH || prev == "-sourcepath") && 1050 !argument.contains(docStubsDir.path) 1051 ) { 1052 // Insert the doc stubs as the default place to look for sources 1053 argument = docStubsDir.path 1054 } 1055 prev = it 1056 1057 if (argument == "STUBS_DIR" && docStubsDir != null) { 1058 docStubsDir.path 1059 } else if (argument == "STUBS_DIR" && stubsDir != null) { 1060 stubsDir?.path 1061 } else if (argument == "DOCS_STUBS_DIR" && docStubsDir != null) { 1062 docStubsDir.path 1063 } else if (argument == "DOC_STUBS_SOURCE_LIST" && docStubsSourceList != null) { 1064 "@${docStubsSourceList?.path}" 1065 } else if (argument == "STUBS_SOURCE_LIST" && stubsSourceList != null) { 1066 "@${stubsSourceList?.path}" 1067 } else if (argument == "STUBS_SOURCE_LIST" && docStubsSourceList != null) { 1068 "@${docStubsSourceList?.path}" 1069 } else { 1070 argument 1071 } 1072 }.toTypedArray() 1073 1074 index = args.size // jump to end of argument loop 1075 } 1076 1077 ARG_REGISTER_ARTIFACT, "-artifact" -> { 1078 val descriptor = stringToExistingFile(getValue(args, ++index)) 1079 val artifactId = getValue(args, ++index) 1080 artifactRegistrations.register(artifactId, descriptor) 1081 } 1082 1083 ARG_CONVERT_TO_JDIFF, 1084 ARG_CONVERT_TO_V1, 1085 ARG_CONVERT_TO_V2, 1086 // doclava compatibility: 1087 "-convert2xml", 1088 "-convert2xmlnostrip" -> { 1089 val strip = arg == "-convert2xml" 1090 val format = when (arg) { 1091 ARG_CONVERT_TO_V1 -> FileFormat.V1 1092 ARG_CONVERT_TO_V2 -> FileFormat.V2 1093 else -> FileFormat.JDIFF 1094 } 1095 1096 val signatureFile = stringToExistingFile(getValue(args, ++index)) 1097 val outputFile = stringToNewFile(getValue(args, ++index)) 1098 mutableConvertToXmlFiles.add(ConvertFile(signatureFile, outputFile, null, strip, format)) 1099 } 1100 1101 ARG_CONVERT_NEW_TO_JDIFF, 1102 ARG_CONVERT_NEW_TO_V1, 1103 ARG_CONVERT_NEW_TO_V2, 1104 // doclava compatibility: 1105 "-new_api", 1106 "-new_api_no_strip" -> { 1107 val format = when (arg) { 1108 ARG_CONVERT_NEW_TO_V1 -> FileFormat.V1 1109 ARG_CONVERT_NEW_TO_V2 -> FileFormat.V2 1110 else -> FileFormat.JDIFF 1111 } 1112 val strip = arg == "-new_api" 1113 if (arg != ARG_CONVERT_NEW_TO_JDIFF) { 1114 // Using old doclava flags: Compatibility behavior: don't include fields in the output 1115 compatibility.includeFieldsInApiDiff = false 1116 } 1117 1118 val baseFile = stringToExistingFile(getValue(args, ++index)) 1119 val signatureFile = stringToExistingFile(getValue(args, ++index)) 1120 val jDiffFile = stringToNewFile(getValue(args, ++index)) 1121 mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, baseFile, strip, format)) 1122 } 1123 1124 "--write-android-jar-signatures" -> { 1125 val root = stringToExistingDir(getValue(args, ++index)) 1126 if (!File(root, "prebuilts/sdk").isDirectory) { 1127 throw DriverException("$androidJarSignatureFiles does not point to an Android source tree") 1128 } 1129 androidJarSignatureFiles = root 1130 } 1131 1132 "-encoding" -> { 1133 val value = getValue(args, ++index) 1134 if (value.toUpperCase() != "UTF-8") { 1135 throw DriverException("$value: Only UTF-8 encoding is supported") 1136 } 1137 } 1138 1139 ARG_JAVA_SOURCE, "-source" -> { 1140 val value = getValue(args, ++index) 1141 val level = LanguageLevel.parse(value) 1142 when { 1143 level == null -> throw DriverException("$value is not a valid or supported Java language level") 1144 level.isLessThan(LanguageLevel.JDK_1_7) -> throw DriverException("$arg must be at least 1.7") 1145 else -> javaLanguageLevel = level 1146 } 1147 } 1148 1149 "--temp-folder" -> { 1150 tempFolder = stringToNewOrExistingDir(getValue(args, ++index)) 1151 } 1152 1153 // Option only meant for tests (not documented); doesn't work in all cases (to do that we'd 1154 // need JNA to call libc) 1155 "--pwd" -> { 1156 val pwd = stringToExistingDir(getValue(args, ++index)).absoluteFile 1157 System.setProperty("user.dir", pwd.path) 1158 } 1159 1160 "--noop", "--no-op" -> { 1161 } 1162 1163 // Doclava1 flag: Already the behavior in metalava 1164 "-keepstubcomments" -> { 1165 } 1166 1167 // Unimplemented doclava1 flags (no arguments) 1168 "-quiet", 1169 "-yamlV2" -> { 1170 unimplemented(arg) 1171 } 1172 1173 "-android" -> { // partially implemented: Pick up the color hint, but there may be other implications 1174 color = true 1175 unimplemented(arg) 1176 } 1177 1178 "-stubsourceonly" -> { 1179 /* noop */ 1180 } 1181 1182 // Unimplemented doclava1 flags (1 argument) 1183 "-d" -> { 1184 unimplemented(arg) 1185 index++ 1186 } 1187 1188 // Unimplemented doclava1 flags (2 arguments) 1189 "-since" -> { 1190 unimplemented(arg) 1191 index += 2 1192 } 1193 1194 // doclava1 doc-related flags: only supported here to make this command a drop-in 1195 // replacement 1196 "-referenceonly", 1197 "-devsite", 1198 "-ignoreJdLinks", 1199 "-nodefaultassets", 1200 "-parsecomments", 1201 "-offlinemode", 1202 "-gcmref", 1203 "-metadataDebug", 1204 "-includePreview", 1205 "-staticonly", 1206 "-navtreeonly", 1207 "-atLinksNavtree" -> { 1208 javadoc(arg) 1209 } 1210 1211 // doclava1 flags with 1 argument 1212 "-doclet", 1213 "-docletpath", 1214 "-templatedir", 1215 "-htmldir", 1216 "-knowntags", 1217 "-resourcesdir", 1218 "-resourcesoutdir", 1219 "-yaml", 1220 "-apidocsdir", 1221 "-toroot", 1222 "-samplegroup", 1223 "-samplesdir", 1224 "-dac_libraryroot", 1225 "-dac_dataname", 1226 "-title", 1227 "-proofread", 1228 "-todo", 1229 "-overview" -> { 1230 javadoc(arg) 1231 index++ 1232 } 1233 1234 // doclava1 flags with two arguments 1235 "-federate", 1236 "-federationapi", 1237 "-htmldir2" -> { 1238 javadoc(arg) 1239 index += 2 1240 } 1241 1242 // doclava1 flags with three arguments 1243 "-samplecode" -> { 1244 javadoc(arg) 1245 index += 3 1246 } 1247 1248 // doclava1 flag with variable number of arguments; skip everything until next arg 1249 "-hdf" -> { 1250 javadoc(arg) 1251 index++ 1252 while (index < args.size) { 1253 if (args[index].startsWith("-")) { 1254 break 1255 } 1256 index++ 1257 } 1258 index-- 1259 } 1260 1261 else -> { 1262 if (arg.startsWith("-J-") || arg.startsWith("-XD")) { 1263 // -J: mechanism to pass extra flags to javadoc, e.g. 1264 // -J-XX:-OmitStackTraceInFastThrow 1265 // -XD: mechanism to set properties, e.g. 1266 // -XDignore.symbol.file 1267 javadoc(arg) 1268 } else if (arg.startsWith(ARG_OUTPUT_KOTLIN_NULLS)) { 1269 outputKotlinStyleNulls = if (arg == ARG_OUTPUT_KOTLIN_NULLS) { 1270 true 1271 } else { 1272 yesNo(arg.substring(ARG_OUTPUT_KOTLIN_NULLS.length + 1)) 1273 } 1274 } else if (arg.startsWith(ARG_INPUT_KOTLIN_NULLS)) { 1275 inputKotlinStyleNulls = if (arg == ARG_INPUT_KOTLIN_NULLS) { 1276 true 1277 } else { 1278 yesNo(arg.substring(ARG_INPUT_KOTLIN_NULLS.length + 1)) 1279 } 1280 } else if (arg.startsWith(ARG_OUTPUT_DEFAULT_VALUES)) { 1281 outputDefaultValues = if (arg == ARG_OUTPUT_DEFAULT_VALUES) { 1282 true 1283 } else { 1284 yesNo(arg.substring(ARG_OUTPUT_DEFAULT_VALUES.length + 1)) 1285 } 1286 } else if (arg.startsWith(ARG_OMIT_COMMON_PACKAGES)) { 1287 compatibility.omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) { 1288 true 1289 } else { 1290 yesNo(arg.substring(ARG_OMIT_COMMON_PACKAGES.length + 1)) 1291 } 1292 } else if (arg.startsWith(ARG_COMPAT_OUTPUT)) { 1293 compatOutput = if (arg == ARG_COMPAT_OUTPUT) 1294 true 1295 else yesNo(arg.substring(ARG_COMPAT_OUTPUT.length + 1)) 1296 } else if (arg.startsWith(ARG_INCLUDE_SIG_VERSION)) { 1297 includeSignatureFormatVersion = if (arg == ARG_INCLUDE_SIG_VERSION) 1298 true 1299 else yesNo(arg.substring(ARG_INCLUDE_SIG_VERSION.length + 1)) 1300 } else if (arg.startsWith(ARG_FORMAT)) { 1301 outputFormat = when (arg) { 1302 "$ARG_FORMAT=v1" -> { 1303 FileFormat.V1 1304 } 1305 "$ARG_FORMAT=v2", "$ARG_FORMAT=recommended" -> { 1306 FileFormat.V2 1307 } 1308 "$ARG_FORMAT=v3", "$ARG_FORMAT=latest" -> { 1309 FileFormat.V3 1310 } 1311 else -> throw DriverException(stderr = "Unexpected signature format; expected v1, v2 or v3") 1312 } 1313 outputFormat.configureOptions(this, compatibility) 1314 } else if (arg.startsWith("-")) { 1315 // Compatibility flag; map to mutable properties in the Compatibility 1316 // class and assign it 1317 val compatibilityArg = findCompatibilityFlag(arg) 1318 if (compatibilityArg != null) { 1319 val dash = arg.indexOf('=') 1320 val value = if (dash == -1) { 1321 true 1322 } else { 1323 arg.substring(dash + 1).toBoolean() 1324 } 1325 compatibilityArg.set(compatibility, value) 1326 } else { 1327 // Some other argument: display usage info and exit 1328 1329 val usage = getUsage(includeHeader = false, colorize = color) 1330 throw DriverException(stderr = "Invalid argument $arg\n\n$usage") 1331 } 1332 } else { 1333 if (delayedCheckApiFiles) { 1334 delayedCheckApiFiles = false 1335 val stableApiFile = stringToExistingFile(arg) 1336 val apiFileToBeTested = stringToExistingFile(getValue(args, ++index)) 1337 val stableRemovedApiFile = stringToExistingFile(getValue(args, ++index)) 1338 val removedApiFileToBeTested = stringToExistingFile(getValue(args, ++index)) 1339 mutableCompatibilityChecks.add( 1340 CheckRequest( 1341 stableApiFile, 1342 ApiType.PUBLIC_API, 1343 ReleaseType.RELEASED, 1344 apiFileToBeTested 1345 ) 1346 ) 1347 mutableCompatibilityChecks.add( 1348 CheckRequest( 1349 stableRemovedApiFile, 1350 ApiType.REMOVED, 1351 ReleaseType.RELEASED, 1352 removedApiFileToBeTested 1353 ) 1354 ) 1355 } else { 1356 // All args that don't start with "-" are taken to be filenames 1357 mutableSources.addAll(stringToExistingFiles(arg)) 1358 1359 // Temporary workaround for 1360 // aosp/I73ff403bfc3d9dfec71789a3e90f9f4ea95eabe3 1361 if (arg.endsWith("hwbinder-stubs-docs-stubs.srcjar.rsp")) { 1362 skipGenerateAnnotations = true 1363 } 1364 } 1365 } 1366 } 1367 } 1368 1369 ++index 1370 } 1371 1372 if (generateApiLevelXml != null) { 1373 val patterns = androidJarPatterns ?: run { 1374 mutableListOf<String>() 1375 } 1376 // Fallbacks 1377 patterns.add("prebuilts/tools/common/api-versions/android-%/android.jar") 1378 patterns.add("prebuilts/sdk/%/public/android.jar") 1379 apiLevelJars = findAndroidJars(patterns, currentApiLevel, currentCodeName, currentJar) 1380 } 1381 1382 // outputKotlinStyleNulls implies format=v3 1383 if (outputKotlinStyleNulls) { 1384 outputFormat = FileFormat.V3 1385 outputFormat.configureOptions(this, compatibility) 1386 } 1387 1388 // If the caller has not explicitly requested that unannotated classes and 1389 // members should be shown in the output then only show them if no annotations were provided. 1390 if (!showUnannotated && showAnnotations.isEmpty()) { 1391 showUnannotated = true 1392 } 1393 1394 if (noUnknownClasses) { 1395 allowReferencingUnknownClasses = false 1396 } 1397 1398 if (skipGenerateAnnotations) { 1399 generateAnnotations = false 1400 } 1401 1402 if (onlyUpdateApi) { 1403 if (onlyCheckApi) { 1404 throw DriverException(stderr = "Cannot supply both $ARG_UPDATE_API and $ARG_CHECK_API at the same time") 1405 } 1406 // We're running in update API mode: cancel other "action" flags; only signature file generation 1407 // flags count 1408 annotationCoverageClassReport = null 1409 annotationCoverageMemberReport = null 1410 dumpAnnotationStatistics = false 1411 apiLevelJars = null 1412 generateApiLevelXml = null 1413 applyApiLevelsXml = null 1414 androidJarSignatureFiles = null 1415 stubsDir = null 1416 docStubsDir = null 1417 stubsSourceList = null 1418 docStubsSourceList = null 1419 sdkValueDir = null 1420 externalAnnotations = null 1421 proguard = null 1422 noDocs = true 1423 invokeDocumentationToolArguments = emptyArray() 1424 checkKotlinInterop = false 1425 mutableCompatibilityChecks.clear() 1426 mutableAnnotationCoverageOf.clear() 1427 artifactRegistrations.clear() 1428 mutableConvertToXmlFiles.clear() 1429 nullabilityAnnotationsValidator = null 1430 nullabilityWarningsTxt = null 1431 validateNullabilityFromMergedStubs = false 1432 validateNullabilityFromMergedStubs = false 1433 validateNullabilityFromList = null 1434 } else if (onlyCheckApi) { 1435 annotationCoverageClassReport = null 1436 annotationCoverageMemberReport = null 1437 dumpAnnotationStatistics = false 1438 apiLevelJars = null 1439 generateApiLevelXml = null 1440 applyApiLevelsXml = null 1441 androidJarSignatureFiles = null 1442 stubsDir = null 1443 docStubsDir = null 1444 stubsSourceList = null 1445 docStubsSourceList = null 1446 sdkValueDir = null 1447 externalAnnotations = null 1448 proguard = null 1449 noDocs = true 1450 invokeDocumentationToolArguments = emptyArray() 1451 checkKotlinInterop = false 1452 mutableAnnotationCoverageOf.clear() 1453 artifactRegistrations.clear() 1454 mutableConvertToXmlFiles.clear() 1455 nullabilityAnnotationsValidator = null 1456 nullabilityWarningsTxt = null 1457 validateNullabilityFromMergedStubs = false 1458 validateNullabilityFromMergedStubs = false 1459 validateNullabilityFromList = null 1460 apiFile = null 1461 apiXmlFile = null 1462 privateApiFile = null 1463 dexApiFile = null 1464 dexApiMappingFile = null 1465 privateDexApiFile = null 1466 removedApiFile = null 1467 removedDexApiFile = null 1468 } 1469 1470 if (baselineFile == null) { 1471 val defaultBaselineFile = getDefaultBaselineFile() 1472 if (defaultBaselineFile != null && defaultBaselineFile.isFile) { 1473 if (updateBaseline && updateBaselineFile == null) { 1474 updateBaselineFile = defaultBaselineFile 1475 } 1476 baseline = Baseline(defaultBaselineFile, updateBaselineFile, mergeBaseline) 1477 } else if (updateBaselineFile != null) { 1478 baseline = Baseline(null, updateBaselineFile, mergeBaseline) 1479 } 1480 } else { 1481 // Add helpful doc in AOSP baseline files? 1482 val headerComment = if (isBuildingAndroid()) 1483 "// See tools/metalava/API-LINT.md for how to update this file.\n\n" 1484 else 1485 "" 1486 if (updateBaseline && updateBaselineFile == null) { 1487 updateBaselineFile = baselineFile 1488 } 1489 baseline = Baseline(baselineFile, updateBaselineFile, mergeBaseline, headerComment) 1490 } 1491 1492 checkFlagConsistency() 1493 } 1494 1495 private fun findCompatibilityFlag(arg: String): KMutableProperty1<Compatibility, Boolean>? { 1496 val index = arg.indexOf('=') 1497 val name = arg 1498 .substring(0, if (index != -1) index else arg.length) 1499 .removePrefix("--") 1500 .replace('-', '_') 1501 val propertyName = SdkVersionInfo.underlinesToCamelCase(name).decapitalize() 1502 return Compatibility::class.memberProperties 1503 .filterIsInstance<KMutableProperty1<Compatibility, Boolean>>() 1504 .find { 1505 it.name == propertyName 1506 } 1507 } 1508 1509 /** 1510 * Produce a default file name for the baseline. It's normally "baseline.txt", but can 1511 * be prefixed by show annotations; e.g. @TestApi -> test-baseline.txt, @SystemApi -> system-baseline.txt, 1512 * etc. 1513 */ 1514 private fun getDefaultBaselineFile(): File? { 1515 if (sourcePath.isNotEmpty() && sourcePath[0].path.isNotBlank()) { 1516 fun annotationToPrefix(qualifiedName: String): String { 1517 val name = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1) 1518 return name.toLowerCase(Locale.US).removeSuffix("api") + "-" 1519 } 1520 val sb = StringBuilder() 1521 showAnnotations.forEach { sb.append(annotationToPrefix(it)) } 1522 sb.append(DEFAULT_BASELINE_NAME) 1523 var base = sourcePath[0] 1524 // Convention: in AOSP, signature files are often in sourcepath/api: let's place baseline 1525 // files there too 1526 val api = File(base, "api") 1527 if (api.isDirectory) { 1528 base = api 1529 } 1530 return File(base, sb.toString()) 1531 } else { 1532 return null 1533 } 1534 } 1535 1536 private fun findAndroidJars( 1537 androidJarPatterns: List<String>, 1538 currentApiLevel: Int, 1539 currentCodeName: String?, 1540 currentJar: File? 1541 ): Array<File> { 1542 1543 @Suppress("NAME_SHADOWING") 1544 val currentApiLevel = if (currentCodeName != null && "REL" != currentCodeName) { 1545 currentApiLevel + 1 1546 } else { 1547 currentApiLevel 1548 } 1549 1550 val apiLevelFiles = mutableListOf<File>() 1551 apiLevelFiles.add(File("there is no api 0")) // api level 0: dummy, should not be processed 1552 val minApi = 1 1553 1554 // Get all the android.jar. They are in platforms-# 1555 var apiLevel = minApi - 1 1556 while (true) { 1557 apiLevel++ 1558 try { 1559 var jar: File? = null 1560 if (apiLevel == currentApiLevel) { 1561 jar = currentJar 1562 } 1563 if (jar == null) { 1564 jar = getAndroidJarFile(apiLevel, androidJarPatterns) 1565 } 1566 if (jar == null || !jar.isFile) { 1567 if (verbose) { 1568 stdout.println("Last API level found: ${apiLevel - 1}") 1569 } 1570 1571 if (apiLevel < 28) { 1572 // Clearly something is wrong with the patterns; this should result in a build error 1573 val argList = mutableListOf<String>() 1574 args.forEachIndexed { index, arg -> 1575 if (arg == ARG_ANDROID_JAR_PATTERN) { 1576 argList.add(args[index + 1]) 1577 } 1578 } 1579 throw DriverException(stderr = "Could not find android.jar for API level $apiLevel; the " + 1580 "$ARG_ANDROID_JAR_PATTERN set might be invalid: ${argList.joinToString()}") 1581 } 1582 1583 break 1584 } 1585 if (verbose) { 1586 stdout.println("Found API $apiLevel at ${jar.path}") 1587 } 1588 apiLevelFiles.add(jar) 1589 } catch (e: IOException) { 1590 e.printStackTrace() 1591 } 1592 } 1593 1594 return apiLevelFiles.toTypedArray() 1595 } 1596 1597 private fun getAndroidJarFile(apiLevel: Int, patterns: List<String>): File? { 1598 return patterns 1599 .map { fileForPath(it.replace("%", Integer.toString(apiLevel))) } 1600 .firstOrNull { it.isFile } 1601 } 1602 1603 private fun yesNo(answer: String): Boolean { 1604 return when (answer) { 1605 "yes", "true", "enabled", "on" -> true 1606 "no", "false", "disabled", "off" -> false 1607 else -> throw DriverException(stderr = "Unexpected $answer; expected yes or no") 1608 } 1609 } 1610 1611 /** Makes sure that the flag combinations make sense */ 1612 private fun checkFlagConsistency() { 1613 if (apiJar != null && sources.isNotEmpty()) { 1614 throw DriverException(stderr = "Specify either $ARG_SOURCE_FILES or $ARG_INPUT_API_JAR, not both") 1615 } 1616 1617 if (compatOutput && outputKotlinStyleNulls) { 1618 throw DriverException( 1619 stderr = "$ARG_OUTPUT_KOTLIN_NULLS=yes should not be combined with " + 1620 "$ARG_COMPAT_OUTPUT=yes" 1621 ) 1622 } 1623 1624 if (compatOutput && outputDefaultValues) { 1625 throw DriverException( 1626 stderr = "$ARG_OUTPUT_DEFAULT_VALUES=yes should not be combined with " + 1627 "$ARG_COMPAT_OUTPUT=yes" 1628 ) 1629 } 1630 1631 if (compatOutput && includeSignatureFormatVersion) { 1632 throw DriverException( 1633 stderr = "$ARG_INCLUDE_SIG_VERSION=yes should not be combined with " + 1634 "$ARG_COMPAT_OUTPUT=yes" 1635 ) 1636 } 1637 } 1638 1639 private fun javadoc(arg: String) { 1640 if (!alreadyWarned.add(arg)) { 1641 return 1642 } 1643 if (!options.quiet) { 1644 reporter.report( 1645 Severity.WARNING, null as String?, "Ignoring javadoc-related doclava1 flag $arg", 1646 color = color 1647 ) 1648 } 1649 } 1650 1651 private fun unimplemented(arg: String) { 1652 if (!alreadyWarned.add(arg)) { 1653 return 1654 } 1655 if (!options.quiet) { 1656 val message = "Ignoring unimplemented doclava1 flag $arg" + 1657 when (arg) { 1658 "-encoding" -> " (UTF-8 assumed)" 1659 "-source" -> " (1.8 assumed)" 1660 else -> "" 1661 } 1662 reporter.report(Severity.WARNING, null as String?, message, color = color) 1663 } 1664 } 1665 1666 private fun helpAndQuit(colorize: Boolean = color) { 1667 throw DriverException(stdout = getUsage(colorize = colorize)) 1668 } 1669 1670 private fun getValue(args: Array<String>, index: Int): String { 1671 if (index >= args.size) { 1672 throw DriverException("Missing argument for ${args[index - 1]}") 1673 } 1674 return args[index] 1675 } 1676 1677 private fun stringToExistingDir(value: String): File { 1678 val file = fileForPath(value) 1679 if (!file.isDirectory) { 1680 throw DriverException("$file is not a directory") 1681 } 1682 return file 1683 } 1684 1685 @Suppress("unused") 1686 private fun stringToExistingDirs(value: String): List<File> { 1687 val files = mutableListOf<File>() 1688 for (path in value.split(File.pathSeparatorChar)) { 1689 val file = fileForPath(path) 1690 if (!file.isDirectory) { 1691 throw DriverException("$file is not a directory") 1692 } 1693 files.add(file) 1694 } 1695 return files 1696 } 1697 1698 private fun stringToExistingDirsOrJars(value: String): List<File> { 1699 val files = mutableListOf<File>() 1700 for (path in value.split(File.pathSeparatorChar)) { 1701 val file = fileForPath(path) 1702 if (!file.isDirectory && !(file.path.endsWith(SdkConstants.DOT_JAR) && file.isFile)) { 1703 throw DriverException("$file is not a jar or directory") 1704 } 1705 files.add(file) 1706 } 1707 return files 1708 } 1709 1710 private fun stringToExistingDirsOrFiles(value: String): List<File> { 1711 val files = mutableListOf<File>() 1712 for (path in value.split(File.pathSeparatorChar)) { 1713 val file = fileForPath(path) 1714 if (!file.exists()) { 1715 throw DriverException("$file does not exist") 1716 } 1717 files.add(file) 1718 } 1719 return files 1720 } 1721 1722 private fun stringToExistingFile(value: String): File { 1723 val file = fileForPath(value) 1724 if (!file.isFile) { 1725 throw DriverException("$file is not a file") 1726 } 1727 return file 1728 } 1729 1730 @Suppress("unused") 1731 private fun stringToExistingFileOrDir(value: String): File { 1732 val file = fileForPath(value) 1733 if (!file.exists()) { 1734 throw DriverException("$file is not a file or directory") 1735 } 1736 return file 1737 } 1738 1739 private fun stringToExistingFiles(value: String): List<File> { 1740 val files = mutableListOf<File>() 1741 value.split(File.pathSeparatorChar) 1742 .map { fileForPath(it) } 1743 .forEach { file -> 1744 if (file.path.startsWith("@")) { 1745 // File list; files to be read are stored inside. SHOULD have been one per line 1746 // but sadly often uses spaces for separation too (so we split by whitespace, 1747 // which means you can't point to files in paths with spaces) 1748 val listFile = File(file.path.substring(1)) 1749 if (!listFile.isFile) { 1750 throw DriverException("$listFile is not a file") 1751 } 1752 val contents = Files.asCharSource(listFile, UTF_8).read() 1753 val pathList = Splitter.on(CharMatcher.whitespace()).trimResults().omitEmptyStrings().split( 1754 contents 1755 ) 1756 pathList.asSequence().map { File(it) }.forEach { 1757 if (!it.isFile) { 1758 throw DriverException("$it is not a file") 1759 } 1760 files.add(it) 1761 } 1762 } else { 1763 if (!file.isFile) { 1764 throw DriverException("$file is not a file") 1765 } 1766 files.add(file) 1767 } 1768 } 1769 return files 1770 } 1771 1772 private fun stringToNewFile(value: String): File { 1773 val output = fileForPath(value) 1774 1775 if (output.exists()) { 1776 if (output.isDirectory) { 1777 throw DriverException("$output is a directory") 1778 } 1779 val deleted = output.delete() 1780 if (!deleted) { 1781 throw DriverException("Could not delete previous version of $output") 1782 } 1783 } else if (output.parentFile != null && !output.parentFile.exists()) { 1784 val ok = output.parentFile.mkdirs() 1785 if (!ok) { 1786 throw DriverException("Could not create ${output.parentFile}") 1787 } 1788 } 1789 1790 return output 1791 } 1792 1793 private fun stringToNewOrExistingDir(value: String): File { 1794 val dir = fileForPath(value) 1795 if (!dir.isDirectory) { 1796 val ok = dir.mkdirs() 1797 if (!ok) { 1798 throw DriverException("Could not create $dir") 1799 } 1800 } 1801 return dir 1802 } 1803 1804 private fun stringToNewOrExistingFile(value: String): File { 1805 val file = fileForPath(value) 1806 if (!file.exists()) { 1807 val parentFile = file.parentFile 1808 if (parentFile != null && !parentFile.isDirectory) { 1809 val ok = parentFile.mkdirs() 1810 if (!ok) { 1811 throw DriverException("Could not create $parentFile") 1812 } 1813 } 1814 } 1815 return file 1816 } 1817 1818 private fun stringToNewDir(value: String): File { 1819 val output = fileForPath(value) 1820 val ok = 1821 if (output.exists()) { 1822 if (output.isDirectory) { 1823 output.deleteRecursively() 1824 } 1825 if (output.exists()) { 1826 true 1827 } else { 1828 output.mkdir() 1829 } 1830 } else { 1831 output.mkdirs() 1832 } 1833 if (!ok) { 1834 throw DriverException("Could not create $output") 1835 } 1836 1837 return output 1838 } 1839 1840 private fun fileForPath(path: String): File { 1841 // java.io.File doesn't automatically handle ~/ -> home directory expansion. 1842 // This isn't necessary when metalava is run via the command line driver 1843 // (since shells will perform this expansion) but when metalava is run 1844 // directly, not from a shell. 1845 if (path.startsWith("~/")) { 1846 val home = System.getProperty("user.home") ?: return File(path) 1847 return File(home + path.substring(1)) 1848 } else if (path.startsWith("@")) { 1849 return File("@" + File(path.substring(1)).absolutePath) 1850 } 1851 1852 return File(path).absoluteFile 1853 } 1854 1855 private fun getUsage(includeHeader: Boolean = true, colorize: Boolean = color): String { 1856 val usage = StringWriter() 1857 val printWriter = PrintWriter(usage) 1858 usage(printWriter, includeHeader, colorize) 1859 return usage.toString() 1860 } 1861 1862 private fun usage(out: PrintWriter, includeHeader: Boolean = true, colorize: Boolean = color) { 1863 if (includeHeader) { 1864 out.println(wrap(HELP_PROLOGUE, MAX_LINE_WIDTH, "")) 1865 } 1866 1867 if (colorize) { 1868 out.println("Usage: ${colorized(PROGRAM_NAME, TerminalColor.BLUE)} <flags>") 1869 } else { 1870 out.println("Usage: $PROGRAM_NAME <flags>") 1871 } 1872 1873 val args = arrayOf( 1874 "", "\nGeneral:", 1875 ARG_HELP, "This message.", 1876 ARG_VERSION, "Show the version of $PROGRAM_NAME.", 1877 ARG_QUIET, "Only include vital output", 1878 ARG_VERBOSE, "Include extra diagnostic output", 1879 ARG_COLOR, "Attempt to colorize the output (defaults to true if \$TERM is xterm)", 1880 ARG_NO_COLOR, "Do not attempt to colorize the output", 1881 ARG_NO_DOCS, "Cancel any other documentation flags supplied to $PROGRAM_NAME. This is here " + 1882 "to make it easier customize build system tasks.", 1883 ARG_UPDATE_API, "Cancel any other \"action\" flags other than generating signature files. This is here " + 1884 "to make it easier customize build system tasks, particularly for the \"make update-api\" task.", 1885 ARG_CHECK_API, "Cancel any other \"action\" flags other than checking signature files. This is here " + 1886 "to make it easier customize build system tasks, particularly for the \"make checkapi\" task.", 1887 1888 "", "\nAPI sources:", 1889 "$ARG_SOURCE_FILES <files>", "A comma separated list of source files to be parsed. Can also be " + 1890 "@ followed by a path to a text file containing paths to the full set of files to parse.", 1891 1892 "$ARG_SOURCE_PATH <paths>", "One or more directories (separated by `${File.pathSeparator}`) " + 1893 "containing source files (within a package hierarchy)", 1894 1895 "$ARG_CLASS_PATH <paths>", "One or more directories or jars (separated by " + 1896 "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " + 1897 "source files", 1898 1899 "$ARG_MERGE_QUALIFIER_ANNOTATIONS <file>", "An external annotations file to merge and overlay " + 1900 "the sources, or a directory of such files. Should be used for annotations intended for " + 1901 "inclusion in the API to be written out, e.g. nullability. Formats supported are: IntelliJ's " + 1902 "external annotations database format, .jar or .zip files containing those, Android signature " + 1903 "files, and Java stub files.", 1904 1905 "$ARG_MERGE_INCLUSION_ANNOTATIONS <file>", "An external annotations file to merge and overlay " + 1906 "the sources, or a directory of such files. Should be used for annotations which determine " + 1907 "inclusion in the API to be written out, i.e. show and hide. The only format supported is " + 1908 "Java stub files.", 1909 1910 ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS, "Triggers validation of nullability annotations " + 1911 "for any class where $ARG_MERGE_QUALIFIER_ANNOTATIONS includes a Java stub file.", 1912 1913 ARG_VALIDATE_NULLABILITY_FROM_LIST, "Triggers validation of nullability annotations " + 1914 "for any class listed in the named file (one top-level class per line, # prefix for comment line).", 1915 1916 "$ARG_NULLABILITY_WARNINGS_TXT <file>", "Specifies where to write warnings encountered during " + 1917 "validation of nullability annotations. (Does not trigger validation by itself.)", 1918 1919 ARG_NULLABILITY_ERRORS_NON_FATAL, "Specifies that errors encountered during validation of " + 1920 "nullability annotations should not be treated as errors. They will be written out to the " + 1921 "file specified in $ARG_NULLABILITY_WARNINGS_TXT instead.", 1922 1923 "$ARG_INPUT_API_JAR <file>", "A .jar file to read APIs from directly", 1924 1925 "$ARG_MANIFEST <file>", "A manifest file, used to for check permissions to cross check APIs", 1926 1927 "$ARG_HIDE_PACKAGE <package>", "Remove the given packages from the API even if they have not been " + 1928 "marked with @hide", 1929 1930 "$ARG_SHOW_ANNOTATION <annotation class>", "Unhide any hidden elements that are also annotated " + 1931 "with the given annotation", 1932 "$ARG_SHOW_SINGLE_ANNOTATION <annotation>", "Like $ARG_SHOW_ANNOTATION, but does not apply " + 1933 "to members; these must also be explicitly annotated", 1934 "$ARG_HIDE_ANNOTATION <annotation class>", "Treat any elements annotated with the given annotation " + 1935 "as hidden", 1936 ARG_SHOW_UNANNOTATED, "Include un-annotated public APIs in the signature file as well", 1937 "$ARG_JAVA_SOURCE <level>", "Sets the source level for Java source files; default is 1.8.", 1938 "$ARG_STUB_PACKAGES <package-list>", "List of packages (separated by ${File.pathSeparator}) which will " + 1939 "be used to filter out irrelevant code. If specified, only code in these packages will be " + 1940 "included in signature files, stubs, etc. (This is not limited to just the stubs; the name " + 1941 "is historical.) You can also use \".*\" at the end to match subpackages, so `foo.*` will " + 1942 "match both `foo` and `foo.bar`.", 1943 "$ARG_SUBTRACT_API <api file>", "Subtracts the API in the given signature or jar file from the " + 1944 "current API being emitted via $ARG_API, $ARG_STUBS, $ARG_DOC_STUBS, etc. " + 1945 "Note that the subtraction only applies to classes; it does not subtract members.", 1946 "$ARG_TYPEDEFS_IN_SIGNATURES <ref|inline>", "Whether to include typedef annotations in signature " + 1947 "files. `$ARG_TYPEDEFS_IN_SIGNATURES ref` will include just a reference to the typedef class, " + 1948 "which is not itself part of the API and is not included as a class, and " + 1949 "`$ARG_TYPEDEFS_IN_SIGNATURES inline` will include the constants themselves into each usage " + 1950 "site. You can also supply `$ARG_TYPEDEFS_IN_SIGNATURES none` to explicitly turn it off, if the " + 1951 "default ever changes.", 1952 1953 "", "\nDocumentation:", 1954 ARG_PUBLIC, "Only include elements that are public", 1955 ARG_PROTECTED, "Only include elements that are public or protected", 1956 ARG_PACKAGE, "Only include elements that are public, protected or package protected", 1957 ARG_PRIVATE, "Include all elements except those that are marked hidden", 1958 ARG_HIDDEN, "Include all elements, including hidden", 1959 1960 "", "\nExtracting Signature Files:", 1961 // TODO: Document --show-annotation! 1962 "$ARG_API <file>", "Generate a signature descriptor file", 1963 "$ARG_PRIVATE_API <file>", "Generate a signature descriptor file listing the exact private APIs", 1964 "$ARG_DEX_API <file>", "Generate a DEX signature descriptor file listing the APIs", 1965 "$ARG_PRIVATE_DEX_API <file>", "Generate a DEX signature descriptor file listing the exact private APIs", 1966 "$ARG_DEX_API_MAPPING <file>", "Generate a DEX signature descriptor along with file and line numbers", 1967 "$ARG_REMOVED_API <file>", "Generate a signature descriptor file for APIs that have been removed", 1968 "$ARG_FORMAT=<v1,v2,v3,...>", "Sets the output signature file format to be the given version.", 1969 "$ARG_OUTPUT_KOTLIN_NULLS[=yes|no]", "Controls whether nullness annotations should be formatted as " + 1970 "in Kotlin (with \"?\" for nullable types, \"\" for non nullable types, and \"!\" for unknown. " + 1971 "The default is yes.", 1972 "$ARG_OUTPUT_DEFAULT_VALUES[=yes|no]", "Controls whether default values should be included in " + 1973 "signature files. The default is yes.", 1974 "$ARG_COMPAT_OUTPUT=[yes|no]", "Controls whether to keep signature files compatible with the " + 1975 "historical format (with its various quirks) or to generate the new format (which will also include " + 1976 "annotations that are part of the API, etc.)", 1977 "$ARG_OMIT_COMMON_PACKAGES[=yes|no]", "Skip common package prefixes like java.lang.* and " + 1978 "kotlin.* in signature files, along with packages for well known annotations like @Nullable and " + 1979 "@NonNull.", 1980 "$ARG_INCLUDE_SIG_VERSION[=yes|no]", "Whether the signature files should include a comment listing " + 1981 "the format version of the signature file.", 1982 1983 "$ARG_PROGUARD <file>", "Write a ProGuard keep file for the API", 1984 "$ARG_SDK_VALUES <dir>", "Write SDK values files to the given directory", 1985 1986 "", "\nGenerating Stubs:", 1987 "$ARG_STUBS <dir>", "Generate stub source files for the API", 1988 "$ARG_DOC_STUBS <dir>", "Generate documentation stub source files for the API. Documentation stub " + 1989 "files are similar to regular stub files, but there are some differences. For example, in " + 1990 "the stub files, we'll use special annotations like @RecentlyNonNull instead of @NonNull to " + 1991 "indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " + 1992 "just list this as @NonNull. Another difference is that @doconly elements are included in " + 1993 "documentation stubs, but not regular stubs, etc.", 1994 ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files", 1995 ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, "Exclude element documentation (javadoc and kdoc) " + 1996 "from the generated stubs. (Copyright notices are not affected by this, they are always included. " + 1997 "Documentation stubs (--doc-stubs) are not affected.)", 1998 "$ARG_STUBS_SOURCE_LIST <file>", "Write the list of generated stub files into the given source " + 1999 "list file. If generating documentation stubs and you haven't also specified " + 2000 "$ARG_DOC_STUBS_SOURCE_LIST, this list will refer to the documentation stubs; " + 2001 "otherwise it's the non-documentation stubs.", 2002 "$ARG_DOC_STUBS_SOURCE_LIST <file>", "Write the list of generated doc stub files into the given source " + 2003 "list file", 2004 "$ARG_REGISTER_ARTIFACT <api-file> <id>", "Registers the given id for the packages found in " + 2005 "the given signature file. $PROGRAM_NAME will inject an @artifactId <id> tag into every top " + 2006 "level stub class in that API.", 2007 2008 "", "\nDiffs and Checks:", 2009 "$ARG_INPUT_KOTLIN_NULLS[=yes|no]", "Whether the signature file being read should be " + 2010 "interpreted as having encoded its types using Kotlin style types: a suffix of \"?\" for nullable " + 2011 "types, no suffix for non nullable types, and \"!\" for unknown. The default is no.", 2012 "$ARG_CHECK_COMPATIBILITY:type:state <file>", "Check compatibility. Type is one of 'api' " + 2013 "and 'removed', which checks either the public api or the removed api. State is one of " + 2014 "'current' and 'released', to check either the currently in development API or the last publicly " + 2015 "released API, respectively. Different compatibility checks apply in the two scenarios. " + 2016 "For example, to check the code base against the current public API, use " + 2017 "$ARG_CHECK_COMPATIBILITY:api:current.", 2018 "$ARG_API_LINT [api file]", "Check API for Android API best practices. If a signature file is " + 2019 "provided, only the APIs that are new since the API will be checked.", 2020 ARG_CHECK_KOTLIN_INTEROP, "Check API intended to be used from both Kotlin and Java for interoperability " + 2021 "issues", 2022 "$ARG_MIGRATE_NULLNESS <api file>", "Compare nullness information with the previous stable API " + 2023 "and mark newly annotated APIs as under migration.", 2024 ARG_WARNINGS_AS_ERRORS, "Promote all warnings to errors", 2025 ARG_LINTS_AS_ERRORS, "Promote all API lint warnings to errors", 2026 "$ARG_ERROR <id>", "Report issues of the given id as errors", 2027 "$ARG_WARNING <id>", "Report issues of the given id as warnings", 2028 "$ARG_LINT <id>", "Report issues of the given id as having lint-severity", 2029 "$ARG_HIDE <id>", "Hide/skip issues of the given id", 2030 "$ARG_BASELINE <file>", "Filter out any errors already reported in the given baseline file, or " + 2031 "create if it does not already exist", 2032 "$ARG_UPDATE_BASELINE [file]", "Rewrite the existing baseline file with the current set of warnings. " + 2033 "If some warnings have been fixed, this will delete them from the baseline files. If a file " + 2034 "is provided, the updated baseline is written to the given file; otherwise the original source " + 2035 "baseline file is updated.", 2036 "$ARG_MERGE_BASELINE [file]", "Like $ARG_UPDATE_BASELINE, but instead of always replacing entries " + 2037 "in the baseline, it will merge the existing baseline with the new baseline. This is useful " + 2038 "if $PROGRAM_NAME runs multiple times on the same source tree with different flags at different " + 2039 "times, such as occasionally with $ARG_API_LINT.", 2040 ARG_PASS_BASELINE_UPDATES, "Normally, encountering error will fail the build, even when updating " + 2041 "baselines. This flag allows you to tell $PROGRAM_NAME to continue without errors, such that " + 2042 "all the baselines in the source tree can be updated in one go.", 2043 ARG_DELETE_EMPTY_BASELINES, "Whether to delete baseline files if they are updated and there is nothing " + 2044 "to include.", 2045 2046 "", "\nJDiff:", 2047 "$ARG_XML_API <file>", "Like $ARG_API, but emits the API in the JDiff XML format instead", 2048 "$ARG_CONVERT_TO_JDIFF <sig> <xml>", "Reads in the given signature file, and writes it out " + 2049 "in the JDiff XML format. Can be specified multiple times.", 2050 "$ARG_CONVERT_NEW_TO_JDIFF <old> <new> <xml>", "Reads in the given old and new api files, " + 2051 "computes the difference, and writes out only the new parts of the API in the JDiff XML format.", 2052 "$ARG_CONVERT_TO_V1 <sig> <sig>", "Reads in the given signature file and writes it out as a " + 2053 "signature file in the original v1/doclava format.", 2054 "$ARG_CONVERT_TO_V2 <sig> <sig>", "Reads in the given signature file and writes it out as a " + 2055 "signature file in the new signature format, v2.", 2056 "$ARG_CONVERT_NEW_TO_V2 <old> <new> <sig>", "Reads in the given old and new api files, " + 2057 "computes the difference, and writes out only the new parts of the API in the v2 format.", 2058 2059 "", "\nStatistics:", 2060 ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " + 2061 "annotations, listing the percentage of the API that has been annotated with nullness information.", 2062 2063 "$ARG_ANNOTATION_COVERAGE_OF <paths>", "One or more jars (separated by `${File.pathSeparator}`) " + 2064 "containing existing apps that we want to measure annotation coverage statistics for. The set of " + 2065 "API usages in those apps are counted up and the most frequently used APIs that are missing " + 2066 "annotation metadata are listed in descending order.", 2067 2068 ARG_SKIP_JAVA_IN_COVERAGE_REPORT, "In the coverage annotation report, skip java.** and kotlin.** to " + 2069 "narrow the focus down to the Android framework APIs.", 2070 2071 "$ARG_WRITE_CLASS_COVERAGE_TO <path>", "Specifies a file to write the annotation " + 2072 "coverage report for classes to.", 2073 "$ARG_WRITE_MEMBER_COVERAGE_TO <path>", "Specifies a file to write the annotation " + 2074 "coverage report for members to.", 2075 2076 "", "\nExtracting Annotations:", 2077 "$ARG_EXTRACT_ANNOTATIONS <zipfile>", "Extracts source annotations from the source files and writes " + 2078 "them into the given zip file", 2079 "$ARG_INCLUDE_ANNOTATION_CLASSES <dir>", "Copies the given stub annotation source files into the " + 2080 "generated stub sources; <dir> is typically $PROGRAM_NAME/stub-annotations/src/main/java/.", 2081 "$ARG_REWRITE_ANNOTATIONS <dir/jar>", "For a bytecode folder or output jar, rewrites the " + 2082 "androidx annotations to be package private", 2083 "$ARG_COPY_ANNOTATIONS <source> <dest>", "For a source folder full of annotation " + 2084 "sources, generates corresponding package private versions of the same annotations.", 2085 ARG_INCLUDE_SOURCE_RETENTION, "If true, include source-retention annotations in the stub files. Does " + 2086 "not apply to signature files. Source retention annotations are extracted into the external " + 2087 "annotations files instead.", 2088 "", "\nInjecting API Levels:", 2089 "$ARG_APPLY_API_LEVELS <api-versions.xml>", "Reads an XML file containing API level descriptions " + 2090 "and merges the information into the documentation", 2091 2092 "", "\nExtracting API Levels:", 2093 "$ARG_GENERATE_API_LEVELS <xmlfile>", 2094 "Reads android.jar SDK files and generates an XML file recording " + 2095 "the API level for each class, method and field", 2096 "$ARG_ANDROID_JAR_PATTERN <pattern>", "Patterns to use to locate Android JAR files. The default " + 2097 "is \$ANDROID_HOME/platforms/android-%/android.jar.", 2098 ARG_CURRENT_VERSION, "Sets the current API level of the current source code", 2099 ARG_CURRENT_CODENAME, "Sets the code name for the current source code", 2100 ARG_CURRENT_JAR, "Points to the current API jar, if any", 2101 2102 "", "\nEnvironment Variables:", 2103 ENV_VAR_METALAVA_DUMP_ARGV, "Set to true to have metalava emit all the arguments it was invoked with. " + 2104 "Helpful when debugging or reproducing under a debugger what the build system is doing.", 2105 ENV_VAR_METALAVA_PREPEND_ARGS, "One or more arguments (concatenated by space) to insert into the " + 2106 "command line, before the documentation flags.", 2107 ENV_VAR_METALAVA_APPEND_ARGS, "One or more arguments (concatenated by space) to append to the " + 2108 "end of the command line, after the generate documentation flags." 2109 ) 2110 2111 var argWidth = 0 2112 var i = 0 2113 while (i < args.size) { 2114 val arg = args[i] 2115 argWidth = Math.max(argWidth, arg.length) 2116 i += 2 2117 } 2118 argWidth += 2 2119 val sb = StringBuilder(20) 2120 for (indent in 0 until argWidth) { 2121 sb.append(' ') 2122 } 2123 val indent = sb.toString() 2124 val formatString = "%1$-" + argWidth + "s%2\$s" 2125 2126 i = 0 2127 while (i < args.size) { 2128 val arg = args[i] 2129 val description = args[i + 1] 2130 if (arg.isEmpty()) { 2131 if (colorize) { 2132 out.println(colorized(description, TerminalColor.YELLOW)) 2133 } else { 2134 out.println(description) 2135 } 2136 } else { 2137 if (colorize) { 2138 val colorArg = bold(arg) 2139 val invisibleChars = colorArg.length - arg.length 2140 // +invisibleChars: the extra chars in the above are counted but don't contribute to width 2141 // so allow more space 2142 val colorFormatString = "%1$-" + (argWidth + invisibleChars) + "s%2\$s" 2143 2144 out.print( 2145 wrap( 2146 String.format(colorFormatString, colorArg, description), 2147 MAX_LINE_WIDTH + invisibleChars, MAX_LINE_WIDTH, indent 2148 ) 2149 ) 2150 } else { 2151 out.print( 2152 wrap( 2153 String.format(formatString, arg, description), 2154 MAX_LINE_WIDTH, indent 2155 ) 2156 ) 2157 } 2158 } 2159 i += 2 2160 } 2161 } 2162 2163 companion object { 2164 /** Whether we should use [Compatibility] mode */ 2165 fun useCompatMode(args: Array<String>): Boolean { 2166 return COMPAT_MODE_BY_DEFAULT && !args.contains("$ARG_COMPAT_OUTPUT=no") && 2167 (args.none { it.startsWith("$ARG_FORMAT=") } || args.contains("--format=v1")) 2168 } 2169 } 2170 } 2171