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