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