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