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