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