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