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.cli.common.CommonOptions
23 import com.android.tools.metalava.cli.common.ExecutionEnvironment
24 import com.android.tools.metalava.cli.common.IssueReportingOptions
25 import com.android.tools.metalava.cli.common.MetalavaCliException
26 import com.android.tools.metalava.cli.common.SourceOptions
27 import com.android.tools.metalava.cli.common.Terminal
28 import com.android.tools.metalava.cli.common.TerminalColor
29 import com.android.tools.metalava.cli.common.Verbosity
30 import com.android.tools.metalava.cli.common.enumOption
31 import com.android.tools.metalava.cli.common.fileForPathInner
32 import com.android.tools.metalava.cli.common.stringToExistingDir
33 import com.android.tools.metalava.cli.common.stringToExistingFile
34 import com.android.tools.metalava.cli.common.stringToNewDir
35 import com.android.tools.metalava.cli.common.stringToNewFile
36 import com.android.tools.metalava.cli.compatibility.ARG_CHECK_COMPATIBILITY_API_RELEASED
37 import com.android.tools.metalava.cli.compatibility.ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED
38 import com.android.tools.metalava.cli.compatibility.CompatibilityCheckOptions
39 import com.android.tools.metalava.cli.compatibility.CompatibilityCheckOptions.CheckRequest
40 import com.android.tools.metalava.cli.lint.ApiLintOptions
41 import com.android.tools.metalava.cli.signature.SignatureFormatOptions
42 import com.android.tools.metalava.manifest.Manifest
43 import com.android.tools.metalava.manifest.emptyManifest
44 import com.android.tools.metalava.model.AnnotationManager
45 import com.android.tools.metalava.model.TypedefMode
46 import com.android.tools.metalava.model.source.DEFAULT_JAVA_LANGUAGE_LEVEL
47 import com.android.tools.metalava.model.source.DEFAULT_KOTLIN_LANGUAGE_LEVEL
48 import com.android.tools.metalava.model.text.ApiClassResolution
49 import com.android.tools.metalava.model.visitors.ApiVisitor
50 import com.android.tools.metalava.reporter.Baseline
51 import com.android.tools.metalava.reporter.Reporter
52 import com.android.tools.metalava.stub.StubWriterConfig
53 import com.android.utils.SdkUtils.wrap
54 import com.github.ajalt.clikt.core.NoSuchOption
55 import com.github.ajalt.clikt.parameters.groups.OptionGroup
56 import com.github.ajalt.clikt.parameters.options.default
57 import com.github.ajalt.clikt.parameters.options.deprecated
58 import com.github.ajalt.clikt.parameters.options.multiple
59 import com.github.ajalt.clikt.parameters.options.option
60 import com.github.ajalt.clikt.parameters.options.unique
61 import com.github.ajalt.clikt.parameters.types.choice
62 import com.github.ajalt.clikt.parameters.types.file
63 import com.github.ajalt.clikt.parameters.types.int
64 import java.io.File
65 import java.io.IOException
66 import java.io.PrintWriter
67 import java.io.StringWriter
68 import java.util.Optional
69 import kotlin.properties.ReadWriteProperty
70 import kotlin.reflect.KProperty
71 import org.jetbrains.jps.model.java.impl.JavaSdkUtil
72 
73 /**
74  * A [ReadWriteProperty] that is used as the delegate for [options].
75  *
76  * It provides read/write methods and also a [disallowAccess] method which when called will cause
77  * any attempt to read the [options] property to fail. This allows code to ensure that any code
78  * which it calls does not access the deprecated [options] property.
79  */
80 object OptionsDelegate : ReadWriteProperty<Nothing?, Options> {
81 
82     /**
83      * The value of this delegate.
84      *
85      * Is `null` if [setValue] has not been called since the last call to [disallowAccess]. In that
86      * case any attempt to read the value of this delegate will fail.
87      */
88     private var possiblyNullOptions: Options? = Options()
89 
90     /**
91      * The stack trace of the last caller to [disallowAccess] (if any) to make it easy to determine
92      * why a read of [options] failed.
93      */
94     private var disallowerStackTrace: Throwable? = null
95 
96     /** Prevent all future reads of [options] until the [setValue] method is next called. */
97     fun disallowAccess() {
98         disallowerStackTrace = UnexpectedOptionsAccess("Global options property cleared")
99         possiblyNullOptions = null
100     }
101 
102     override fun setValue(thisRef: Nothing?, property: KProperty<*>, value: Options) {
103         disallowerStackTrace = null
104         possiblyNullOptions = value
105     }
106 
107     override fun getValue(thisRef: Nothing?, property: KProperty<*>): Options {
108         return possiblyNullOptions
109             ?: throw UnexpectedOptionsAccess("options is not set", disallowerStackTrace!!)
110     }
111 }
112 
113 /** A private class to try and avoid it being caught and ignored. */
114 private class UnexpectedOptionsAccess(message: String, cause: Throwable? = null) :
115     RuntimeException(message, cause)
116 
117 /**
118  * Global options for the metadata extraction tool
119  *
120  * This is an empty options which is created to avoid having a nullable options. It is replaced with
121  * the actual options to use, either created from the command line arguments for the main process or
122  * with arguments supplied by tests.
123  */
124 @Deprecated(
125     """
126     Do not add any more usages of this and please remove any existing uses that you find. Global
127     variables tightly couple all the code that uses them making them hard to test, modularize and
128     reuse. Which is why there is an ongoing process to remove usages of global variables and
129     eventually the global variable itself.
130     """
131 )
132 var options by OptionsDelegate
133 
134 private const val INDENT_WIDTH = 45
135 
136 const val ARG_CLASS_PATH = "--classpath"
137 const val ARG_SOURCE_FILES = "--source-files"
138 const val ARG_API_CLASS_RESOLUTION = "--api-class-resolution"
139 const val ARG_DEX_API = "--dex-api"
140 const val ARG_SDK_VALUES = "--sdk-values"
141 const val ARG_MERGE_QUALIFIER_ANNOTATIONS = "--merge-qualifier-annotations"
142 const val ARG_MERGE_INCLUSION_ANNOTATIONS = "--merge-inclusion-annotations"
143 const val ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS = "--validate-nullability-from-merged-stubs"
144 const val ARG_VALIDATE_NULLABILITY_FROM_LIST = "--validate-nullability-from-list"
145 const val ARG_NULLABILITY_WARNINGS_TXT = "--nullability-warnings-txt"
146 const val ARG_NULLABILITY_ERRORS_NON_FATAL = "--nullability-errors-non-fatal"
147 const val ARG_DOC_STUBS = "--doc-stubs"
148 const val ARG_KOTLIN_STUBS = "--kotlin-stubs"
149 /** Used by Firebase, see b/116185431#comment15, not used by Android Platform or AndroidX */
150 const val ARG_PROGUARD = "--proguard"
151 const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
152 const val ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS = "--exclude-documentation-from-stubs"
153 const val ARG_ENHANCE_DOCUMENTATION = "--enhance-documentation"
154 const val ARG_SKIP_READING_COMMENTS = "--ignore-comments"
155 const val ARG_HIDE_PACKAGE = "--hide-package"
156 const val ARG_MANIFEST = "--manifest"
157 const val ARG_MIGRATE_NULLNESS = "--migrate-nullness"
158 const val ARG_HIDE_ANNOTATION = "--hide-annotation"
159 const val ARG_REVERT_ANNOTATION = "--revert-annotation"
160 const val ARG_SUPPRESS_COMPATIBILITY_META_ANNOTATION = "--suppress-compatibility-meta-annotation"
161 const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
162 const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
163 const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
164 const val ARG_REMOVE_MISSING_CLASS_REFERENCES_IN_API_LEVELS =
165     "--remove-missing-class-references-in-api-levels"
166 const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
167 const val ARG_CURRENT_VERSION = "--current-version"
168 const val ARG_FIRST_VERSION = "--first-version"
169 const val ARG_CURRENT_CODENAME = "--current-codename"
170 const val ARG_CURRENT_JAR = "--current-jar"
171 const val ARG_GENERATE_API_VERSION_HISTORY = "--generate-api-version-history"
172 const val ARG_API_VERSION_SIGNATURE_FILES = "--api-version-signature-files"
173 const val ARG_API_VERSION_NAMES = "--api-version-names"
174 const val ARG_JAVA_SOURCE = "--java-source"
175 const val ARG_KOTLIN_SOURCE = "--kotlin-source"
176 const val ARG_SDK_HOME = "--sdk-home"
177 const val ARG_JDK_HOME = "--jdk-home"
178 const val ARG_COMPILE_SDK_VERSION = "--compile-sdk-version"
179 const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention"
180 const val ARG_PASS_THROUGH_ANNOTATION = "--pass-through-annotation"
181 const val ARG_EXCLUDE_ANNOTATION = "--exclude-annotation"
182 const val ARG_STUB_PACKAGES = "--stub-packages"
183 const val ARG_STUB_IMPORT_PACKAGES = "--stub-import-packages"
184 const val ARG_DELETE_EMPTY_REMOVED_SIGNATURES = "--delete-empty-removed-signatures"
185 const val ARG_SUBTRACT_API = "--subtract-api"
186 const val ARG_TYPEDEFS_IN_SIGNATURES = "--typedefs-in-signatures"
187 const val ARG_IGNORE_CLASSES_ON_CLASSPATH = "--ignore-classes-on-classpath"
188 const val ARG_SDK_JAR_ROOT = "--sdk-extensions-root"
189 const val ARG_SDK_INFO_FILE = "--sdk-extensions-info"
190 const val ARG_USE_K2_UAST = "--Xuse-k2-uast"
191 const val ARG_SOURCE_MODEL_PROVIDER = "--source-model-provider"
192 
193 class Options(
194     private val commonOptions: CommonOptions = CommonOptions(),
195     private val sourceOptions: SourceOptions = SourceOptions(),
196     private val issueReportingOptions: IssueReportingOptions =
197         IssueReportingOptions(commonOptions = commonOptions),
198     private val generalReportingOptions: GeneralReportingOptions = GeneralReportingOptions(),
199     private val apiSelectionOptions: ApiSelectionOptions = ApiSelectionOptions(),
200     val apiLintOptions: ApiLintOptions = ApiLintOptions(),
201     private val compatibilityCheckOptions: CompatibilityCheckOptions = CompatibilityCheckOptions(),
202     signatureFileOptions: SignatureFileOptions = SignatureFileOptions(),
203     signatureFormatOptions: SignatureFormatOptions = SignatureFormatOptions(),
204     stubGenerationOptions: StubGenerationOptions = StubGenerationOptions(),
205 ) : OptionGroup() {
206     /** Execution environment; initialized in [parse]. */
207     private lateinit var executionEnvironment: ExecutionEnvironment
208 
209     /** Writer to direct output to. */
210     val stdout: PrintWriter
211         get() = executionEnvironment.stdout
212     /** Writer to direct error messages to. */
213     val stderr: PrintWriter
214         get() = executionEnvironment.stderr
215 
216     /** Internal list backing [sources] */
217     private val mutableSources: MutableList<File> = mutableListOf()
218     /** Internal list backing [classpath] */
219     private val mutableClassPath: MutableList<File> = mutableListOf()
220     /** Internal builder backing [hideAnnotations] */
221     private val hideAnnotationsBuilder = AnnotationFilterBuilder()
222     /** Internal builder backing [revertAnnotations] */
223     private val revertAnnotationsBuilder = AnnotationFilterBuilder()
224     /** Internal list backing [stubImportPackages] */
225     private val mutableStubImportPackages: MutableSet<String> = mutableSetOf()
226     /** Internal list backing [mergeQualifierAnnotations] */
227     private val mutableMergeQualifierAnnotations: MutableList<File> = mutableListOf()
228     /** Internal list backing [mergeInclusionAnnotations] */
229     private val mutableMergeInclusionAnnotations: MutableList<File> = mutableListOf()
230     /** Internal list backing [hidePackages] */
231     private val mutableHidePackages: MutableList<String> = mutableListOf()
232     /** Internal list backing [passThroughAnnotations] */
233     private val mutablePassThroughAnnotations: MutableSet<String> = mutableSetOf()
234     /** Internal list backing [excludeAnnotations] */
235     private val mutableExcludeAnnotations: MutableSet<String> = mutableSetOf()
236 
237     /** API to subtract from signature and stub generation. Corresponds to [ARG_SUBTRACT_API]. */
238     var subtractApi: File? = null
239 
240     /**
241      * Backing property for [nullabilityAnnotationsValidator]
242      *
243      * This uses [Optional] to wrap the value as [lazy] cannot handle nullable values as it uses
244      * `null` as a special value.
245      *
246      * Creates [NullabilityAnnotationsValidator] lazily as it depends on a number of different
247      * options which may be supplied in different orders.
248      */
<lambda>null249     private val optionalNullabilityAnnotationsValidator by lazy {
250         Optional.ofNullable(
251             if (validateNullabilityFromMergedStubs || validateNullabilityFromList != null) {
252                 NullabilityAnnotationsValidator(
253                     reporter,
254                     nullabilityErrorsFatal,
255                     nullabilityWarningsTxt
256                 )
257             } else null
258         )
259     }
260 
261     /** Validator for nullability annotations, if validation is enabled. */
262     val nullabilityAnnotationsValidator: NullabilityAnnotationsValidator?
263         get() = optionalNullabilityAnnotationsValidator.orElse(null)
264 
265     /** Whether nullability validation errors should be considered fatal. */
266     private var nullabilityErrorsFatal = true
267 
268     /**
269      * A file to write non-fatal nullability validation issues to. If null, all issues are treated
270      * as fatal or else logged as warnings, depending on the value of [nullabilityErrorsFatal].
271      */
272     private var nullabilityWarningsTxt: File? = null
273 
274     /**
275      * Whether to validate nullability for all the classes where we are merging annotations from
276      * external java stub files. If true, [nullabilityAnnotationsValidator] must be set.
277      */
278     var validateNullabilityFromMergedStubs = false
279 
280     /**
281      * A file containing a list of classes whose nullability annotations should be validated. If
282      * set, [nullabilityAnnotationsValidator] must also be set.
283      */
284     var validateNullabilityFromList: File? = null
285 
286     /**
287      * Whether to include element documentation (javadoc and KDoc) is in the generated stubs.
288      * (Copyright notices are not affected by this, they are always included. Documentation stubs
289      * (--doc-stubs) are not affected.)
290      */
291     var includeDocumentationInStubs = true
292 
293     /**
294      * Enhance documentation in various ways, for example auto-generating documentation based on
295      * source annotations present in the code. This is implied by --doc-stubs.
296      */
297     var enhanceDocumentation = false
298 
299     /**
300      * Whether to allow reading comments If false, any attempts by Metalava to read a PSI comment
301      * will return "" This can help callers to be sure that comment-only changes shouldn't affect
302      * Metalava output
303      */
304     var allowReadingComments = true
305 
306     /** Ths list of source roots in the common module */
307     val commonSourcePath: List<File> by sourceOptions::commonSourcePath
308 
309     /** The list of source roots */
310     val sourcePath: List<File> by sourceOptions::sourcePath
311 
312     /** The list of dependency jars */
313     val classpath: List<File> = mutableClassPath
314 
315     /** All source files to parse */
316     var sources: List<File> = mutableSources
317 
318     val apiClassResolution by
319         enumOption(
320             help =
321                 """
322                 Determines how class resolution is performed when loading API signature files. Any
323                 classes that cannot be found will be treated as empty.",
324             """
325                     .trimIndent(),
<lambda>null326             enumValueHelpGetter = { it.help },
327             default = ApiClassResolution.API_CLASSPATH,
<lambda>null328             key = { it.optionValue },
329         )
330 
331     val allShowAnnotations by apiSelectionOptions::allShowAnnotations
332 
333     /**
334      * Whether to include unannotated elements if {@link #showAnnotations} is set. Note: This only
335      * applies to signature files, not stub files.
336      */
337     var showUnannotated = false
338 
339     /** Packages to include (if null, include all) */
340     private var stubPackages: PackageFilter? = null
341 
342     /** Packages to import (if empty, include all) */
343     private var stubImportPackages: Set<String> = mutableStubImportPackages
344 
345     /** Packages to exclude/hide */
346     var hidePackages: List<String> = mutableHidePackages
347 
348     /** Packages that we should skip generating even if not hidden; typically only used by tests */
349     val skipEmitPackages
350         get() = executionEnvironment.testEnvironment?.skipEmitPackages ?: emptyList()
351 
352     /** Annotations to hide */
353     val hideAnnotations by lazy(hideAnnotationsBuilder::build)
354 
355     /** Annotations to revert */
356     val revertAnnotations by lazy(revertAnnotationsBuilder::build)
357 
<lambda>null358     val annotationManager: AnnotationManager by lazy {
359         DefaultAnnotationManager(
360             DefaultAnnotationManager.Config(
361                 passThroughAnnotations = passThroughAnnotations,
362                 allShowAnnotations = allShowAnnotations,
363                 showAnnotations = apiSelectionOptions.showAnnotations,
364                 showSingleAnnotations = apiSelectionOptions.showSingleAnnotations,
365                 showForStubPurposesAnnotations = apiSelectionOptions.showForStubPurposesAnnotations,
366                 hideAnnotations = hideAnnotations,
367                 revertAnnotations = revertAnnotations,
368                 suppressCompatibilityMetaAnnotations = suppressCompatibilityMetaAnnotations,
369                 excludeAnnotations = excludeAnnotations,
370                 typedefMode = typedefMode,
371                 apiPredicate = ApiPredicate(config = apiPredicateConfig),
372                 previouslyReleasedCodebasesProvider = {
373                     compatibilityCheckOptions.previouslyReleasedCodebases(signatureFileCache)
374                 },
375             )
376         )
377     }
378 
<lambda>null379     internal val signatureFileCache by lazy { SignatureFileCache(annotationManager) }
380 
381     /** Meta-annotations for which annotated APIs should not be checked for compatibility. */
382     private val suppressCompatibilityMetaAnnotations by
383         option(
384                 ARG_SUPPRESS_COMPATIBILITY_META_ANNOTATION,
385                 help =
386                     """
387                        Suppress compatibility checks for any elements within the scope of an
388                        annotation which is itself annotated with the given meta-annotation.
389                     """
390                         .trimIndent(),
391                 metavar = "<meta-annotation class>",
392             )
393             .multiple()
394             .unique()
395 
396     /**
397      * Whether the generated API can contain classes that are not present in the source but are
398      * present on the classpath. Defaults to true for backwards compatibility but is set to false if
399      * any API signatures are imported as they must provide a complete set of all classes required
400      * but not provided by the generated API.
401      *
402      * Once all APIs are either self-contained or imported all the required references this will be
403      * removed and no classes will be allowed from the classpath JARs.
404      */
405     private var allowClassesFromClasspath = true
406 
407     /** The configuration options for the [ApiVisitor] class. */
<lambda>null408     val apiVisitorConfig by lazy {
409         ApiVisitor.Config(
410             packageFilter = stubPackages,
411             apiPredicateConfig = apiPredicateConfig,
412         )
413     }
414 
415     /** The configuration options for the [ApiAnalyzer] class. */
<lambda>null416     val apiAnalyzerConfig by lazy {
417         ApiAnalyzer.Config(
418             manifest = manifest,
419             hidePackages = hidePackages,
420             skipEmitPackages = skipEmitPackages,
421             mergeQualifierAnnotations = mergeQualifierAnnotations,
422             mergeInclusionAnnotations = mergeInclusionAnnotations,
423             stubImportPackages = stubImportPackages,
424             allShowAnnotations = allShowAnnotations,
425             apiPredicateConfig = apiPredicateConfig,
426         )
427     }
428 
<lambda>null429     val apiPredicateConfig by lazy {
430         ApiPredicate.Config(
431             ignoreShown = showUnannotated,
432             allowClassesFromClasspath = allowClassesFromClasspath,
433             addAdditionalOverrides = signatureFileFormat.addAdditionalOverrides,
434         )
435     }
436 
437     /** This is set directly by [preprocessArgv]. */
438     private var verbosity: Verbosity = Verbosity.NORMAL
439 
440     /** Whether to report warnings and other diagnostics along the way */
441     val quiet: Boolean
442         get() = verbosity.quiet
443 
444     /**
445      * Whether to report extra diagnostics along the way (note that verbose isn't the same as not
446      * quiet)
447      */
448     val verbose: Boolean
449         get() = verbosity.verbose
450 
<lambda>null451     internal val stubWriterConfig by lazy {
452         StubWriterConfig(
453             apiVisitorConfig = apiVisitorConfig,
454             kotlinStubs = kotlinStubs,
455             includeDocumentationInStubs = includeDocumentationInStubs,
456         )
457     }
458 
459     val stubsDir by stubGenerationOptions::stubsDir
460     val forceConvertToWarningNullabilityAnnotations by
461         stubGenerationOptions::forceConvertToWarningNullabilityAnnotations
462     val generateAnnotations by stubGenerationOptions::includeAnnotations
463 
464     /**
465      * If set, a directory to write documentation stub files to. Corresponds to the --stubs/-stubs
466      * flag.
467      */
468     var docStubsDir: File? = null
469 
470     /** Whether code compiled from Kotlin should be emitted as .kt stubs instead of .java stubs */
471     var kotlinStubs = false
472 
473     /** Proguard Keep list file to write */
474     var proguard: File? = null
475 
476     val apiFile by signatureFileOptions::apiFile
477     val removedApiFile by signatureFileOptions::removedApiFile
478     val signatureFileFormat by signatureFormatOptions::fileFormat
479 
480     /** Like [apiFile], but with JDiff xml format. */
481     var apiXmlFile: File? = null
482 
483     /** If set, a file to write the DEX signatures to. Corresponds to [ARG_DEX_API]. */
484     var dexApiFile: File? = null
485 
486     /** Path to directory to write SDK values to */
487     var sdkValueDir: File? = null
488 
489     /**
490      * If set, a file to write extracted annotations to. Corresponds to the --extract-annotations
491      * flag.
492      */
493     var externalAnnotations: File? = null
494 
495     /** An optional manifest [File]. */
496     private val manifestFile by
497         option(
498                 ARG_MANIFEST,
499                 help =
500                     """
501         A manifest file, used to check permissions to cross check APIs and retrieve min_sdk_version.
502         (default: no manifest)
503                     """
504                         .trimIndent()
505             )
506             .file(mustExist = true, canBeDir = false, mustBeReadable = true)
507 
508     /**
509      * A [Manifest] object to look up available permissions and min_sdk_version.
510      *
511      * Created lazily to make sure that the [reporter] has been initialized.
512      */
<lambda>null513     val manifest by lazy { manifestFile?.let { Manifest(it, reporter) } ?: emptyManifest }
514 
515     /** The set of annotation classes that should be passed through unchanged */
516     private var passThroughAnnotations = mutablePassThroughAnnotations
517 
518     /** The set of annotation classes that should be removed from all outputs */
519     private var excludeAnnotations = mutableExcludeAnnotations
520 
521     /** A signature file to migrate nullness data from */
522     var migrateNullsFrom: File? = null
523 
524     /** The list of compatibility checks to run */
525     val compatibilityChecks: List<CheckRequest> by compatibilityCheckOptions::compatibilityChecks
526 
527     /** The API to use a base for the otherwise checked API during compat checks. */
528     val baseApiForCompatCheck by compatibilityCheckOptions::baseApiForCompatCheck
529 
530     /** Existing external annotation files to merge in */
531     private var mergeQualifierAnnotations: List<File> = mutableMergeQualifierAnnotations
532     private var mergeInclusionAnnotations: List<File> = mutableMergeInclusionAnnotations
533 
534     /** mapping from API level to android.jar files, if computing API levels */
535     var apiLevelJars: Array<File>? = null
536 
537     /** The api level of the codebase, or -1 if not known/specified */
538     var currentApiLevel = -1
539 
540     /**
541      * The first api level of the codebase; typically 1 but can be higher for example for the System
542      * API.
543      */
544     var firstApiLevel = 1
545 
546     /**
547      * The codename of the codebase: non-null string if this is a developer preview build, null if
548      * this is a release build.
549      */
550     var currentCodeName: String? = null
551 
552     /** API level XML file to generate */
553     var generateApiLevelXml: File? = null
554 
555     /** Whether references to missing classes should be removed from the api levels file. */
556     var removeMissingClassesInApiLevels: Boolean = false
557 
558     /** Reads API XML file to apply into documentation */
559     var applyApiLevelsXml: File? = null
560 
561     /** Directory of prebuilt extension SDK jars that contribute to the API */
562     var sdkJarRoot: File? = null
563 
564     /**
565      * Rules to filter out some extension SDK APIs from the API, and assign extensions to the APIs
566      * that are kept
567      */
568     var sdkInfoFile: File? = null
569 
570     /**
571      * The latest publicly released SDK extension version. When generating docs for d.android.com,
572      * the SDK extensions that have been finalized but not yet publicly released should be excluded
573      * from the docs.
574      *
575      * If null, the docs will include all SDK extensions.
576      */
577     val latestReleasedSdkExtension by
578         option(
579                 "--hide-sdk-extensions-newer-than",
580                 help =
581                     "Ignore SDK extensions version INT and above. Used to exclude finalized but not yet released SDK extensions."
582             )
583             .int()
584 
585     /** API version history JSON file to generate */
586     var generateApiVersionsJson: File? = null
587 
588     /** Ordered list of signatures for each past API version, if generating an API version JSON */
589     var apiVersionSignatureFiles: List<File>? = null
590 
591     /**
592      * The names of the API versions in [apiVersionSignatureFiles], in the same order, and the name
593      * of the current API version
594      */
595     var apiVersionNames: List<String>? = null
596 
597     /** Whether to include the signature file format version header in removed signature files */
598     val includeSignatureFormatVersionRemoved: EmitFileHeader
599         get() =
600             if (deleteEmptyRemovedSignatures) {
601                 EmitFileHeader.IF_NONEMPTY_FILE
602             } else {
603                 EmitFileHeader.ALWAYS
604             }
605 
606     var allBaselines: List<Baseline> = emptyList()
607 
608     /** [IssueConfiguration] used by all reporters. */
609     val issueConfiguration by issueReportingOptions::issueConfiguration
610 
611     /** [Reporter] for general use. */
612     lateinit var reporter: Reporter
613 
614     /**
615      * [Reporter] for "api-lint".
616      *
617      * Initialized in [parse].
618      */
619     lateinit var reporterApiLint: Reporter
620 
621     /**
622      * [Reporter] for "check-compatibility:*:released". (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASED]
623      * and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED]).
624      *
625      * Initialized in [parse].
626      */
627     lateinit var reporterCompatibilityReleased: Reporter
628 
629     internal var allReporters: List<DefaultReporter> = emptyList()
630 
631     /** If generating a removed signature file, and it is empty, delete it */
632     var deleteEmptyRemovedSignatures = false
633 
634     /** The language level to use for Java files, set with [ARG_JAVA_SOURCE] */
635     var javaLanguageLevelAsString: String = DEFAULT_JAVA_LANGUAGE_LEVEL
636 
637     /** The language level to use for Kotlin files, set with [ARG_KOTLIN_SOURCE] */
638     var kotlinLanguageLevelAsString: String = DEFAULT_KOTLIN_LANGUAGE_LEVEL
639 
640     /**
641      * The JDK to use as a platform, if set with [ARG_JDK_HOME]. This is only set when metalava is
642      * used for non-Android projects.
643      */
644     var jdkHome: File? = null
645 
646     /**
647      * The JDK to use as a platform, if set with [ARG_SDK_HOME]. If this is set along with
648      * [ARG_COMPILE_SDK_VERSION], metalava will automatically add the platform's android.jar file to
649      * the classpath if it does not already find the android.jar file in the classpath.
650      */
651     private var sdkHome: File? = null
652 
653     /**
654      * The compileSdkVersion, set by [ARG_COMPILE_SDK_VERSION]. For example, for R it would be "29".
655      * For R preview, it would be "R".
656      */
657     private var compileSdkVersion: String? = null
658 
659     /**
660      * How to handle typedef annotations in signature files; corresponds to
661      * $ARG_TYPEDEFS_IN_SIGNATURES
662      */
663     private val typedefMode by
664         enumOption(
665             ARG_TYPEDEFS_IN_SIGNATURES,
666             help = """Whether to include typedef annotations in signature files.""",
<lambda>null667             enumValueHelpGetter = { it.help },
668             default = TypedefMode.NONE,
<lambda>null669             key = { it.optionValue },
670         )
671 
672     /** Temporary folder to use instead of the JDK default, if any */
673     private var tempFolder: File? = null
674 
675     var useK2Uast: Boolean? = null
676 
677     val sourceModelProvider by
678         option(
679                 ARG_SOURCE_MODEL_PROVIDER,
680                 hidden = true,
681             )
682             .choice("psi", "turbine")
683             .default("psi")
684             .deprecated(
685                 """WARNING: The turbine model is under work and not usable for now. Eventually this option can be used to set the source model provider to either turbine or psi. The default is psi. """
686                     .trimIndent()
687             )
688 
parsenull689     fun parse(
690         executionEnvironment: ExecutionEnvironment,
691         args: Array<String>,
692     ) {
693         this.executionEnvironment = executionEnvironment
694 
695         var androidJarPatterns: MutableList<String>? = null
696         var currentJar: File? = null
697 
698         var index = 0
699         while (index < args.size) {
700             when (val arg = args[index]) {
701                 // For now, we don't distinguish between bootclasspath and classpath
702                 ARG_CLASS_PATH -> {
703                     val path = getValue(args, ++index)
704                     mutableClassPath.addAll(stringToExistingDirsOrJars(path))
705                 }
706                 ARG_SOURCE_FILES -> {
707                     val listString = getValue(args, ++index)
708                     listString.split(",").forEach { path ->
709                         mutableSources.addAll(stringToExistingFiles(path))
710                     }
711                 }
712                 ARG_SUBTRACT_API -> {
713                     if (subtractApi != null) {
714                         throw MetalavaCliException(
715                             stderr = "Only one $ARG_SUBTRACT_API can be supplied"
716                         )
717                     }
718                     subtractApi = stringToExistingFile(getValue(args, ++index))
719                 }
720 
721                 // TODO: Remove the legacy --merge-annotations flag once it's no longer used to
722                 // update P docs
723                 ARG_MERGE_QUALIFIER_ANNOTATIONS,
724                 "--merge-zips",
725                 "--merge-annotations" ->
726                     mutableMergeQualifierAnnotations.addAll(
727                         stringToExistingDirsOrFiles(getValue(args, ++index))
728                     )
729                 ARG_MERGE_INCLUSION_ANNOTATIONS ->
730                     mutableMergeInclusionAnnotations.addAll(
731                         stringToExistingDirsOrFiles(getValue(args, ++index))
732                     )
733                 ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS -> {
734                     validateNullabilityFromMergedStubs = true
735                 }
736                 ARG_VALIDATE_NULLABILITY_FROM_LIST -> {
737                     validateNullabilityFromList = stringToExistingFile(getValue(args, ++index))
738                 }
739                 ARG_NULLABILITY_WARNINGS_TXT ->
740                     nullabilityWarningsTxt = stringToNewFile(getValue(args, ++index))
741                 ARG_NULLABILITY_ERRORS_NON_FATAL -> nullabilityErrorsFatal = false
742                 ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index))
743                 ARG_DEX_API -> dexApiFile = stringToNewFile(getValue(args, ++index))
744                 ARG_SHOW_UNANNOTATED -> showUnannotated = true
745                 ARG_HIDE_ANNOTATION -> hideAnnotationsBuilder.add(getValue(args, ++index))
746                 ARG_REVERT_ANNOTATION -> revertAnnotationsBuilder.add(getValue(args, ++index))
747                 ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index))
748                 ARG_KOTLIN_STUBS -> kotlinStubs = true
749                 ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS -> includeDocumentationInStubs = false
750                 ARG_ENHANCE_DOCUMENTATION -> enhanceDocumentation = true
751                 ARG_SKIP_READING_COMMENTS -> allowReadingComments = false
752                 ARG_PASS_THROUGH_ANNOTATION -> {
753                     val annotations = getValue(args, ++index)
754                     annotations.split(",").forEach { path ->
755                         mutablePassThroughAnnotations.add(path)
756                     }
757                 }
758                 ARG_EXCLUDE_ANNOTATION -> {
759                     val annotations = getValue(args, ++index)
760                     annotations.split(",").forEach { path -> mutableExcludeAnnotations.add(path) }
761                 }
762                 ARG_PROGUARD -> proguard = stringToNewFile(getValue(args, ++index))
763                 ARG_HIDE_PACKAGE -> mutableHidePackages.add(getValue(args, ++index))
764                 ARG_STUB_PACKAGES -> {
765                     val packages = getValue(args, ++index)
766                     val filter =
767                         stubPackages
768                             ?: run {
769                                 val newFilter = PackageFilter()
770                                 stubPackages = newFilter
771                                 newFilter
772                             }
773                     filter.addPackages(packages)
774                 }
775                 ARG_STUB_IMPORT_PACKAGES -> {
776                     val packages = getValue(args, ++index)
777                     for (pkg in packages.split(File.pathSeparatorChar)) {
778                         mutableStubImportPackages.add(pkg)
779                         mutableHidePackages.add(pkg)
780                     }
781                 }
782                 ARG_IGNORE_CLASSES_ON_CLASSPATH -> {
783                     allowClassesFromClasspath = false
784                 }
785                 ARG_DELETE_EMPTY_REMOVED_SIGNATURES -> deleteEmptyRemovedSignatures = true
786                 ARG_EXTRACT_ANNOTATIONS ->
787                     externalAnnotations = stringToNewFile(getValue(args, ++index))
788                 ARG_MIGRATE_NULLNESS -> {
789                     migrateNullsFrom = stringToExistingFile(getValue(args, ++index))
790                 }
791 
792                 // Extracting API levels
793                 ARG_ANDROID_JAR_PATTERN -> {
794                     val list =
795                         androidJarPatterns
796                             ?: run {
797                                 val list = arrayListOf<String>()
798                                 androidJarPatterns = list
799                                 list
800                             }
801                     list.add(getValue(args, ++index))
802                 }
803                 ARG_CURRENT_VERSION -> {
804                     currentApiLevel = Integer.parseInt(getValue(args, ++index))
805                     if (currentApiLevel <= 26) {
806                         throw MetalavaCliException(
807                             "Suspicious currentApi=$currentApiLevel, expected at least 27"
808                         )
809                     }
810                 }
811                 ARG_FIRST_VERSION -> {
812                     firstApiLevel = Integer.parseInt(getValue(args, ++index))
813                 }
814                 ARG_CURRENT_CODENAME -> {
815                     val codeName = getValue(args, ++index)
816                     if (codeName != "REL") {
817                         currentCodeName = codeName
818                     }
819                 }
820                 ARG_CURRENT_JAR -> {
821                     currentJar = stringToExistingFile(getValue(args, ++index))
822                 }
823                 ARG_GENERATE_API_LEVELS -> {
824                     generateApiLevelXml = stringToNewFile(getValue(args, ++index))
825                 }
826                 ARG_APPLY_API_LEVELS -> {
827                     applyApiLevelsXml =
828                         if (args.contains(ARG_GENERATE_API_LEVELS)) {
829                             // If generating the API file at the same time, it doesn't have
830                             // to already exist
831                             stringToNewFile(getValue(args, ++index))
832                         } else {
833                             stringToExistingFile(getValue(args, ++index))
834                         }
835                 }
836                 ARG_REMOVE_MISSING_CLASS_REFERENCES_IN_API_LEVELS ->
837                     removeMissingClassesInApiLevels = true
838                 ARG_GENERATE_API_VERSION_HISTORY -> {
839                     generateApiVersionsJson = stringToNewFile(getValue(args, ++index))
840                 }
841                 ARG_API_VERSION_SIGNATURE_FILES -> {
842                     apiVersionSignatureFiles = stringToExistingFiles(getValue(args, ++index))
843                 }
844                 ARG_API_VERSION_NAMES -> {
845                     apiVersionNames = getValue(args, ++index).split(' ')
846                 }
847                 ARG_JAVA_SOURCE -> {
848                     val value = getValue(args, ++index)
849                     javaLanguageLevelAsString = value
850                 }
851                 ARG_KOTLIN_SOURCE -> {
852                     val value = getValue(args, ++index)
853                     kotlinLanguageLevelAsString = value
854                 }
855                 ARG_JDK_HOME -> {
856                     jdkHome = stringToExistingDir(getValue(args, ++index))
857                 }
858                 ARG_SDK_HOME -> {
859                     sdkHome = stringToExistingDir(getValue(args, ++index))
860                 }
861                 ARG_COMPILE_SDK_VERSION -> {
862                     compileSdkVersion = getValue(args, ++index)
863                 }
864                 ARG_USE_K2_UAST -> useK2Uast = true
865                 ARG_SDK_JAR_ROOT -> {
866                     sdkJarRoot = stringToExistingDir(getValue(args, ++index))
867                 }
868                 ARG_SDK_INFO_FILE -> {
869                     sdkInfoFile = stringToExistingFile(getValue(args, ++index))
870                 }
871                 "--temp-folder" -> {
872                     tempFolder = stringToNewOrExistingDir(getValue(args, ++index))
873                 }
874 
875                 // Option only meant for tests (not documented); doesn't work in all cases (to do
876                 // that we'd
877                 // need JNA to call libc)
878                 "--pwd" -> {
879                     val pwd = stringToExistingDir(getValue(args, ++index)).absoluteFile
880                     System.setProperty("user.dir", pwd.path)
881                 }
882                 else -> {
883                     if (arg.startsWith("-")) {
884                         // Some other argument: display usage info and exit
885                         throw NoSuchOption(givenName = arg)
886                     } else {
887                         // All args that don't start with "-" are taken to be filenames
888                         mutableSources.addAll(stringToExistingFiles(arg))
889                     }
890                 }
891             }
892 
893             ++index
894         }
895 
896         if (generateApiLevelXml != null) {
897             if (currentApiLevel == -1) {
898                 throw MetalavaCliException(
899                     stderr = "$ARG_GENERATE_API_LEVELS requires $ARG_CURRENT_VERSION"
900                 )
901             }
902 
903             // <String> is redundant here but while IDE (with newer type inference engine
904             // understands that) the current 1.3.x compiler does not
905             @Suppress("RemoveExplicitTypeArguments")
906             val patterns = androidJarPatterns ?: run { mutableListOf<String>() }
907             // Fallbacks
908             patterns.add("prebuilts/tools/common/api-versions/android-%/android.jar")
909             patterns.add("prebuilts/sdk/%/public/android.jar")
910             apiLevelJars =
911                 findAndroidJars(
912                     args,
913                     patterns,
914                     firstApiLevel,
915                     currentApiLevel + if (isDeveloperPreviewBuild()) 1 else 0,
916                     currentJar
917                 )
918         }
919 
920         if ((sdkJarRoot == null) != (sdkInfoFile == null)) {
921             throw MetalavaCliException(
922                 stderr = "$ARG_SDK_JAR_ROOT and $ARG_SDK_INFO_FILE must both be supplied"
923             )
924         }
925 
926         // apiVersionNames will include the current version but apiVersionSignatureFiles will not,
927         // so there should be 1 more name than signature file (or both can be null)
928         val numVersionNames = apiVersionNames?.size ?: 0
929         val numVersionFiles = apiVersionSignatureFiles?.size ?: 0
930         if (numVersionNames != 0 && numVersionNames != numVersionFiles + 1) {
931             throw MetalavaCliException(
932                 "$ARG_API_VERSION_NAMES must have one more version than $ARG_API_VERSION_SIGNATURE_FILES to include the current version name"
933             )
934         }
935 
936         // If the caller has not explicitly requested that unannotated classes and
937         // members should be shown in the output then only show them if no annotations were
938         // provided.
939         if (!showUnannotated && allShowAnnotations.isEmpty()) {
940             showUnannotated = true
941         }
942 
943         // Initialize the reporters.
944         val baseline = generalReportingOptions.baseline
945         reporter =
946             DefaultReporter(
947                 environment = executionEnvironment.reporterEnvironment,
948                 issueConfiguration = issueConfiguration,
949                 baseline = baseline,
950                 packageFilter = stubPackages,
951                 config = issueReportingOptions.reporterConfig,
952             )
953         reporterApiLint =
954             DefaultReporter(
955                 environment = executionEnvironment.reporterEnvironment,
956                 issueConfiguration = issueConfiguration,
957                 baseline = apiLintOptions.baseline ?: baseline,
958                 errorMessage = apiLintOptions.errorMessage,
959                 packageFilter = stubPackages,
960                 config = issueReportingOptions.reporterConfig,
961             )
962         reporterCompatibilityReleased =
963             DefaultReporter(
964                 environment = executionEnvironment.reporterEnvironment,
965                 issueConfiguration = issueConfiguration,
966                 baseline = compatibilityCheckOptions.baseline ?: baseline,
967                 errorMessage = compatibilityCheckOptions.errorMessage,
968                 packageFilter = stubPackages,
969                 config = issueReportingOptions.reporterConfig,
970             )
971 
972         // Build "all baselines" and "all reporters"
973 
974         // Baselines are nullable, so selectively add to the list.
975         allBaselines =
976             listOfNotNull(baseline, apiLintOptions.baseline, compatibilityCheckOptions.baseline)
977 
978         // Reporters are non-null.
979         // Downcast to DefaultReporter to gain access to some implementation specific functionality.
980         allReporters =
981             listOf(
982                     issueReportingOptions.bootstrapReporter,
983                     reporter,
984                     reporterApiLint,
985                     reporterCompatibilityReleased,
986                 )
987                 .map { it as DefaultReporter }
988 
989         updateClassPath()
990     }
991 
isDeveloperPreviewBuildnull992     fun isDeveloperPreviewBuild(): Boolean = currentCodeName != null
993 
994     /** Update the classpath to insert android.jar or JDK classpath elements if necessary */
995     private fun updateClassPath() {
996         val sdkHome = sdkHome
997         val jdkHome = jdkHome
998 
999         if (
1000             sdkHome != null &&
1001                 compileSdkVersion != null &&
1002                 classpath.none { it.name == FN_FRAMEWORK_LIBRARY }
1003         ) {
1004             val jar = File(sdkHome, "platforms/android-$compileSdkVersion")
1005             if (jar.isFile) {
1006                 mutableClassPath.add(jar)
1007             } else {
1008                 throw MetalavaCliException(
1009                     stderr =
1010                         "Could not find android.jar for API level " +
1011                             "$compileSdkVersion in SDK $sdkHome: $jar does not exist"
1012                 )
1013             }
1014             if (jdkHome != null) {
1015                 throw MetalavaCliException(
1016                     stderr = "Do not specify both $ARG_SDK_HOME and $ARG_JDK_HOME"
1017                 )
1018             }
1019         } else if (jdkHome != null) {
1020             val isJre = !isJdkFolder(jdkHome)
1021             val roots = JavaSdkUtil.getJdkClassesRoots(jdkHome.toPath(), isJre).map { it.toFile() }
1022             mutableClassPath.addAll(roots)
1023         }
1024     }
1025 
1026     /**
1027      * Find an android stub jar that matches the given criteria.
1028      *
1029      * Note because the default baseline file is not explicitly set in the command line, this file
1030      * would trigger a --strict-input-files violation. To avoid that, use
1031      * --strict-input-files-exempt to exempt the jar directory.
1032      */
findAndroidJarsnull1033     private fun findAndroidJars(
1034         args: Array<String>,
1035         androidJarPatterns: List<String>,
1036         minApi: Int,
1037         currentApiLevel: Int,
1038         currentJar: File?
1039     ): Array<File> {
1040         val apiLevelFiles = mutableListOf<File>()
1041         // api level 0: placeholder, should not be processed.
1042         // (This is here because we want the array index to match
1043         // the API level)
1044         val element = File("not an api: the starting API index is $minApi")
1045         for (i in 0 until minApi) {
1046             apiLevelFiles.add(element)
1047         }
1048 
1049         // Get all the android.jar. They are in platforms-#
1050         var apiLevel = minApi - 1
1051         while (true) {
1052             apiLevel++
1053             try {
1054                 var jar: File? = null
1055                 if (apiLevel == currentApiLevel) {
1056                     jar = currentJar
1057                 }
1058                 if (jar == null) {
1059                     jar = getAndroidJarFile(apiLevel, androidJarPatterns)
1060                 }
1061                 if (jar == null || !jar.isFile) {
1062                     if (verbose) {
1063                         stdout.println("Last API level found: ${apiLevel - 1}")
1064                     }
1065 
1066                     if (apiLevel < 28) {
1067                         // Clearly something is wrong with the patterns; this should result in a
1068                         // build error
1069                         val argList = mutableListOf<String>()
1070                         args.forEachIndexed { index, arg ->
1071                             if (arg == ARG_ANDROID_JAR_PATTERN) {
1072                                 argList.add(args[index + 1])
1073                             }
1074                         }
1075                         throw MetalavaCliException(
1076                             stderr =
1077                                 "Could not find android.jar for API level $apiLevel; the " +
1078                                     "$ARG_ANDROID_JAR_PATTERN set might be invalid: ${argList.joinToString()}"
1079                         )
1080                     }
1081 
1082                     break
1083                 }
1084                 if (verbose) {
1085                     stdout.println("Found API $apiLevel at ${jar.path}")
1086                 }
1087                 apiLevelFiles.add(jar)
1088             } catch (e: IOException) {
1089                 e.printStackTrace()
1090             }
1091         }
1092 
1093         return apiLevelFiles.toTypedArray()
1094     }
1095 
getAndroidJarFilenull1096     private fun getAndroidJarFile(apiLevel: Int, patterns: List<String>): File? {
1097         return patterns
1098             .map { fileForPathInner(it.replace("%", apiLevel.toString())) }
1099             .firstOrNull { it.isFile }
1100     }
1101 
getValuenull1102     private fun getValue(args: Array<String>, index: Int): String {
1103         if (index >= args.size) {
1104             throw MetalavaCliException("Missing argument for ${args[index - 1]}")
1105         }
1106         return args[index]
1107     }
1108 
stringToExistingDirsOrJarsnull1109     private fun stringToExistingDirsOrJars(value: String): List<File> {
1110         val files = mutableListOf<File>()
1111         for (path in value.split(File.pathSeparatorChar)) {
1112             val file = fileForPathInner(path)
1113             if (!file.isDirectory && !(file.path.endsWith(SdkConstants.DOT_JAR) && file.isFile)) {
1114                 throw MetalavaCliException("$file is not a jar or directory")
1115             }
1116             files.add(file)
1117         }
1118         return files
1119     }
1120 
stringToExistingDirsOrFilesnull1121     private fun stringToExistingDirsOrFiles(value: String): List<File> {
1122         val files = mutableListOf<File>()
1123         for (path in value.split(File.pathSeparatorChar)) {
1124             val file = fileForPathInner(path)
1125             if (!file.exists()) {
1126                 throw MetalavaCliException("$file does not exist")
1127             }
1128             files.add(file)
1129         }
1130         return files
1131     }
1132 
1133     @Suppress("unused")
stringToExistingFileOrDirnull1134     private fun stringToExistingFileOrDir(value: String): File {
1135         val file = fileForPathInner(value)
1136         if (!file.exists()) {
1137             throw MetalavaCliException("$file is not a file or directory")
1138         }
1139         return file
1140     }
1141 
stringToExistingFilesnull1142     private fun stringToExistingFiles(value: String): List<File> {
1143         return value
1144             .split(File.pathSeparatorChar)
1145             .map { fileForPathInner(it) }
1146             .map { file ->
1147                 if (!file.isFile) {
1148                     throw MetalavaCliException("$file is not a file")
1149                 }
1150                 file
1151             }
1152     }
1153 
stringToNewOrExistingDirnull1154     private fun stringToNewOrExistingDir(value: String): File {
1155         val dir = fileForPathInner(value)
1156         if (!dir.isDirectory) {
1157             val ok = dir.mkdirs()
1158             if (!ok) {
1159                 throw MetalavaCliException("Could not create $dir")
1160             }
1161         }
1162         return dir
1163     }
1164 }
1165 
1166 object OptionsHelp {
getUsagenull1167     fun getUsage(terminal: Terminal, width: Int): String {
1168         val usage = StringWriter()
1169         val printWriter = PrintWriter(usage)
1170         usage(printWriter, terminal, width)
1171         return usage.toString()
1172     }
1173 
usagenull1174     private fun usage(out: PrintWriter, terminal: Terminal, width: Int) {
1175         val args =
1176             arrayOf(
1177                 "",
1178                 "API sources:",
1179                 "$ARG_SOURCE_FILES <files>",
1180                 "A comma separated list of source files to be parsed. Can also be " +
1181                     "@ followed by a path to a text file containing paths to the full set of files to parse.",
1182                 "$ARG_CLASS_PATH <paths>",
1183                 "One or more directories or jars (separated by " +
1184                     "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " +
1185                     "source files",
1186                 "$ARG_MERGE_QUALIFIER_ANNOTATIONS <file>",
1187                 "An external annotations file to merge and overlay " +
1188                     "the sources, or a directory of such files. Should be used for annotations intended for " +
1189                     "inclusion in the API to be written out, e.g. nullability. Formats supported are: IntelliJ's " +
1190                     "external annotations database format, .jar or .zip files containing those, Android signature " +
1191                     "files, and Java stub files.",
1192                 "$ARG_MERGE_INCLUSION_ANNOTATIONS <file>",
1193                 "An external annotations file to merge and overlay " +
1194                     "the sources, or a directory of such files. Should be used for annotations which determine " +
1195                     "inclusion in the API to be written out, i.e. show and hide. The only format supported is " +
1196                     "Java stub files.",
1197                 ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS,
1198                 "Triggers validation of nullability annotations " +
1199                     "for any class where $ARG_MERGE_QUALIFIER_ANNOTATIONS includes a Java stub file.",
1200                 ARG_VALIDATE_NULLABILITY_FROM_LIST,
1201                 "Triggers validation of nullability annotations " +
1202                     "for any class listed in the named file (one top-level class per line, # prefix for comment line).",
1203                 "$ARG_NULLABILITY_WARNINGS_TXT <file>",
1204                 "Specifies where to write warnings encountered during " +
1205                     "validation of nullability annotations. (Does not trigger validation by itself.)",
1206                 ARG_NULLABILITY_ERRORS_NON_FATAL,
1207                 "Specifies that errors encountered during validation of " +
1208                     "nullability annotations should not be treated as errors. They will be written out to the " +
1209                     "file specified in $ARG_NULLABILITY_WARNINGS_TXT instead.",
1210                 "$ARG_HIDE_PACKAGE <package>",
1211                 "Remove the given packages from the API even if they have not been " +
1212                     "marked with @hide",
1213                 "$ARG_HIDE_ANNOTATION <annotation class>",
1214                 "Treat any elements annotated with the given annotation " + "as hidden",
1215                 ARG_SHOW_UNANNOTATED,
1216                 "Include un-annotated public APIs in the signature file as well",
1217                 "$ARG_JAVA_SOURCE <level>",
1218                 "Sets the source level for Java source files; default is $DEFAULT_JAVA_LANGUAGE_LEVEL.",
1219                 "$ARG_KOTLIN_SOURCE <level>",
1220                 "Sets the source level for Kotlin source files; default is $DEFAULT_KOTLIN_LANGUAGE_LEVEL.",
1221                 "$ARG_SDK_HOME <dir>",
1222                 "If set, locate the `android.jar` file from the given Android SDK",
1223                 "$ARG_COMPILE_SDK_VERSION <api>",
1224                 "Use the given API level",
1225                 "$ARG_JDK_HOME <dir>",
1226                 "If set, add the Java APIs from the given JDK to the classpath",
1227                 "$ARG_STUB_PACKAGES <package-list>",
1228                 "List of packages (separated by ${File.pathSeparator}) which will " +
1229                     "be used to filter out irrelevant code. If specified, only code in these packages will be " +
1230                     "included in signature files, stubs, etc. (This is not limited to just the stubs; the name " +
1231                     "is historical.) You can also use \".*\" at the end to match subpackages, so `foo.*` will " +
1232                     "match both `foo` and `foo.bar`.",
1233                 "$ARG_SUBTRACT_API <api file>",
1234                 "Subtracts the API in the given signature or jar file from the " +
1235                     "current API being emitted via $ARG_API, $ARG_STUBS, $ARG_DOC_STUBS, etc. " +
1236                     "Note that the subtraction only applies to classes; it does not subtract members.",
1237                 ARG_IGNORE_CLASSES_ON_CLASSPATH,
1238                 "Prevents references to classes on the classpath from being added to " +
1239                     "the generated stub files.",
1240                 ARG_SKIP_READING_COMMENTS,
1241                 "Ignore any comments in source files.",
1242                 "",
1243                 "Extracting Signature Files:",
1244                 // TODO: Document --show-annotation!
1245                 "$ARG_DEX_API <file>",
1246                 "Generate a DEX signature descriptor file listing the APIs",
1247                 "$ARG_PROGUARD <file>",
1248                 "Write a ProGuard keep file for the API",
1249                 "$ARG_SDK_VALUES <dir>",
1250                 "Write SDK values files to the given directory",
1251                 "",
1252                 "Generating Stubs:",
1253                 "$ARG_DOC_STUBS <dir>",
1254                 "Generate documentation stub source files for the API. Documentation stub " +
1255                     "files are similar to regular stub files, but there are some differences. For example, in " +
1256                     "the stub files, we'll use special annotations like @RecentlyNonNull instead of @NonNull to " +
1257                     "indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " +
1258                     "just list this as @NonNull. Another difference is that @doconly elements are included in " +
1259                     "documentation stubs, but not regular stubs, etc.",
1260                 ARG_KOTLIN_STUBS,
1261                 "[CURRENTLY EXPERIMENTAL] If specified, stubs generated from Kotlin source code will " +
1262                     "be written in Kotlin rather than the Java programming language.",
1263                 "$ARG_PASS_THROUGH_ANNOTATION <annotation classes>",
1264                 "A comma separated list of fully qualified names of " +
1265                     "annotation classes that must be passed through unchanged.",
1266                 "$ARG_EXCLUDE_ANNOTATION <annotation classes>",
1267                 "A comma separated list of fully qualified names of " +
1268                     "annotation classes that must be stripped from metalava's outputs.",
1269                 ARG_ENHANCE_DOCUMENTATION,
1270                 "Enhance documentation in various ways, for example auto-generating documentation based on source " +
1271                     "annotations present in the code. This is implied by --doc-stubs.",
1272                 ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS,
1273                 "Exclude element documentation (javadoc and kdoc) " +
1274                     "from the generated stubs. (Copyright notices are not affected by this, they are always included. " +
1275                     "Documentation stubs (--doc-stubs) are not affected.)",
1276                 "",
1277                 "Diffs and Checks:",
1278                 "$ARG_MIGRATE_NULLNESS <api file>",
1279                 "Compare nullness information with the previous stable API " +
1280                     "and mark newly annotated APIs as under migration.",
1281                 "",
1282                 "Extracting Annotations:",
1283                 "$ARG_EXTRACT_ANNOTATIONS <zipfile>",
1284                 "Extracts source annotations from the source files and writes " +
1285                     "them into the given zip file",
1286                 ARG_INCLUDE_SOURCE_RETENTION,
1287                 "If true, include source-retention annotations in the stub files. Does " +
1288                     "not apply to signature files. Source retention annotations are extracted into the external " +
1289                     "annotations files instead.",
1290                 "",
1291                 "Injecting API Levels:",
1292                 "$ARG_APPLY_API_LEVELS <api-versions.xml>",
1293                 "Reads an XML file containing API level descriptions " +
1294                     "and merges the information into the documentation",
1295                 "",
1296                 "Extracting API Levels:",
1297                 "$ARG_GENERATE_API_LEVELS <xmlfile>",
1298                 "Reads android.jar SDK files and generates an XML file recording " +
1299                     "the API level for each class, method and field",
1300                 ARG_REMOVE_MISSING_CLASS_REFERENCES_IN_API_LEVELS,
1301                 "Removes references to missing classes when generating the API levels XML file. " +
1302                     "This can happen when generating the XML file for the non-updatable portions of " +
1303                     "the module-lib sdk, as those non-updatable portions can reference classes that are " +
1304                     "part of an updatable apex.",
1305                 "$ARG_ANDROID_JAR_PATTERN <pattern>",
1306                 "Patterns to use to locate Android JAR files. The default " +
1307                     "is \$ANDROID_HOME/platforms/android-%/android.jar.",
1308                 ARG_FIRST_VERSION,
1309                 "Sets the first API level to generate an API database from; usually 1",
1310                 ARG_CURRENT_VERSION,
1311                 "Sets the current API level of the current source code",
1312                 ARG_CURRENT_CODENAME,
1313                 "Sets the code name for the current source code",
1314                 ARG_CURRENT_JAR,
1315                 "Points to the current API jar, if any",
1316                 ARG_SDK_JAR_ROOT,
1317                 "Points to root of prebuilt extension SDK jars, if any. This directory is expected to " +
1318                     "contain snapshots of historical extension SDK versions in the form of stub jars. " +
1319                     "The paths should be on the format \"<int>/public/<module-name>.jar\", where <int> " +
1320                     "corresponds to the extension SDK version, and <module-name> to the name of the mainline module.",
1321                 ARG_SDK_INFO_FILE,
1322                 "Points to map of extension SDK APIs to include, if any. The file is a plain text file " +
1323                     "and describes, per extension SDK, what APIs from that extension to include in the " +
1324                     "file created via $ARG_GENERATE_API_LEVELS. The format of each line is one of the following: " +
1325                     "\"<module-name> <pattern> <ext-name> [<ext-name> [...]]\", where <module-name> is the " +
1326                     "name of the mainline module this line refers to, <pattern> is a common Java name prefix " +
1327                     "of the APIs this line refers to, and <ext-name> is a list of extension SDK names " +
1328                     "in which these SDKs first appeared, or \"<ext-name> <ext-id> <type>\", where " +
1329                     "<ext-name> is the name of an SDK, " +
1330                     "<ext-id> its numerical ID and <type> is one of " +
1331                     "\"platform\" (the Android platform SDK), " +
1332                     "\"platform-ext\" (an extension to the Android platform SDK), " +
1333                     "\"standalone\" (a separate SDK). " +
1334                     "Fields are separated by whitespace. " +
1335                     "A mainline module may be listed multiple times. " +
1336                     "The special pattern \"*\" refers to all APIs in the given mainline module. " +
1337                     "Lines beginning with # are comments.",
1338                 "",
1339                 "Generating API version history:",
1340                 "$ARG_GENERATE_API_VERSION_HISTORY <jsonfile>",
1341                 "Reads API signature files and generates a JSON file recording the API version each " +
1342                     "class, method, and field was added in and (if applicable) deprecated in. " +
1343                     "Required to generate API version JSON.",
1344                 "$ARG_API_VERSION_SIGNATURE_FILES <files>",
1345                 "An ordered list of text API signature files. The oldest API version should be " +
1346                     "first, the newest last. This should not include a signature file for the " +
1347                     "current API version, which will be parsed from the provided source files. Not " +
1348                     "required to generate API version JSON if the current version is the only version.",
1349                 "$ARG_API_VERSION_NAMES <strings>",
1350                 "An ordered list of strings with the names to use for the API versions from " +
1351                     "$ARG_API_VERSION_SIGNATURE_FILES, and the name of the current API version. " +
1352                     "Required to generate API version JSON.",
1353                 "",
1354                 "Environment Variables:",
1355                 ENV_VAR_METALAVA_DUMP_ARGV,
1356                 "Set to true to have metalava emit all the arguments it was invoked with. " +
1357                     "Helpful when debugging or reproducing under a debugger what the build system is doing.",
1358                 ENV_VAR_METALAVA_PREPEND_ARGS,
1359                 "One or more arguments (concatenated by space) to insert into the " +
1360                     "command line, before the documentation flags.",
1361                 ENV_VAR_METALAVA_APPEND_ARGS,
1362                 "One or more arguments (concatenated by space) to append to the " +
1363                     "end of the command line, after the generate documentation flags."
1364             )
1365 
1366         val indent = " ".repeat(INDENT_WIDTH)
1367 
1368         var i = 0
1369         while (i < args.size) {
1370             val arg = args[i]
1371             if (arg.isEmpty()) {
1372                 val groupTitle = args[i + 1]
1373                 out.println("\n")
1374                 out.println(terminal.colorize(groupTitle, TerminalColor.YELLOW))
1375             } else {
1376                 val description = "\n" + args[i + 1]
1377                 val formattedArg = terminal.bold(arg)
1378                 val invisibleChars = formattedArg.length - arg.length
1379                 // +invisibleChars: the extra chars in the above are counted but don't
1380                 // contribute to width so allow more space
1381                 val formatString = "%1$-" + (INDENT_WIDTH + invisibleChars) + "s%2\$s"
1382 
1383                 val output =
1384                     wrap(
1385                         String.format(formatString, formattedArg, description),
1386                         width + invisibleChars,
1387                         width,
1388                         indent
1389                     )
1390 
1391                 // Remove trailing whitespace
1392                 val lines = output.lines()
1393                 lines.forEachIndexed { index, line ->
1394                     out.print(line.trimEnd())
1395                     if (index < lines.size - 1) {
1396                         out.println()
1397                     }
1398                 }
1399             }
1400             i += 2
1401         }
1402     }
1403 }
1404