1 /* 2 * 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.doclava1.Errors 22 import com.android.utils.SdkUtils.wrap 23 import com.google.common.base.CharMatcher 24 import com.google.common.base.Splitter 25 import com.google.common.io.Files 26 import com.intellij.pom.java.LanguageLevel 27 import java.io.File 28 import java.io.IOException 29 import java.io.OutputStreamWriter 30 import java.io.PrintWriter 31 import java.io.StringWriter 32 import kotlin.reflect.KMutableProperty1 33 import kotlin.reflect.full.memberProperties 34 35 /** Global options for the metadata extraction tool */ 36 var options = Options(emptyArray()) 37 38 private const val MAX_LINE_WIDTH = 90 39 40 private const val ARGS_COMPAT_OUTPUT = "--compatible-output" 41 private const val ARG_HELP = "--help" 42 private const val ARG_VERSION = "--version" 43 private const val ARG_QUIET = "--quiet" 44 private const val ARG_VERBOSE = "--verbose" 45 private const val ARG_CLASS_PATH = "--classpath" 46 private const val ARG_SOURCE_PATH = "--source-path" 47 private const val ARG_SOURCE_FILES = "--source-files" 48 private const val ARG_API = "--api" 49 private const val ARG_PRIVATE_API = "--private-api" 50 private const val ARG_DEX_API = "--dex-api" 51 private const val ARG_PRIVATE_DEX_API = "--private-dex-api" 52 private const val ARG_SDK_VALUES = "--sdk-values" 53 private const val ARG_REMOVED_API = "--removed-api" 54 private const val ARG_REMOVED_DEX_API = "--removed-dex-api" 55 private const val ARG_MERGE_ANNOTATIONS = "--merge-annotations" 56 private const val ARG_INPUT_API_JAR = "--input-api-jar" 57 private const val ARG_EXACT_API = "--exact-api" 58 private const val ARG_STUBS = "--stubs" 59 private const val ARG_DOC_STUBS = "--doc-stubs" 60 private const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list" 61 private const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list" 62 private const val ARG_PROGUARD = "--proguard" 63 private const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations" 64 private const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations" 65 private const val ARG_HIDE_PACKAGE = "--hide-package" 66 private const val ARG_MANIFEST = "--manifest" 67 private const val ARG_PREVIOUS_API = "--previous-api" 68 private const val ARG_CURRENT_API = "--current-api" 69 private const val ARG_MIGRATE_NULLNESS = "--migrate-nullness" 70 private const val ARG_CHECK_COMPATIBILITY = "--check-compatibility" 71 private const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls" 72 private const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls" 73 private const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values" 74 private const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats" 75 private const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of" 76 private const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to" 77 private const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to" 78 private const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors" 79 private const val ARG_LINTS_AS_ERRORS = "--lints-as-errors" 80 private const val ARG_SHOW_ANNOTATION = "--show-annotation" 81 private const val ARG_SHOW_UNANNOTATED = "--show-unannotated" 82 private const val ARG_COLOR = "--color" 83 private const val ARG_NO_COLOR = "--no-color" 84 private const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages" 85 private const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report" 86 private const val ARG_NO_BANNER = "--no-banner" 87 private const val ARG_ERROR = "--error" 88 private const val ARG_WARNING = "--warning" 89 private const val ARG_LINT = "--lint" 90 private const val ARG_HIDE = "--hide" 91 private const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes" 92 private const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes" 93 private const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes" 94 private const val ARG_APPLY_API_LEVELS = "--apply-api-levels" 95 private const val ARG_GENERATE_API_LEVELS = "--generate-api-levels" 96 private const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern" 97 private const val ARG_CURRENT_VERSION = "--current-version" 98 private const val ARG_CURRENT_CODENAME = "--current-codename" 99 private const val ARG_CURRENT_JAR = "--current-jar" 100 private const val ARG_CHECK_KOTLIN_INTEROP = "--check-kotlin-interop" 101 private const val ARG_PUBLIC = "--public" 102 private const val ARG_PROTECTED = "--protected" 103 private const val ARG_PACKAGE = "--package" 104 private const val ARG_PRIVATE = "--private" 105 private const val ARG_HIDDEN = "--hidden" 106 private const val ARG_NO_DOCS = "--no-docs" 107 private const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation" 108 private const val ARG_JAVA_SOURCE = "--java-source" 109 private const val ARG_REGISTER_ARTIFACT = "--register-artifact" 110 111 class Options( 112 args: Array<String>, 113 /** Writer to direct output to */ 114 var stdout: PrintWriter = PrintWriter(OutputStreamWriter(System.out)), 115 /** Writer to direct error messages to */ 116 var stderr: PrintWriter = PrintWriter(OutputStreamWriter(System.err)) 117 ) { 118 119 /** Internal list backing [sources] */ 120 private val mutableSources: MutableList<File> = mutableListOf() 121 /** Internal list backing [sourcePath] */ 122 private val mutableSourcePath: MutableList<File> = mutableListOf() 123 /** Internal list backing [classpath] */ 124 private val mutableClassPath: MutableList<File> = mutableListOf() 125 /** Internal list backing [showAnnotations] */ 126 private val mutableShowAnnotations: MutableList<String> = mutableListOf() 127 /** Internal list backing [hideAnnotations] */ 128 private val mutableHideAnnotations: MutableList<String> = mutableListOf() 129 /** Internal list backing [stubImportPackages] */ 130 private val mutableStubImportPackages: MutableSet<String> = mutableSetOf() 131 /** Internal list backing [mergeAnnotations] */ 132 private val mutableMergeAnnotations: MutableList<File> = mutableListOf() 133 /** Internal list backing [annotationCoverageOf] */ 134 private val mutableAnnotationCoverageOf: MutableList<File> = mutableListOf() 135 /** Internal list backing [hidePackages] */ 136 private val mutableHidePackages: MutableList<String> = mutableListOf() 137 /** Internal list backing [skipEmitPackages] */ 138 private val mutableSkipEmitPackages: MutableList<String> = mutableListOf() 139 140 /** Ignored flags we've already warned about - store here such that we don't keep reporting them */ 141 private val alreadyWarned: MutableSet<String> = mutableSetOf() 142 143 /** 144 * Set of arguments to invoke documentation generation tool (arg 0) with, unless --no-docs is also 145 * supplied 146 */ 147 var invokeDocumentationToolArguments: Array<String> = emptyArray() 148 149 /** 150 * Whether to suppress documentation generation, even if a documentation generator has 151 * been configured via ${#ARG_GENERATE_DOCUMENTATION} 152 */ 153 var noDocs = false 154 155 /** 156 * Whether signature files should emit in "compat" mode, preserving the various 157 * quirks of the previous signature file format -- this will for example use a non-standard 158 * modifier ordering, it will call enums interfaces, etc. See the [Compatibility] class 159 * for more fine grained control (which is not (currently) exposed as individual command line 160 * flags. 161 */ 162 var compatOutput = useCompatMode(args) 163 164 /** Whether nullness annotations should be displayed as ?/!/empty instead of with @NonNull/@Nullable. */ 165 var outputKotlinStyleNulls = !compatOutput 166 167 /** Whether default values should be included in signature files */ 168 var outputDefaultValues = !compatOutput 169 170 /** Whether we should omit common packages such as java.lang.* and kotlin.* from signature output */ 171 var omitCommonPackages = !compatOutput 172 173 /** 174 * Whether reading signature files should assume the input is formatted as Kotlin-style nulls 175 * (e.g. ? means nullable, ! means unknown, empty means not null) 176 */ 177 var inputKotlinStyleNulls = false 178 179 /** If true, treat all warnings as errors */ 180 var warningsAreErrors: Boolean = false 181 182 /** If true, treat all API lint warnings as errors */ 183 var lintsAreErrors: Boolean = false 184 185 /** The list of source roots */ 186 val sourcePath: List<File> = mutableSourcePath 187 188 /** The list of dependency jars */ 189 val classpath: List<File> = mutableClassPath 190 191 /** All source files to parse */ 192 var sources: List<File> = mutableSources 193 194 /** Whether to include APIs with annotations (intended for documentation purposes) */ 195 var showAnnotations = mutableShowAnnotations 196 197 /** Whether to include unannotated elements if {@link #showAnnotations} is set */ 198 var showUnannotated = false 199 200 /** Whether to validate the API for Kotlin interop */ 201 var checkKotlinInterop = false 202 203 /** Packages to include (if null, include all) */ 204 var stubPackages: PackageFilter? = null 205 206 /** Packages to import (if empty, include all) */ 207 var stubImportPackages: Set<String> = mutableStubImportPackages 208 209 /** Packages to exclude/hide */ 210 var hidePackages = mutableHidePackages 211 212 /** Packages that we should skip generating even if not hidden; typically only used by tests */ 213 var skipEmitPackages = mutableSkipEmitPackages 214 215 var showAnnotationOverridesVisibility: Boolean = false 216 217 /** Annotations to hide */ 218 var hideAnnotations = mutableHideAnnotations 219 220 /** Whether to report warnings and other diagnostics along the way */ 221 var quiet = false 222 223 /** Whether to report extra diagnostics along the way (note that verbose isn't the same as not quiet) */ 224 var verbose = false 225 226 /** If set, a directory to write stub files to. Corresponds to the --stubs/-stubs flag. */ 227 var stubsDir: File? = null 228 229 /** If set, a directory to write documentation stub files to. Corresponds to the --stubs/-stubs flag. */ 230 var docStubsDir: File? = null 231 232 /** If set, a source file to write the stub index (list of source files) to. Can be passed to 233 * other tools like javac/javadoc using the special @-syntax. */ 234 var stubsSourceList: File? = null 235 236 /** If set, a source file to write the doc stub index (list of source files) to. Can be passed to 237 * other tools like javac/javadoc using the special @-syntax. */ 238 var docStubsSourceList: File? = null 239 240 /** Proguard Keep list file to write */ 241 var proguard: File? = null 242 243 /** If set, a file to write an API file to. Corresponds to the --api/-api flag. */ 244 var apiFile: File? = null 245 246 /** If set, a file to write the private API file to. Corresponds to the --private-api/-privateApi flag. */ 247 var privateApiFile: File? = null 248 249 /** If set, a file to write the DEX signatures to. Corresponds to --dex-api. */ 250 var dexApiFile: File? = null 251 252 /** If set, a file to write the private DEX signatures to. Corresponds to --private-dex-api. */ 253 var privateDexApiFile: File? = null 254 255 /** Path to directory to write SDK values to */ 256 var sdkValueDir: File? = null 257 258 /** If set, a file to write extracted annotations to. Corresponds to the --extract-annotations flag. */ 259 var externalAnnotations: File? = null 260 261 /** A manifest file to read to for example look up available permissions */ 262 var manifest: File? = null 263 264 /** If set, a file to write a dex API file to. Corresponds to the --removed-dex-api/-removedDexApi flag. */ 265 var removedApiFile: File? = null 266 267 /** If set, a file to write an API file to. Corresponds to the --removed-api/-removedApi flag. */ 268 var removedDexApiFile: File? = null 269 270 /** Whether output should be colorized */ 271 var color = System.getenv("TERM")?.startsWith("xterm") ?: false 272 273 /** Whether to omit Java and Kotlin runtime library packages from annotation coverage stats */ 274 var omitRuntimePackageStats = false 275 276 /** Whether to generate annotations into the stubs */ 277 var generateAnnotations = true 278 279 /** 280 * A signature file for the previous version of this API (for nullness 281 * migration, possibly for compatibility checking (if [currentApi] is not defined), etc.) 282 */ 283 var previousApi: File? = null 284 285 /** 286 * A signature file for the current version of this API (for compatibility checks). 287 */ 288 var currentApi: File? = null 289 290 /** Whether we should check API compatibility based on the previous API in [previousApi] */ 291 var checkCompatibility: Boolean = false 292 293 /** Whether we should migrate nulls based on the previous API in [previousApi] */ 294 var migrateNulls: Boolean = false 295 296 /** Existing external annotation files to merge in */ 297 var mergeAnnotations: List<File> = mutableMergeAnnotations 298 299 /** Set of jars and class files for existing apps that we want to measure coverage of */ 300 var annotationCoverageOf: List<File> = mutableAnnotationCoverageOf 301 302 /** File to write the annotation class coverage report to, if any */ 303 var annotationCoverageClassReport: File? = null 304 305 /** File to write the annotation member coverage report to, if any */ 306 var annotationCoverageMemberReport: File? = null 307 308 /** An optional <b>jar</b> file to load classes from instead of from source. 309 * This is similar to the [classpath] attribute except we're explicitly saying 310 * that this is the complete set of classes and that we <b>should</b> generate 311 * signatures/stubs from them or use them to diff APIs with (whereas [classpath] 312 * is only used to resolve types.) */ 313 var apiJar: File? = null 314 315 /** Whether to emit coverage statistics for annotations in the API surface */ 316 var dumpAnnotationStatistics = false 317 318 /** Only used for tests: Normally we want to treat classes not found as source (e.g. supplied via 319 * classpath) as hidden, but for the unit tests (where we're not passing in 320 * a complete API surface) this makes the tests more cumbersome. 321 * This option lets the testing infrastructure treat these classes differently. 322 * To see the what this means in practice, try turning it back on for the tests 323 * and see what it does to the results :) 324 */ 325 var hideClasspathClasses = true 326 327 /** Only used for tests: Whether during code filtering we allow referencing super classes 328 * etc that are unknown (because they're not included in the codebase) */ 329 var allowReferencingUnknownClasses = true 330 331 /** Reverse of [allowReferencingUnknownClasses]: Require all classes to be known. This 332 * is used when compiling the main SDK itself (which includes definitions for everything, 333 * including java.lang.Object.) */ 334 var noUnknownClasses = false 335 336 /** 337 * mapping from API level to android.jar files, if computing API levels 338 */ 339 var apiLevelJars: Array<File>? = null 340 341 /** The api level of the codebase, or -1 if not known/specified */ 342 var currentApiLevel = -1 343 344 /** API level XML file to generate */ 345 var generateApiLevelXml: File? = null 346 347 /** Reads API XML file to apply into documentation */ 348 var applyApiLevelsXml: File? = null 349 350 /** Level to include for javadoc */ 351 var docLevel = DocLevel.PROTECTED 352 353 /** 354 * Whether to omit locations for warnings and errors. This is not a flag exposed to users 355 * or listed in help; this is intended for the unit test suite, used for example for the 356 * test which checks compatibility between signature and API files where the paths vary. 357 */ 358 var omitLocations = false 359 360 /** 361 * The language level to use for Java files, set with [ARG_JAVA_SOURCE] 362 */ 363 var javaLanguageLevel: LanguageLevel = LanguageLevel.JDK_1_8 364 365 /** Map from XML API descriptor file to corresponding artifact id name */ 366 val artifactRegistrations = ArtifactTagger() 367 368 init { 369 // Pre-check whether --color/--no-color is present and use that to decide how 370 // to emit the banner even before we emit errors 371 if (args.contains(ARG_NO_COLOR)) { 372 color = false 373 } else if (args.contains(ARG_COLOR) || args.contains("-android")) { 374 color = true 375 } 376 // empty args: only when building initial default Options (options field 377 // at the top of this file; replaced once the driver runs and passes in 378 // a real argv. Don't print a banner when initializing the default options.) 379 if (args.isNotEmpty() && !args.contains(ARG_QUIET) && !args.contains(ARG_NO_BANNER) && 380 !args.contains(ARG_VERSION) 381 ) { 382 if (color) { 383 stdout.print(colorized(BANNER.trimIndent(), TerminalColor.BLUE)) 384 } else { 385 stdout.println(BANNER.trimIndent()) 386 } 387 } 388 stdout.println() 389 stdout.flush() 390 391 var androidJarPatterns: MutableList<String>? = null 392 var currentCodeName: String? = null 393 var currentJar: File? = null 394 395 var index = 0 396 while (index < args.size) { 397 val arg = args[index] 398 399 when (arg) { 400 ARG_HELP, "-h", "-?" -> { 401 helpAndQuit(color) 402 } 403 404 ARG_QUIET -> { 405 quiet = true; verbose = false 406 } 407 408 ARG_VERBOSE -> { 409 verbose = true; quiet = false 410 } 411 412 ARG_VERSION -> { 413 throw DriverException(stdout = "$PROGRAM_NAME version: ${Version.VERSION}") 414 } 415 416 ARGS_COMPAT_OUTPUT -> compatOutput = true 417 418 // For now we don't distinguish between bootclasspath and classpath 419 ARG_CLASS_PATH, "-classpath", "-bootclasspath" -> 420 mutableClassPath.addAll(stringToExistingDirsOrJars(getValue(args, ++index))) 421 422 ARG_SOURCE_PATH, "--sources", "--sourcepath", "-sourcepath" -> { 423 val path = getValue(args, ++index) 424 if (path.endsWith(SdkConstants.DOT_JAVA)) { 425 throw DriverException( 426 "$arg should point to a source root directory, not a source file ($path)" 427 ) 428 } 429 mutableSourcePath.addAll(stringToExistingDirsOrJars(path)) 430 } 431 432 ARG_SOURCE_FILES -> { 433 val listString = getValue(args, ++index) 434 listString.split(",").forEach { path -> 435 mutableSources.addAll(stringToExistingFiles(path)) 436 } 437 } 438 439 ARG_MERGE_ANNOTATIONS, "--merge-zips" -> mutableMergeAnnotations.addAll( 440 stringToExistingDirsOrFiles( 441 getValue(args, ++index) 442 ) 443 ) 444 445 "-sdkvalues", ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index)) 446 447 ARG_API, "-api" -> apiFile = stringToNewFile(getValue(args, ++index)) 448 ARG_DEX_API, "-dexApi" -> dexApiFile = stringToNewFile(getValue(args, ++index)) 449 450 ARG_PRIVATE_API, "-privateApi" -> privateApiFile = stringToNewFile(getValue(args, ++index)) 451 ARG_PRIVATE_DEX_API, "-privateDexApi" -> privateDexApiFile = stringToNewFile(getValue(args, ++index)) 452 453 ARG_REMOVED_API, "-removedApi" -> removedApiFile = stringToNewFile(getValue(args, ++index)) 454 ARG_REMOVED_DEX_API, "-removedDexApi" -> removedDexApiFile = stringToNewFile(getValue(args, ++index)) 455 456 ARG_EXACT_API, "-exactApi" -> { 457 getValue(args, ++index) // prevent next arg from tripping up parser 458 unimplemented(arg) // Not yet implemented (because it seems to no longer be hooked up in doclava1) 459 } 460 461 ARG_MANIFEST, "-manifest" -> manifest = stringToExistingFile(getValue(args, ++index)) 462 463 ARG_SHOW_ANNOTATION, "-showAnnotation" -> mutableShowAnnotations.add(getValue(args, ++index)) 464 465 ARG_SHOW_UNANNOTATED, "-showUnannotated" -> showUnannotated = true 466 467 "--showAnnotationOverridesVisibility" -> { 468 unimplemented(arg) 469 showAnnotationOverridesVisibility = true 470 } 471 472 "--hideAnnotations", "-hideAnnotation" -> mutableHideAnnotations.add(getValue(args, ++index)) 473 474 ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index)) 475 ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index)) 476 ARG_STUBS_SOURCE_LIST -> stubsSourceList = stringToNewFile(getValue(args, ++index)) 477 ARG_DOC_STUBS_SOURCE_LIST -> docStubsSourceList = stringToNewFile(getValue(args, ++index)) 478 479 ARG_EXCLUDE_ANNOTATIONS -> generateAnnotations = false 480 481 // Note that this only affects stub generation, not signature files. 482 // For signature files, clear the compatibility mode 483 // (--annotations-in-signatures) 484 "--include-annotations" -> generateAnnotations = true // temporary for tests 485 486 // Flag used by test suite to avoid including locations in 487 // the output when diffing against golden files 488 "--omit-locations" -> omitLocations = true 489 490 ARG_PROGUARD, "-proguard" -> proguard = stringToNewFile(getValue(args, ++index)) 491 492 ARG_HIDE_PACKAGE, "-hidePackage" -> mutableHidePackages.add(getValue(args, ++index)) 493 494 "--stub-packages", "-stubpackages" -> { 495 val packages = getValue(args, ++index) 496 val filter = stubPackages ?: run { 497 val newFilter = PackageFilter() 498 stubPackages = newFilter 499 newFilter 500 } 501 filter.packagePrefixes += packages.split(File.pathSeparatorChar) 502 } 503 504 "--stub-import-packages", "-stubimportpackages" -> { 505 val packages = getValue(args, ++index) 506 for (pkg in packages.split(File.pathSeparatorChar)) { 507 mutableStubImportPackages.add(pkg) 508 mutableHidePackages.add(pkg) 509 } 510 } 511 512 "--skip-emit-packages" -> { 513 val packages = getValue(args, ++index) 514 mutableSkipEmitPackages += packages.split(File.pathSeparatorChar) 515 } 516 517 ARG_PUBLIC, "-public" -> docLevel = DocLevel.PUBLIC 518 ARG_PROTECTED, "-protected" -> docLevel = DocLevel.PROTECTED 519 ARG_PACKAGE, "-package" -> docLevel = DocLevel.PACKAGE 520 ARG_PRIVATE, "-private" -> docLevel = DocLevel.PRIVATE 521 ARG_HIDDEN, "-hidden" -> docLevel = DocLevel.HIDDEN 522 523 ARG_INPUT_API_JAR -> apiJar = stringToExistingFile(getValue(args, ++index)) 524 525 ARG_EXTRACT_ANNOTATIONS -> externalAnnotations = stringToNewFile(getValue(args, ++index)) 526 527 ARG_PREVIOUS_API -> previousApi = stringToExistingFile(getValue(args, ++index)) 528 ARG_CURRENT_API -> currentApi = stringToExistingFile(getValue(args, ++index)) 529 530 ARG_MIGRATE_NULLNESS -> migrateNulls = true 531 532 ARG_CHECK_COMPATIBILITY -> { 533 checkCompatibility = true 534 } 535 536 ARG_ANNOTATION_COVERAGE_STATS -> dumpAnnotationStatistics = true 537 ARG_ANNOTATION_COVERAGE_OF -> mutableAnnotationCoverageOf.addAll( 538 stringToExistingDirsOrJars( 539 getValue(args, ++index) 540 ) 541 ) 542 ARG_WRITE_CLASS_COVERAGE_TO -> { 543 annotationCoverageClassReport = stringToNewFile(getValue(args, ++index)) 544 } 545 ARG_WRITE_MEMBER_COVERAGE_TO -> { 546 annotationCoverageMemberReport = stringToNewFile(getValue(args, ++index)) 547 } 548 549 ARG_ERROR, "-error" -> Errors.setErrorLevel(getValue(args, ++index), Severity.ERROR) 550 ARG_WARNING, "-warning" -> Errors.setErrorLevel(getValue(args, ++index), Severity.WARNING) 551 ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT) 552 ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN) 553 554 ARG_WARNINGS_AS_ERRORS -> warningsAreErrors = true 555 ARG_LINTS_AS_ERRORS -> lintsAreErrors = true 556 "-werror" -> { 557 // Temporarily disabled; this is used in various builds but is pretty much 558 // never what we want. 559 //warningsAreErrors = true 560 } 561 "-lerror" -> { 562 // Temporarily disabled; this is used in various builds but is pretty much 563 // never what we want. 564 //lintsAreErrors = true 565 } 566 567 ARG_CHECK_KOTLIN_INTEROP -> checkKotlinInterop = true 568 569 ARG_COLOR -> color = true 570 ARG_NO_COLOR -> color = false 571 ARG_NO_BANNER -> { 572 // Already processed above but don't flag it here as invalid 573 } 574 575 ARG_OMIT_COMMON_PACKAGES, "$ARG_OMIT_COMMON_PACKAGES=yes" -> omitCommonPackages = true 576 "$ARG_OMIT_COMMON_PACKAGES=no" -> omitCommonPackages = false 577 578 ARG_SKIP_JAVA_IN_COVERAGE_REPORT -> omitRuntimePackageStats = true 579 580 ARG_UNHIDE_CLASSPATH_CLASSES -> hideClasspathClasses = false 581 ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES -> allowReferencingUnknownClasses = true 582 ARG_NO_UNKNOWN_CLASSES -> noUnknownClasses = true 583 584 // Extracting API levels 585 ARG_ANDROID_JAR_PATTERN -> { 586 val list = androidJarPatterns ?: run { 587 val list = arrayListOf<String>() 588 androidJarPatterns = list 589 list 590 } 591 list.add(getValue(args, ++index)) 592 } 593 ARG_CURRENT_VERSION -> { 594 currentApiLevel = Integer.parseInt(getValue(args, ++index)) 595 if (currentApiLevel <= 26) { 596 throw DriverException("Suspicious currentApi=$currentApiLevel, expected at least 27") 597 } 598 } 599 ARG_CURRENT_CODENAME -> { 600 currentCodeName = getValue(args, ++index) 601 } 602 ARG_CURRENT_JAR -> { 603 currentJar = stringToExistingFile(getValue(args, ++index)) 604 } 605 ARG_GENERATE_API_LEVELS -> { 606 generateApiLevelXml = stringToNewFile(getValue(args, ++index)) 607 } 608 ARG_APPLY_API_LEVELS -> { 609 applyApiLevelsXml = if (args.contains(ARG_GENERATE_API_LEVELS)) { 610 // If generating the API file at the same time, it doesn't have 611 // to already exist 612 stringToNewFile(getValue(args, ++index)) 613 } else { 614 stringToExistingFile(getValue(args, ++index)) 615 } 616 } 617 618 ARG_NO_DOCS, "-nodocs" -> noDocs = true 619 620 ARG_GENERATE_DOCUMENTATION -> { 621 // Digest all the remaining arguments. 622 // Allow "STUBS_DIR" to reference the stubs directory. 623 invokeDocumentationToolArguments = args.slice(++index until args.size).mapNotNull { 624 if (it == "STUBS_DIR" && docStubsDir != null) { 625 docStubsDir?.path 626 } else if (it == "STUBS_DIR" && stubsDir != null) { 627 stubsDir?.path 628 } else if (it == "DOC_STUBS_SOURCE_LIST" && docStubsSourceList != null) { 629 "@${docStubsSourceList?.path}" 630 } else if (it == "STUBS_SOURCE_LIST" && stubsSourceList != null) { 631 "@${stubsSourceList?.path}" 632 } else if (it == "STUBS_SOURCE_LIST" && docStubsSourceList != null) { 633 "@${docStubsSourceList?.path}" 634 } else { 635 it 636 } 637 }.toTypedArray() 638 639 index = args.size // jump to end of argument loop 640 } 641 642 ARG_REGISTER_ARTIFACT, "-artifact" -> { 643 val descriptor = stringToExistingFile(getValue(args, ++index)) 644 val artifactId = getValue(args, ++index) 645 artifactRegistrations.register(artifactId, descriptor) 646 } 647 648 // Unimplemented doclava1 flags (no arguments) 649 "-quiet", 650 "-yamlV2" -> { 651 unimplemented(arg) 652 } 653 654 "-android" -> { // partially implemented: Pick up the color hint, but there may be other implications 655 color = true 656 unimplemented(arg) 657 } 658 659 "-stubsourceonly" -> { 660 /* noop */ 661 } 662 663 // Unimplemented doclava1 flags (1 argument) 664 "-d" -> { 665 unimplemented(arg) 666 index++ 667 } 668 669 "-encoding" -> { 670 val value = getValue(args, ++index) 671 if (value.toUpperCase() != "UTF-8") { 672 throw DriverException("$value: Only UTF-8 encoding is supported") 673 } 674 } 675 676 ARG_JAVA_SOURCE, "-source" -> { 677 val value = getValue(args, ++index) 678 val level = LanguageLevel.parse(value) 679 when { 680 level == null -> throw DriverException("$value is not a valid or supported Java language level") 681 level.isLessThan(LanguageLevel.JDK_1_7) -> throw DriverException("$arg must be at least 1.7") 682 else -> javaLanguageLevel = level 683 } 684 } 685 686 // Unimplemented doclava1 flags (2 arguments) 687 "-since" -> { 688 unimplemented(arg) 689 index += 2 690 } 691 692 // doclava1 doc-related flags: only supported here to make this command a drop-in 693 // replacement 694 "-referenceonly", 695 "-devsite", 696 "-ignoreJdLinks", 697 "-nodefaultassets", 698 "-parsecomments", 699 "-offlinemode", 700 "-gcmref", 701 "-metadataDebug", 702 "-includePreview", 703 "-staticonly", 704 "-navtreeonly", 705 "-atLinksNavtree" -> { 706 javadoc(arg) 707 } 708 709 // doclava1 flags with 1 argument 710 "-doclet", 711 "-docletpath", 712 "-templatedir", 713 "-htmldir", 714 "-knowntags", 715 "-resourcesdir", 716 "-resourcesoutdir", 717 "-yaml", 718 "-apidocsdir", 719 "-toroot", 720 "-samplegroup", 721 "-samplesdir", 722 "-dac_libraryroot", 723 "-dac_dataname", 724 "-title", 725 "-proofread", 726 "-todo", 727 "-overview" -> { 728 javadoc(arg) 729 index++ 730 } 731 732 // doclava1 flags with two arguments 733 "-federate", 734 "-federationapi", 735 "-htmldir2" -> { 736 javadoc(arg) 737 index += 2 738 } 739 740 // doclava1 flags with three arguments 741 "-samplecode" -> { 742 javadoc(arg) 743 index += 3 744 } 745 746 // doclava1 flag with variable number of arguments; skip everything until next arg 747 "-hdf" -> { 748 javadoc(arg) 749 index++ 750 while (index < args.size) { 751 if (args[index].startsWith("-")) { 752 break 753 } 754 index++ 755 } 756 index-- 757 } 758 759 else -> { 760 if (arg.startsWith("-J-") || arg.startsWith("-XD")) { 761 // -J: mechanism to pass extra flags to javadoc, e.g. 762 // -J-XX:-OmitStackTraceInFastThrow 763 // -XD: mechanism to set properties, e.g. 764 // -XDignore.symbol.file 765 javadoc(arg) 766 } else if (arg.startsWith(ARG_OUTPUT_KOTLIN_NULLS)) { 767 outputKotlinStyleNulls = if (arg == ARG_OUTPUT_KOTLIN_NULLS) { 768 true 769 } else { 770 yesNo(arg.substring(ARG_OUTPUT_KOTLIN_NULLS.length + 1)) 771 } 772 } else if (arg.startsWith(ARG_INPUT_KOTLIN_NULLS)) { 773 inputKotlinStyleNulls = if (arg == ARG_INPUT_KOTLIN_NULLS) { 774 true 775 } else { 776 yesNo(arg.substring(ARG_INPUT_KOTLIN_NULLS.length + 1)) 777 } 778 } else if (arg.startsWith(ARG_OUTPUT_DEFAULT_VALUES)) { 779 outputDefaultValues = if (arg == ARG_OUTPUT_DEFAULT_VALUES) { 780 true 781 } else { 782 yesNo(arg.substring(ARG_OUTPUT_DEFAULT_VALUES.length + 1)) 783 } 784 } else if (arg.startsWith(ARG_OMIT_COMMON_PACKAGES)) { 785 omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) { 786 true 787 } else { 788 yesNo(arg.substring(ARG_OMIT_COMMON_PACKAGES.length + 1)) 789 } 790 } else if (arg.startsWith(ARGS_COMPAT_OUTPUT)) { 791 compatOutput = if (arg == ARGS_COMPAT_OUTPUT) 792 true 793 else yesNo(arg.substring(ARGS_COMPAT_OUTPUT.length + 1)) 794 } else if (arg.startsWith("-")) { 795 // Compatibility flag; map to mutable properties in the Compatibility 796 // class and assign it 797 val compatibilityArg = findCompatibilityFlag(arg) 798 if (compatibilityArg != null) { 799 val dash = arg.indexOf('=') 800 val value = if (dash == -1) { 801 true 802 } else { 803 arg.substring(dash + 1).toBoolean() 804 } 805 compatibilityArg.set(compatibility, value) 806 } else { 807 // Some other argument: display usage info and exit 808 809 val usage = getUsage(includeHeader = false, colorize = color) 810 throw DriverException(stderr = "Invalid argument $arg\n\n$usage") 811 } 812 } else { 813 // All args that don't start with "-" are taken to be filenames 814 mutableSources.addAll(stringToExistingFiles(arg)) 815 } 816 } 817 } 818 819 ++index 820 } 821 822 if (generateApiLevelXml != null) { 823 if (androidJarPatterns == null) { 824 androidJarPatterns = mutableListOf( 825 "prebuilts/tools/common/api-versions/android-%/android.jar", 826 "prebuilts/sdk/%/public/android.jar" 827 ) 828 } 829 apiLevelJars = findAndroidJars(androidJarPatterns!!, currentApiLevel, currentCodeName, currentJar) 830 } 831 832 // If the caller has not explicitly requested that unannotated classes and 833 // members should be shown in the output then only show them if no annotations were provided. 834 if (!showUnannotated && showAnnotations.isEmpty()) { 835 showUnannotated = true 836 } 837 838 if (noUnknownClasses) { 839 allowReferencingUnknownClasses = false 840 } 841 842 checkFlagConsistency() 843 } 844 845 private fun findCompatibilityFlag(arg: String): KMutableProperty1<Compatibility, Boolean>? { 846 val index = arg.indexOf('=') 847 val name = arg 848 .substring(0, if (index != -1) index else arg.length) 849 .removePrefix("--") 850 .replace('-', '_') 851 val propertyName = SdkVersionInfo.underlinesToCamelCase(name).decapitalize() 852 return Compatibility::class.memberProperties 853 .filterIsInstance<KMutableProperty1<Compatibility, Boolean>>() 854 .find { 855 it.name == propertyName 856 } 857 } 858 859 private fun findAndroidJars( 860 androidJarPatterns: List<String>, 861 currentApiLevel: Int, 862 currentCodeName: String?, 863 currentJar: File? 864 ): Array<File> { 865 866 @Suppress("NAME_SHADOWING") 867 val currentApiLevel = if (currentCodeName != null && "REL" != currentCodeName) { 868 currentApiLevel + 1 869 } else { 870 currentApiLevel 871 } 872 873 val apiLevelFiles = mutableListOf<File>() 874 apiLevelFiles.add(File("")) // api level 0: dummy 875 val minApi = 1 876 877 // Get all the android.jar. They are in platforms-# 878 var apiLevel = minApi - 1 879 while (true) { 880 apiLevel++ 881 try { 882 var jar: File? = null 883 if (apiLevel == currentApiLevel) { 884 jar = currentJar 885 } 886 if (jar == null) { 887 jar = getAndroidJarFile(apiLevel, androidJarPatterns) 888 } 889 if (jar == null || !jar.isFile) { 890 if (verbose) { 891 stdout.println("Last API level found: ${apiLevel - 1}") 892 } 893 break 894 } 895 if (verbose) { 896 stdout.println("Found API $apiLevel at ${jar.path}") 897 } 898 apiLevelFiles.add(jar) 899 } catch (e: IOException) { 900 e.printStackTrace() 901 } 902 } 903 904 return apiLevelFiles.toTypedArray() 905 } 906 907 private fun getAndroidJarFile(apiLevel: Int, patterns: List<String>): File? { 908 return patterns 909 .map { fileForPath(it.replace("%", Integer.toString(apiLevel))) } 910 .firstOrNull { it.isFile } 911 } 912 913 private fun yesNo(answer: String): Boolean { 914 return when (answer) { 915 "yes", "true", "enabled", "on" -> true 916 "no", "false", "disabled", "off" -> false 917 else -> throw DriverException(stderr = "Unexpected $answer; expected yes or no") 918 } 919 } 920 921 /** Makes sure that the flag combinations make sense */ 922 private fun checkFlagConsistency() { 923 if (checkCompatibility && currentApi == null && previousApi == null) { 924 throw DriverException(stderr = "$ARG_CHECK_COMPATIBILITY requires $ARG_CURRENT_API") 925 } 926 927 if (migrateNulls && previousApi == null) { 928 throw DriverException(stderr = "$ARG_MIGRATE_NULLNESS requires $ARG_PREVIOUS_API") 929 } 930 931 if (apiJar != null && sources.isNotEmpty()) { 932 throw DriverException(stderr = "Specify either $ARG_SOURCE_FILES or $ARG_INPUT_API_JAR, not both") 933 } 934 935 if (compatOutput && outputKotlinStyleNulls) { 936 throw DriverException( 937 stderr = "$ARG_OUTPUT_KOTLIN_NULLS should not be combined with " + 938 "$ARGS_COMPAT_OUTPUT=yes" 939 ) 940 } 941 942 if (compatOutput && outputDefaultValues) { 943 throw DriverException( 944 stderr = "$ARG_OUTPUT_DEFAULT_VALUES should not be combined with " + 945 "$ARGS_COMPAT_OUTPUT=yes" 946 ) 947 } 948 } 949 950 private fun javadoc(arg: String) { 951 if (!alreadyWarned.add(arg)) { 952 return 953 } 954 if (!options.quiet) { 955 reporter.report( 956 Severity.WARNING, null as String?, "Ignoring javadoc-related doclava1 flag $arg", 957 color = color 958 ) 959 } 960 } 961 962 private fun unimplemented(arg: String) { 963 if (!alreadyWarned.add(arg)) { 964 return 965 } 966 if (!options.quiet) { 967 val message = "Ignoring unimplemented doclava1 flag $arg" + 968 when (arg) { 969 "-encoding" -> " (UTF-8 assumed)" 970 "-source" -> " (1.8 assumed)" 971 else -> "" 972 } 973 reporter.report(Severity.WARNING, null as String?, message, color = color) 974 } 975 } 976 977 private fun helpAndQuit(colorize: Boolean = color) { 978 throw DriverException(stdout = getUsage(colorize = colorize)) 979 } 980 981 private fun getValue(args: Array<String>, index: Int): String { 982 if (index >= args.size) { 983 throw DriverException("Missing argument for ${args[index - 1]}") 984 } 985 return args[index] 986 } 987 988 private fun stringToExistingDir(value: String): File { 989 val file = fileForPath(value) 990 if (!file.isDirectory) { 991 throw DriverException("$file is not a directory") 992 } 993 return file 994 } 995 996 private fun stringToExistingDirs(value: String): List<File> { 997 val files = mutableListOf<File>() 998 for (path in value.split(File.pathSeparatorChar)) { 999 val file = fileForPath(path) 1000 if (!file.isDirectory) { 1001 throw DriverException("$file is not a directory") 1002 } 1003 files.add(file) 1004 } 1005 return files 1006 } 1007 1008 private fun stringToExistingDirsOrJars(value: String): List<File> { 1009 val files = mutableListOf<File>() 1010 for (path in value.split(File.pathSeparatorChar)) { 1011 val file = fileForPath(path) 1012 if (!file.isDirectory && !(file.path.endsWith(SdkConstants.DOT_JAR) && file.isFile)) { 1013 throw DriverException("$file is not a jar or directory") 1014 } 1015 files.add(file) 1016 } 1017 return files 1018 } 1019 1020 private fun stringToExistingDirsOrFiles(value: String): List<File> { 1021 val files = mutableListOf<File>() 1022 for (path in value.split(File.pathSeparatorChar)) { 1023 val file = fileForPath(path) 1024 if (!file.exists()) { 1025 throw DriverException("$file does not exist") 1026 } 1027 files.add(file) 1028 } 1029 return files 1030 } 1031 1032 private fun stringToExistingFile(value: String): File { 1033 val file = fileForPath(value) 1034 if (!file.isFile) { 1035 throw DriverException("$file is not a file") 1036 } 1037 return file 1038 } 1039 1040 private fun stringToExistingFileOrDir(value: String): File { 1041 val file = fileForPath(value) 1042 if (!file.exists()) { 1043 throw DriverException("$file is not a file or directory") 1044 } 1045 return file 1046 } 1047 1048 private fun stringToExistingFiles(value: String): List<File> { 1049 val files = mutableListOf<File>() 1050 value.split(File.pathSeparatorChar) 1051 .map { fileForPath(it) } 1052 .forEach { file -> 1053 if (file.path.startsWith("@")) { 1054 // File list; files to be read are stored inside. SHOULD have been one per line 1055 // but sadly often uses spaces for separation too (so we split by whitespace, 1056 // which means you can't point to files in paths with spaces) 1057 val listFile = File(file.path.substring(1)) 1058 if (!listFile.isFile) { 1059 throw DriverException("$listFile is not a file") 1060 } 1061 val contents = Files.asCharSource(listFile, Charsets.UTF_8).read() 1062 val pathList = Splitter.on(CharMatcher.whitespace()).trimResults().omitEmptyStrings().split( 1063 contents 1064 ) 1065 pathList.asSequence().map { File(it) }.forEach { 1066 if (!it.isFile) { 1067 throw DriverException("$it is not a file") 1068 } 1069 files.add(it) 1070 } 1071 } else { 1072 if (!file.isFile) { 1073 throw DriverException("$file is not a file") 1074 } 1075 files.add(file) 1076 } 1077 } 1078 return files 1079 } 1080 1081 private fun stringToNewFile(value: String): File { 1082 val output = fileForPath(value) 1083 1084 if (output.exists()) { 1085 if (output.isDirectory) { 1086 throw DriverException("$output is a directory") 1087 } 1088 val deleted = output.delete() 1089 if (!deleted) { 1090 throw DriverException("Could not delete previous version of $output") 1091 } 1092 } else if (output.parentFile != null && !output.parentFile.exists()) { 1093 val ok = output.parentFile.mkdirs() 1094 if (!ok) { 1095 throw DriverException("Could not create ${output.parentFile}") 1096 } 1097 } 1098 1099 return output 1100 } 1101 1102 private fun stringToNewDir(value: String): File { 1103 val output = fileForPath(value) 1104 1105 if (output.exists()) { 1106 if (output.isDirectory) { 1107 output.deleteRecursively() 1108 } 1109 } else if (output.parentFile != null && !output.parentFile.exists()) { 1110 val ok = output.parentFile.mkdirs() 1111 if (!ok) { 1112 throw DriverException("Could not create ${output.parentFile}") 1113 } 1114 } 1115 1116 return output 1117 } 1118 1119 private fun fileForPath(path: String): File { 1120 // java.io.File doesn't automatically handle ~/ -> home directory expansion. 1121 // This isn't necessary when metalava is run via the command line driver 1122 // (since shells will perform this expansion) but when metalava is run 1123 // directly, not from a shell. 1124 if (path.startsWith("~/")) { 1125 val home = System.getProperty("user.home") ?: return File(path) 1126 return File(home + path.substring(1)) 1127 } 1128 1129 return File(path) 1130 } 1131 1132 private fun getUsage(includeHeader: Boolean = true, colorize: Boolean = color): String { 1133 val usage = StringWriter() 1134 val printWriter = PrintWriter(usage) 1135 usage(printWriter, includeHeader, colorize) 1136 return usage.toString() 1137 } 1138 1139 private fun usage(out: PrintWriter, includeHeader: Boolean = true, colorize: Boolean = color) { 1140 if (includeHeader) { 1141 out.println(wrap(HELP_PROLOGUE, MAX_LINE_WIDTH, "")) 1142 } 1143 1144 if (colorize) { 1145 out.println("Usage: ${colorized(PROGRAM_NAME, TerminalColor.BLUE)} <flags>") 1146 } else { 1147 out.println("Usage: $PROGRAM_NAME <flags>") 1148 } 1149 1150 val args = arrayOf( 1151 "", "\nGeneral:", 1152 ARG_HELP, "This message.", 1153 ARG_VERSION, "Show the version of $PROGRAM_NAME.", 1154 ARG_QUIET, "Only include vital output", 1155 ARG_VERBOSE, "Include extra diagnostic output", 1156 ARG_COLOR, "Attempt to colorize the output (defaults to true if \$TERM is xterm)", 1157 ARG_NO_COLOR, "Do not attempt to colorize the output", 1158 1159 "", "\nAPI sources:", 1160 "$ARG_SOURCE_FILES <files>", "A comma separated list of source files to be parsed. Can also be " + 1161 "@ followed by a path to a text file containing paths to the full set of files to parse.", 1162 1163 "$ARG_SOURCE_PATH <paths>", "One or more directories (separated by `${File.pathSeparator}`) " + 1164 "containing source files (within a package hierarchy)", 1165 1166 "$ARG_CLASS_PATH <paths>", "One or more directories or jars (separated by " + 1167 "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " + 1168 "source files", 1169 1170 "$ARG_MERGE_ANNOTATIONS <file>", "An external annotations file (using IntelliJ's external " + 1171 "annotations database format) to merge and overlay the sources. A subset of .jaif files " + 1172 "is also supported.", 1173 1174 "$ARG_INPUT_API_JAR <file>", "A .jar file to read APIs from directly", 1175 1176 "$ARG_MANIFEST <file>", "A manifest file, used to for check permissions to cross check APIs", 1177 1178 "$ARG_HIDE_PACKAGE <package>", "Remove the given packages from the API even if they have not been " + 1179 "marked with @hide", 1180 1181 "$ARG_SHOW_ANNOTATION <annotation class>", "Include the given annotation in the API analysis", 1182 ARG_SHOW_UNANNOTATED, "Include un-annotated public APIs in the signature file as well", 1183 "$ARG_JAVA_SOURCE <level>", "Sets the source level for Java source files; default is 1.8.", 1184 1185 "", "\nDocumentation:", 1186 ARG_PUBLIC, "Only include elements that are public", 1187 ARG_PROTECTED, "Only include elements that are public or protected", 1188 ARG_PACKAGE, "Only include elements that are public, protected or package protected", 1189 ARG_PRIVATE, "Include all elements except those that are marked hidden", 1190 ARG_HIDDEN, "Include all elements, including hidden", 1191 1192 "", "\nExtracting Signature Files:", 1193 // TODO: Document --show-annotation! 1194 "$ARG_API <file>", "Generate a signature descriptor file", 1195 "$ARG_PRIVATE_API <file>", "Generate a signature descriptor file listing the exact private APIs", 1196 "$ARG_DEX_API <file>", "Generate a DEX signature descriptor file listing the APIs", 1197 "$ARG_PRIVATE_DEX_API <file>", "Generate a DEX signature descriptor file listing the exact private APIs", 1198 "$ARG_REMOVED_API <file>", "Generate a signature descriptor file for APIs that have been removed", 1199 "$ARG_OUTPUT_KOTLIN_NULLS[=yes|no]", "Controls whether nullness annotations should be formatted as " + 1200 "in Kotlin (with \"?\" for nullable types, \"\" for non nullable types, and \"!\" for unknown. " + 1201 "The default is yes.", 1202 "$ARG_OUTPUT_DEFAULT_VALUES[=yes|no]", "Controls whether default values should be included in " + 1203 "signature files. The default is yes.", 1204 "$ARGS_COMPAT_OUTPUT=[yes|no]", "Controls whether to keep signature files compatible with the " + 1205 "historical format (with its various quirks) or to generate the new format (which will also include " + 1206 "annotations that are part of the API, etc.)", 1207 "$ARG_OMIT_COMMON_PACKAGES[=yes|no]", "Skip common package prefixes like java.lang.* and " + 1208 "kotlin.* in signature files, along with packages for well known annotations like @Nullable and " + 1209 "@NonNull.", 1210 1211 "$ARG_PROGUARD <file>", "Write a ProGuard keep file for the API", 1212 "$ARG_SDK_VALUES <dir>", "Write SDK values files to the given directory", 1213 1214 "", "\nGenerating Stubs:", 1215 "$ARG_STUBS <dir>", "Generate stub source files for the API", 1216 "$ARG_DOC_STUBS <dir>", "Generate documentation stub source files for the API. Documentation stub " + 1217 "files are similar to regular stub files, but there are some differences. For example, in " + 1218 "the stub files, we'll use special annotations like @RecentlyNonNull instead of @NonNull to " + 1219 "indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " + 1220 "just list this as @NonNull. Another difference is that @doconly elements are included in " + 1221 "documentation stubs, but not regular stubs, etc.", 1222 ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files", 1223 "$ARG_STUBS_SOURCE_LIST <file>", "Write the list of generated stub files into the given source " + 1224 "list file. If generating documentation stubs and you haven't also specified " + 1225 "$ARG_DOC_STUBS_SOURCE_LIST, this list will refer to the documentation stubs; " + 1226 "otherwise it's the non-documentation stubs.", 1227 "$ARG_DOC_STUBS_SOURCE_LIST <file>", "Write the list of generated doc stub files into the given source " + 1228 "list file", 1229 "$ARG_REGISTER_ARTIFACT <api-file> <id>", "Registers the given id for the packages found in " + 1230 "the given signature file. $PROGRAM_NAME will inject an @artifactId <id> tag into every top " + 1231 "level stub class in that API.", 1232 1233 "", "\nDiffs and Checks:", 1234 "$ARG_PREVIOUS_API <signature file>", "A signature file for the previous version of this " + 1235 "API to apply diffs with", 1236 "$ARG_INPUT_KOTLIN_NULLS[=yes|no]", "Whether the signature file being read should be " + 1237 "interpreted as having encoded its types using Kotlin style types: a suffix of \"?\" for nullable " + 1238 "types, no suffix for non nullable types, and \"!\" for unknown. The default is no.", 1239 ARG_CHECK_COMPATIBILITY, "Check compatibility with the previous API", 1240 ARG_CHECK_KOTLIN_INTEROP, "Check API intended to be used from both Kotlin and Java for interoperability " + 1241 "issues", 1242 "$ARG_CURRENT_API <signature file>", "A signature file for the current version of this " + 1243 "API to check compatibility with. If not specified, $ARG_PREVIOUS_API will be used " + 1244 "instead.", 1245 ARG_MIGRATE_NULLNESS, "Compare nullness information with the previous API and mark newly " + 1246 "annotated APIs as under migration.", 1247 ARG_WARNINGS_AS_ERRORS, "Promote all warnings to errors", 1248 ARG_LINTS_AS_ERRORS, "Promote all API lint warnings to errors", 1249 "$ARG_ERROR <id>", "Report issues of the given id as errors", 1250 "$ARG_WARNING <id>", "Report issues of the given id as warnings", 1251 "$ARG_LINT <id>", "Report issues of the given id as having lint-severity", 1252 "$ARG_HIDE <id>", "Hide/skip issues of the given id", 1253 1254 "", "\nStatistics:", 1255 ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " + 1256 "annotations, listing the percentage of the API that has been annotated with nullness information.", 1257 1258 "$ARG_ANNOTATION_COVERAGE_OF <paths>", "One or more jars (separated by `${File.pathSeparator}`) " + 1259 "containing existing apps that we want to measure annotation coverage statistics for. The set of " + 1260 "API usages in those apps are counted up and the most frequently used APIs that are missing " + 1261 "annotation metadata are listed in descending order.", 1262 1263 ARG_SKIP_JAVA_IN_COVERAGE_REPORT, "In the coverage annotation report, skip java.** and kotlin.** to " + 1264 "narrow the focus down to the Android framework APIs.", 1265 1266 "$ARG_WRITE_CLASS_COVERAGE_TO <path>", "Specifies a file to write the annotation " + 1267 "coverage report for classes to.", 1268 "$ARG_WRITE_MEMBER_COVERAGE_TO <path>", "Specifies a file to write the annotation " + 1269 "coverage report for members to.", 1270 1271 "", "\nExtracting Annotations:", 1272 "$ARG_EXTRACT_ANNOTATIONS <zipfile>", "Extracts source annotations from the source files and writes " + 1273 "them into the given zip file", 1274 1275 "", "\nInjecting API Levels:", 1276 "$ARG_APPLY_API_LEVELS <api-versions.xml>", "Reads an XML file containing API level descriptions " + 1277 "and merges the information into the documentation", 1278 1279 "", "\nExtracting API Levels:", 1280 "$ARG_GENERATE_API_LEVELS <xmlfile>", 1281 "Reads android.jar SDK files and generates an XML file recording " + 1282 "the API level for each class, method and field", 1283 "$ARG_ANDROID_JAR_PATTERN <pattern>", "Patterns to use to locate Android JAR files. The default " + 1284 "is \$ANDROID_HOME/platforms/android-%/android.jar.", 1285 ARG_CURRENT_VERSION, "Sets the current API level of the current source code", 1286 ARG_CURRENT_CODENAME, "Sets the code name for the current source code", 1287 ARG_CURRENT_JAR, "Points to the current API jar, if any" 1288 ) 1289 1290 var argWidth = 0 1291 var i = 0 1292 while (i < args.size) { 1293 val arg = args[i] 1294 argWidth = Math.max(argWidth, arg.length) 1295 i += 2 1296 } 1297 argWidth += 2 1298 val sb = StringBuilder(20) 1299 for (indent in 0 until argWidth) { 1300 sb.append(' ') 1301 } 1302 val indent = sb.toString() 1303 val formatString = "%1$-" + argWidth + "s%2\$s" 1304 1305 i = 0 1306 while (i < args.size) { 1307 val arg = args[i] 1308 val description = args[i + 1] 1309 if (arg.isEmpty()) { 1310 if (colorize) { 1311 out.println(colorized(description, TerminalColor.YELLOW)) 1312 } else { 1313 out.println(description) 1314 } 1315 } else { 1316 if (colorize) { 1317 val colorArg = bold(arg) 1318 val invisibleChars = colorArg.length - arg.length 1319 // +invisibleChars: the extra chars in the above are counted but don't contribute to width 1320 // so allow more space 1321 val colorFormatString = "%1$-" + (argWidth + invisibleChars) + "s%2\$s" 1322 1323 out.print( 1324 wrap( 1325 String.format(colorFormatString, colorArg, description), 1326 MAX_LINE_WIDTH + invisibleChars, MAX_LINE_WIDTH, indent 1327 ) 1328 ) 1329 } else { 1330 out.print( 1331 wrap( 1332 String.format(formatString, arg, description), 1333 MAX_LINE_WIDTH, indent 1334 ) 1335 ) 1336 } 1337 } 1338 i += 2 1339 } 1340 } 1341 1342 companion object { 1343 /** Whether we should use [Compatibility] mode */ 1344 fun useCompatMode(args: Array<String>): Boolean { 1345 return COMPAT_MODE_BY_DEFAULT && !args.contains("$ARGS_COMPAT_OUTPUT=no") 1346 } 1347 } 1348 } 1349