Home | History | Annotate | Download | only in metalava
      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