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.DefaultSignatureFileLoader 24 import com.android.tools.metalava.cli.common.ExecutionEnvironment 25 import com.android.tools.metalava.cli.common.IssueReportingOptions 26 import com.android.tools.metalava.cli.common.PreviouslyReleasedApi 27 import com.android.tools.metalava.cli.common.SourceOptions 28 import com.android.tools.metalava.cli.common.Terminal 29 import com.android.tools.metalava.cli.common.TerminalColor 30 import com.android.tools.metalava.cli.common.Verbosity 31 import com.android.tools.metalava.cli.common.cliError 32 import com.android.tools.metalava.cli.common.enumOption 33 import com.android.tools.metalava.cli.common.existingFile 34 import com.android.tools.metalava.cli.common.fileForPathInner 35 import com.android.tools.metalava.cli.common.map 36 import com.android.tools.metalava.cli.common.stringToExistingDir 37 import com.android.tools.metalava.cli.common.stringToExistingFile 38 import com.android.tools.metalava.cli.common.stringToNewDir 39 import com.android.tools.metalava.cli.common.stringToNewFile 40 import com.android.tools.metalava.cli.compatibility.CompatibilityCheckOptions 41 import com.android.tools.metalava.cli.compatibility.CompatibilityCheckOptions.CheckRequest 42 import com.android.tools.metalava.cli.lint.ApiLintOptions 43 import com.android.tools.metalava.cli.signature.SignatureFormatOptions 44 import com.android.tools.metalava.doc.ApiVersionFilter 45 import com.android.tools.metalava.doc.ApiVersionLabelProvider 46 import com.android.tools.metalava.manifest.Manifest 47 import com.android.tools.metalava.manifest.emptyManifest 48 import com.android.tools.metalava.model.AnnotationManager 49 import com.android.tools.metalava.model.Codebase 50 import com.android.tools.metalava.model.Item 51 import com.android.tools.metalava.model.PackageFilter 52 import com.android.tools.metalava.model.PackageItem 53 import com.android.tools.metalava.model.TypedefMode 54 import com.android.tools.metalava.model.annotation.DefaultAnnotationManager 55 import com.android.tools.metalava.model.source.DEFAULT_JAVA_LANGUAGE_LEVEL 56 import com.android.tools.metalava.model.source.DEFAULT_KOTLIN_LANGUAGE_LEVEL 57 import com.android.tools.metalava.model.text.ApiClassResolution 58 import com.android.tools.metalava.model.text.EmitFileHeader 59 import com.android.tools.metalava.model.visitors.ApiPredicate 60 import com.android.tools.metalava.reporter.Baseline 61 import com.android.tools.metalava.reporter.DefaultReporter 62 import com.android.tools.metalava.reporter.IssueConfiguration 63 import com.android.tools.metalava.reporter.Issues 64 import com.android.tools.metalava.reporter.Reportable 65 import com.android.tools.metalava.reporter.Reporter 66 import com.android.tools.metalava.stub.StubWriterConfig 67 import com.android.utils.SdkUtils.wrap 68 import com.github.ajalt.clikt.core.NoSuchOption 69 import com.github.ajalt.clikt.parameters.groups.OptionGroup 70 import com.github.ajalt.clikt.parameters.options.default 71 import com.github.ajalt.clikt.parameters.options.deprecated 72 import com.github.ajalt.clikt.parameters.options.multiple 73 import com.github.ajalt.clikt.parameters.options.option 74 import com.github.ajalt.clikt.parameters.options.unique 75 import com.github.ajalt.clikt.parameters.types.choice 76 import com.github.ajalt.clikt.parameters.types.file 77 import java.io.File 78 import java.io.PrintWriter 79 import java.io.StringWriter 80 import java.util.Optional 81 import java.util.function.Predicate 82 import kotlin.properties.ReadWriteProperty 83 import kotlin.reflect.KProperty 84 import org.jetbrains.jps.model.java.impl.JavaSdkUtil 85 86 /** 87 * A [ReadWriteProperty] that is used as the delegate for [options]. 88 * 89 * It provides read/write methods and also a [disallowAccess] method which when called will cause 90 * any attempt to read the [options] property to fail. This allows code to ensure that any code 91 * which it calls does not access the deprecated [options] property. 92 */ 93 object OptionsDelegate : ReadWriteProperty<Nothing?, Options> { 94 95 /** 96 * The value of this delegate. 97 * 98 * Is `null` if [setValue] has not been called since the last call to [disallowAccess]. In that 99 * case any attempt to read the value of this delegate will fail. 100 */ 101 private var possiblyNullOptions: Options? = Options() 102 103 /** 104 * The stack trace of the last caller to [disallowAccess] (if any) to make it easy to determine 105 * why a read of [options] failed. 106 */ 107 private var disallowerStackTrace: Throwable? = null 108 109 /** Prevent all future reads of [options] until the [setValue] method is next called. */ 110 fun disallowAccess() { 111 disallowerStackTrace = UnexpectedOptionsAccess("Global options property cleared") 112 possiblyNullOptions = null 113 } 114 115 override fun setValue(thisRef: Nothing?, property: KProperty<*>, value: Options) { 116 disallowerStackTrace = null 117 possiblyNullOptions = value 118 } 119 120 override fun getValue(thisRef: Nothing?, property: KProperty<*>): Options { 121 return possiblyNullOptions 122 ?: throw UnexpectedOptionsAccess("options is not set", disallowerStackTrace!!) 123 } 124 } 125 126 /** A private class to try and avoid it being caught and ignored. */ 127 private class UnexpectedOptionsAccess(message: String, cause: Throwable? = null) : 128 RuntimeException(message, cause) 129 130 /** 131 * Global options for the metadata extraction tool 132 * 133 * This is an empty options which is created to avoid having a nullable options. It is replaced with 134 * the actual options to use, either created from the command line arguments for the main process or 135 * with arguments supplied by tests. 136 */ 137 @Deprecated( 138 """ 139 Do not add any more usages of this and please remove any existing uses that you find. Global 140 variables tightly couple all the code that uses them making them hard to test, modularize and 141 reuse. Which is why there is an ongoing process to remove usages of global variables and 142 eventually the global variable itself. 143 """ 144 ) 145 var options by OptionsDelegate 146 147 private const val INDENT_WIDTH = 45 148 149 const val ARG_CLASS_PATH = "--classpath" 150 const val ARG_SOURCE_FILES = "--source-files" 151 const val ARG_API_CLASS_RESOLUTION = "--api-class-resolution" 152 const val ARG_SDK_VALUES = "--sdk-values" 153 const val ARG_MERGE_QUALIFIER_ANNOTATIONS = "--merge-qualifier-annotations" 154 const val ARG_MERGE_INCLUSION_ANNOTATIONS = "--merge-inclusion-annotations" 155 const val ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS = "--validate-nullability-from-merged-stubs" 156 const val ARG_VALIDATE_NULLABILITY_FROM_LIST = "--validate-nullability-from-list" 157 const val ARG_NULLABILITY_WARNINGS_TXT = "--nullability-warnings-txt" 158 const val ARG_NULLABILITY_ERRORS_NON_FATAL = "--nullability-errors-non-fatal" 159 const val ARG_DOC_STUBS = "--doc-stubs" 160 /** Used by Firebase, see b/116185431#comment15, not used by Android Platform or AndroidX */ 161 const val ARG_PROGUARD = "--proguard" 162 const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations" 163 const val ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS = "--exclude-documentation-from-stubs" 164 const val ARG_ENHANCE_DOCUMENTATION = "--enhance-documentation" 165 const val ARG_SKIP_READING_COMMENTS = "--ignore-comments" 166 const val ARG_MANIFEST = "--manifest" 167 const val ARG_MIGRATE_NULLNESS = "--migrate-nullness" 168 const val ARG_SUPPRESS_COMPATIBILITY_META_ANNOTATION = "--suppress-compatibility-meta-annotation" 169 const val ARG_APPLY_API_LEVELS = "--apply-api-levels" 170 const val ARG_JAVA_SOURCE = "--java-source" 171 const val ARG_KOTLIN_SOURCE = "--kotlin-source" 172 const val ARG_SDK_HOME = "--sdk-home" 173 const val ARG_JDK_HOME = "--jdk-home" 174 const val ARG_COMPILE_SDK_VERSION = "--compile-sdk-version" 175 const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention" 176 const val ARG_PASS_THROUGH_ANNOTATION = "--pass-through-annotation" 177 const val ARG_EXCLUDE_ANNOTATION = "--exclude-annotation" 178 const val ARG_DELETE_EMPTY_REMOVED_SIGNATURES = "--delete-empty-removed-signatures" 179 const val ARG_SUBTRACT_API = "--subtract-api" 180 const val ARG_TYPEDEFS_IN_SIGNATURES = "--typedefs-in-signatures" 181 const val ARG_IGNORE_CLASSES_ON_CLASSPATH = "--ignore-classes-on-classpath" 182 const val ARG_USE_K2_UAST = "--Xuse-k2-uast" 183 const val ARG_PROJECT = "--project" 184 const val ARG_SOURCE_MODEL_PROVIDER = "--source-model-provider" 185 186 class Options( 187 private val executionEnvironment: ExecutionEnvironment = ExecutionEnvironment(), 188 private val commonOptions: CommonOptions = CommonOptions(), 189 private val sourceOptions: SourceOptions = SourceOptions(), 190 private val issueReportingOptions: IssueReportingOptions = 191 IssueReportingOptions(commonOptions = commonOptions), 192 private val generalReportingOptions: GeneralReportingOptions = GeneralReportingOptions(), 193 internal val configFileOptions: ConfigFileOptions = ConfigFileOptions(), 194 val apiSelectionOptions: ApiSelectionOptions = ApiSelectionOptions(), 195 val apiLintOptions: ApiLintOptions = ApiLintOptions(), 196 private val compatibilityCheckOptions: CompatibilityCheckOptions = CompatibilityCheckOptions(), 197 signatureFileOptions: SignatureFileOptions = SignatureFileOptions(), 198 signatureFormatOptions: SignatureFormatOptions = SignatureFormatOptions(), 199 stubGenerationOptions: StubGenerationOptions = StubGenerationOptions(), 200 internal val apiLevelsGenerationOptions: ApiLevelsGenerationOptions = 201 ApiLevelsGenerationOptions(), 202 ) : OptionGroup() { 203 /** Writer to direct output to. */ 204 val stdout: PrintWriter 205 get() = executionEnvironment.stdout 206 /** Writer to direct error messages to. */ 207 val stderr: PrintWriter 208 get() = executionEnvironment.stderr 209 210 /** Internal list backing [sources] */ 211 private val mutableSources: MutableList<File> = mutableListOf() 212 /** Internal list backing [classpath] */ 213 private val mutableClassPath: MutableList<File> = mutableListOf() 214 /** Internal list backing [mergeQualifierAnnotations] */ 215 private val mutableMergeQualifierAnnotations: MutableList<File> = mutableListOf() 216 /** Internal list backing [mergeInclusionAnnotations] */ 217 private val mutableMergeInclusionAnnotations: MutableList<File> = mutableListOf() 218 /** Internal list backing [passThroughAnnotations] */ 219 private val mutablePassThroughAnnotations: MutableSet<String> = mutableSetOf() 220 /** Internal list backing [excludeAnnotations] */ 221 private val mutableExcludeAnnotations: MutableSet<String> = mutableSetOf() 222 223 /** API to subtract from signature and stub generation. Corresponds to [ARG_SUBTRACT_API]. */ 224 var subtractApi: File? = null 225 226 /** 227 * Backing property for [nullabilityAnnotationsValidator] 228 * 229 * This uses [Optional] to wrap the value as [lazy] cannot handle nullable values as it uses 230 * `null` as a special value. 231 * 232 * Creates [NullabilityAnnotationsValidator] lazily as it depends on a number of different 233 * options which may be supplied in different orders. 234 */ <lambda>null235 private val optionalNullabilityAnnotationsValidator by lazy { 236 Optional.ofNullable( 237 if (validateNullabilityFromMergedStubs || validateNullabilityFromList != null) { 238 NullabilityAnnotationsValidator( 239 reporter, 240 nullabilityErrorsFatal, 241 nullabilityWarningsTxt 242 ) 243 } else null 244 ) 245 } 246 247 /** Validator for nullability annotations, if validation is enabled. */ 248 val nullabilityAnnotationsValidator: NullabilityAnnotationsValidator? 249 get() = optionalNullabilityAnnotationsValidator.orElse(null) 250 251 /** Whether nullability validation errors should be considered fatal. */ 252 private var nullabilityErrorsFatal = true 253 254 /** 255 * A file to write non-fatal nullability validation issues to. If null, all issues are treated 256 * as fatal or else logged as warnings, depending on the value of [nullabilityErrorsFatal]. 257 */ 258 private var nullabilityWarningsTxt: File? = null 259 260 /** 261 * Whether to validate nullability for all the classes where we are merging annotations from 262 * external java stub files. If true, [nullabilityAnnotationsValidator] must be set. 263 */ 264 var validateNullabilityFromMergedStubs = false 265 266 /** 267 * A file containing a list of classes whose nullability annotations should be validated. If 268 * set, [nullabilityAnnotationsValidator] must also be set. 269 */ 270 var validateNullabilityFromList: File? = null 271 272 /** 273 * Whether to include element documentation (javadoc and KDoc) is in the generated stubs. 274 * (Copyright notices are not affected by this, they are always included. Documentation stubs 275 * (--doc-stubs) are not affected.) 276 */ 277 private var includeDocumentationInStubs = true 278 279 /** 280 * Enhance documentation in various ways, for example auto-generating documentation based on 281 * source annotations present in the code. This is implied by --doc-stubs. 282 */ 283 var enhanceDocumentation = false 284 285 /** 286 * Whether to allow reading comments If false, any attempts by Metalava to read a PSI comment 287 * will return "" This can help callers to be sure that comment-only changes shouldn't affect 288 * Metalava output 289 */ 290 var allowReadingComments = true 291 292 /** The list of source roots */ 293 val sourcePath: List<File> by sourceOptions::sourcePath 294 295 /** The list of dependency jars */ 296 val classpath: List<File> = mutableClassPath 297 298 /** All source files to parse */ 299 var sources: List<File> = mutableSources 300 301 /** Lint project description that describes project's module structure in details */ 302 var projectDescription: File? = null 303 304 val apiClassResolution by 305 enumOption( 306 help = 307 """ 308 Determines how class resolution is performed when loading API signature files. Any 309 classes that cannot be found will be treated as empty.", 310 """ 311 .trimIndent(), <lambda>null312 enumValueHelpGetter = { it.help }, 313 default = ApiClassResolution.API_CLASSPATH, <lambda>null314 key = { it.optionValue }, 315 ) 316 317 val allShowAnnotations by apiSelectionOptions::allShowAnnotations 318 319 /** 320 * Whether to include unannotated elements if {@link #showAnnotations} is set. Note: This only 321 * applies to signature files, not stub files. 322 */ 323 val showUnannotated 324 get() = apiSelectionOptions.showUnannotated 325 326 val apiSurfaces 327 get() = apiSelectionOptions.apiSurfaces 328 329 /** Packages to include in the API (if null, include all) */ 330 val apiPackages: PackageFilter? by sourceOptions::apiPackages 331 332 /** 333 * An optional [Reportable] predicate that will ignore issues from (i.e. return false for) 334 * [Item]s that do not match the [apiPackages] filter. If no [apiPackages] filter is provided 335 * then this will be `null`. 336 */ 337 private val reportableFilter: Predicate<Reportable>? by <lambda>null338 lazy(LazyThreadSafetyMode.NONE) { 339 apiPackages?.let { packageFilter -> 340 Predicate { reportable -> 341 // If we are only emitting some packages (--stub-packages), don't report 342 // issues from other packages 343 (reportable as? Item)?.let { item -> 344 val pkg = (item as? PackageItem) ?: item.containingPackage() 345 pkg == null || packageFilter.matches(pkg) 346 } 347 ?: true 348 } 349 } 350 } 351 352 /** Packages that we should skip generating even if not hidden; typically only used by tests */ 353 val skipEmitPackages 354 get() = executionEnvironment.testEnvironment?.skipEmitPackages ?: emptyList() 355 <lambda>null356 private val annotationManager: AnnotationManager by lazy { 357 DefaultAnnotationManager( 358 DefaultAnnotationManager.Config( 359 passThroughAnnotations = passThroughAnnotations, 360 allShowAnnotations = allShowAnnotations, 361 showAnnotations = apiSelectionOptions.showAnnotations, 362 showSingleAnnotations = apiSelectionOptions.showSingleAnnotations, 363 showForStubPurposesAnnotations = apiSelectionOptions.showForStubPurposesAnnotations, 364 hideAnnotations = apiSelectionOptions.hideAnnotations, 365 suppressCompatibilityMetaAnnotations = suppressCompatibilityMetaAnnotations, 366 excludeAnnotations = excludeAnnotations, 367 typedefMode = typedefMode, 368 apiPredicate = ApiPredicate(config = apiPredicateConfig), 369 previouslyReleasedCodebaseProvider = { previouslyReleasedCodebase }, 370 apiFlags = ApiFlagsCreator.createFromConfig(configFileOptions.config.apiFlags), 371 ) 372 ) 373 } 374 375 /** Make this available for testing purposes. */ 376 internal val previouslyReleasedCodebase 377 get() = compatibilityCheckOptions.previouslyReleasedCodebase(signatureFileCache) 378 379 internal val codebaseConfig by <lambda>null380 lazy(LazyThreadSafetyMode.NONE) { 381 Codebase.Config( 382 annotationManager = annotationManager, 383 apiSurfaces = apiSurfaces, 384 reporter = reporter, 385 ) 386 } 387 388 internal val signatureFileLoader by <lambda>null389 lazy(LazyThreadSafetyMode.NONE) { DefaultSignatureFileLoader(codebaseConfig) } 390 391 internal val signatureFileCache by <lambda>null392 lazy(LazyThreadSafetyMode.NONE) { SignatureFileCache(signatureFileLoader) } 393 394 /** Meta-annotations for which annotated APIs should not be checked for compatibility. */ 395 private val suppressCompatibilityMetaAnnotations by 396 option( 397 ARG_SUPPRESS_COMPATIBILITY_META_ANNOTATION, 398 help = 399 """ 400 Suppress compatibility checks for any elements within the scope of an 401 annotation which is itself annotated with the given meta-annotation. 402 """ 403 .trimIndent(), 404 metavar = "<meta-annotation class>", 405 ) 406 .multiple() 407 .unique() 408 409 /** 410 * Whether the generated API can contain classes that are not present in the source but are 411 * present on the classpath. Defaults to true for backwards compatibility but is set to false if 412 * any API signatures are imported as they must provide a complete set of all classes required 413 * but not provided by the generated API. 414 * 415 * Once all APIs are either self-contained or imported all the required references this will be 416 * removed and no classes will be allowed from the classpath JARs. 417 */ 418 private var allowClassesFromClasspath = true 419 420 /** The configuration options for the [ApiAnalyzer] class. */ <lambda>null421 val apiAnalyzerConfig by lazy { 422 ApiAnalyzer.Config( 423 manifest = manifest, 424 skipEmitPackages = skipEmitPackages, 425 mergeQualifierAnnotations = mergeQualifierAnnotations, 426 mergeInclusionAnnotations = mergeInclusionAnnotations, 427 allShowAnnotations = allShowAnnotations, 428 apiPredicateConfig = apiPredicateConfig, 429 ) 430 } 431 <lambda>null432 val apiPredicateConfig by lazy { 433 ApiPredicate.Config( 434 ignoreShown = showUnannotated, 435 allowClassesFromClasspath = allowClassesFromClasspath, 436 addAdditionalOverrides = signatureFileFormat.addAdditionalOverrides, 437 ) 438 } 439 440 /** This is set directly by [preprocessArgv]. */ 441 private var verbosity: Verbosity = Verbosity.NORMAL 442 443 /** Whether to report warnings and other diagnostics along the way */ 444 val quiet: Boolean 445 get() = verbosity.quiet 446 447 /** 448 * Whether to report extra diagnostics along the way (note that verbose isn't the same as not 449 * quiet) 450 */ 451 val verbose: Boolean 452 get() = verbosity.verbose 453 <lambda>null454 internal val stubWriterConfig by lazy { 455 StubWriterConfig( 456 includeDocumentationInStubs = includeDocumentationInStubs, 457 ) 458 } 459 460 val stubsDir by stubGenerationOptions::stubsDir 461 val forceConvertToWarningNullabilityAnnotations by 462 stubGenerationOptions::forceConvertToWarningNullabilityAnnotations 463 val generateAnnotations by stubGenerationOptions::includeAnnotations 464 465 /** 466 * If set, a directory to write documentation stub files to. Corresponds to the --stubs/-stubs 467 * flag. 468 */ 469 var docStubsDir: File? = null 470 471 /** Proguard Keep list file to write */ 472 var proguard: File? = null 473 474 val apiFile by signatureFileOptions::apiFile 475 val removedApiFile by signatureFileOptions::removedApiFile 476 val signatureFileFormat by signatureFormatOptions::fileFormat 477 478 /** Path to directory to write SDK values to */ 479 var sdkValueDir: File? = null 480 481 /** 482 * If set, a file to write extracted annotations to. Corresponds to the --extract-annotations 483 * flag. 484 */ 485 var externalAnnotations: File? = null 486 487 /** An optional manifest [File]. */ 488 private val manifestFile by 489 option( 490 ARG_MANIFEST, 491 help = 492 """ 493 A manifest file, used to check permissions to cross check APIs and retrieve min_sdk_version. 494 (default: no manifest) 495 """ 496 .trimIndent() 497 ) 498 .file(mustExist = true, canBeDir = false, mustBeReadable = true) 499 500 /** 501 * A [Manifest] object to look up available permissions and min_sdk_version. 502 * 503 * Created lazily to make sure that the [reporter] has been initialized. 504 */ <lambda>null505 val manifest by lazy { manifestFile?.let { Manifest(it, reporter) } ?: emptyManifest } 506 507 /** The set of annotation classes that should be passed through unchanged */ 508 private var passThroughAnnotations = mutablePassThroughAnnotations 509 510 /** The set of annotation classes that should be removed from all outputs */ 511 private var excludeAnnotations = mutableExcludeAnnotations 512 513 /** A signature file to migrate nullness data from */ 514 val migrateNullsFrom by 515 option( 516 ARG_MIGRATE_NULLNESS, 517 metavar = "<api file>", 518 help = 519 """ 520 Compare nullness information with the previous stable API 521 and mark newly annotated APIs as under migration. 522 """ 523 .trimIndent() 524 ) 525 .existingFile() 526 .multiple() <lambda>null527 .map { 528 PreviouslyReleasedApi.optionalPreviouslyReleasedApi( 529 ARG_MIGRATE_NULLNESS, 530 it, 531 onlyUseLastForMainApiSurface = false 532 ) 533 } 534 535 /** The list of compatibility checks to run */ 536 val compatibilityChecks: List<CheckRequest> by compatibilityCheckOptions::compatibilityChecks 537 538 /** The set of annotation classes that should be treated as API compatibility important */ 539 val apiCompatAnnotations by compatibilityCheckOptions::apiCompatAnnotations 540 541 /** Existing external annotation files to merge in */ 542 private var mergeQualifierAnnotations: List<File> = mutableMergeQualifierAnnotations 543 private var mergeInclusionAnnotations: List<File> = mutableMergeInclusionAnnotations 544 545 val apiVersionLabelProvider: ApiVersionLabelProvider = 546 apiLevelsGenerationOptions::getApiVersionLabel 547 548 val includeApiLevelInDocumentation: ApiVersionFilter = 549 apiLevelsGenerationOptions::includeApiVersionInDocumentation 550 551 /** Reads API XML file to apply into documentation */ 552 var applyApiLevelsXml: File? = null 553 554 /** Whether to include the signature file format version header in removed signature files */ 555 val includeSignatureFormatVersionRemoved: EmitFileHeader 556 get() = 557 if (deleteEmptyRemovedSignatures) { 558 EmitFileHeader.IF_NONEMPTY_FILE 559 } else { 560 EmitFileHeader.ALWAYS 561 } 562 563 var allBaselines: List<Baseline> = emptyList() 564 565 /** [IssueConfiguration] used by all reporters. */ 566 val issueConfiguration by issueReportingOptions::issueConfiguration 567 568 /** [Reporter] that will redirect [Issues.Issue] depending on their [Issues.Category]. */ 569 lateinit var reporter: Reporter 570 private set 571 572 internal var allReporters: List<DefaultReporter> = emptyList() 573 574 /** If generating a removed signature file, and it is empty, delete it */ 575 var deleteEmptyRemovedSignatures = false 576 577 /** The language level to use for Java files, set with [ARG_JAVA_SOURCE] */ 578 var javaLanguageLevelAsString: String = DEFAULT_JAVA_LANGUAGE_LEVEL 579 580 /** The language level to use for Kotlin files, set with [ARG_KOTLIN_SOURCE] */ 581 var kotlinLanguageLevelAsString: String = DEFAULT_KOTLIN_LANGUAGE_LEVEL 582 583 /** 584 * The JDK to use as a platform, if set with [ARG_JDK_HOME]. This is only set when metalava is 585 * used for non-Android projects. 586 */ 587 var jdkHome: File? = null 588 589 /** 590 * The JDK to use as a platform, if set with [ARG_SDK_HOME]. If this is set along with 591 * [ARG_COMPILE_SDK_VERSION], metalava will automatically add the platform's android.jar file to 592 * the classpath if it does not already find the android.jar file in the classpath. 593 */ 594 private var sdkHome: File? = null 595 596 /** 597 * The compileSdkVersion, set by [ARG_COMPILE_SDK_VERSION]. For example, for R it would be "29". 598 * For R preview, it would be "R". 599 */ 600 private var compileSdkVersion: String? = null 601 602 /** 603 * How to handle typedef annotations in signature files; corresponds to 604 * $ARG_TYPEDEFS_IN_SIGNATURES 605 */ 606 private val typedefMode by 607 enumOption( 608 ARG_TYPEDEFS_IN_SIGNATURES, 609 help = """Whether to include typedef annotations in signature files.""", <lambda>null610 enumValueHelpGetter = { it.help }, 611 default = TypedefMode.NONE, <lambda>null612 key = { it.optionValue }, 613 ) 614 615 /** Temporary folder to use instead of the JDK default, if any */ 616 private var tempFolder: File? = null 617 618 var useK2Uast: Boolean? = null 619 620 val sourceModelProvider by 621 option( 622 ARG_SOURCE_MODEL_PROVIDER, 623 hidden = true, 624 ) 625 .choice("psi", "turbine") 626 .default("psi") 627 .deprecated( 628 """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. """ 629 .trimIndent() 630 ) 631 parsenull632 fun parse(args: Array<String>) { 633 var index = 0 634 while (index < args.size) { 635 when (val arg = args[index]) { 636 // For now, we don't distinguish between bootclasspath and classpath 637 ARG_CLASS_PATH -> { 638 val path = getValue(args, ++index) 639 mutableClassPath.addAll(stringToExistingDirsOrJars(path)) 640 } 641 ARG_SOURCE_FILES -> { 642 val listString = getValue(args, ++index) 643 listString.split(",").forEach { path -> 644 mutableSources.addAll(stringToExistingFiles(path)) 645 } 646 } 647 ARG_SUBTRACT_API -> { 648 if (subtractApi != null) { 649 cliError("Only one $ARG_SUBTRACT_API can be supplied") 650 } 651 subtractApi = stringToExistingFile(getValue(args, ++index)) 652 } 653 654 // TODO: Remove the legacy --merge-annotations flag once it's no longer used to 655 // update P docs 656 ARG_MERGE_QUALIFIER_ANNOTATIONS, 657 "--merge-zips", 658 "--merge-annotations" -> 659 mutableMergeQualifierAnnotations.addAll( 660 stringToExistingDirsOrFiles(getValue(args, ++index)) 661 ) 662 ARG_MERGE_INCLUSION_ANNOTATIONS -> 663 mutableMergeInclusionAnnotations.addAll( 664 stringToExistingDirsOrFiles(getValue(args, ++index)) 665 ) 666 ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS -> { 667 validateNullabilityFromMergedStubs = true 668 } 669 ARG_VALIDATE_NULLABILITY_FROM_LIST -> { 670 validateNullabilityFromList = stringToExistingFile(getValue(args, ++index)) 671 } 672 ARG_NULLABILITY_WARNINGS_TXT -> 673 nullabilityWarningsTxt = stringToNewFile(getValue(args, ++index)) 674 ARG_NULLABILITY_ERRORS_NON_FATAL -> nullabilityErrorsFatal = false 675 ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index)) 676 ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index)) 677 ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS -> includeDocumentationInStubs = false 678 ARG_ENHANCE_DOCUMENTATION -> enhanceDocumentation = true 679 ARG_SKIP_READING_COMMENTS -> allowReadingComments = false 680 ARG_PASS_THROUGH_ANNOTATION -> { 681 val annotations = getValue(args, ++index) 682 annotations.split(",").forEach { path -> 683 mutablePassThroughAnnotations.add(path) 684 } 685 } 686 ARG_EXCLUDE_ANNOTATION -> { 687 val annotations = getValue(args, ++index) 688 annotations.split(",").forEach { path -> mutableExcludeAnnotations.add(path) } 689 } 690 ARG_PROGUARD -> proguard = stringToNewFile(getValue(args, ++index)) 691 ARG_IGNORE_CLASSES_ON_CLASSPATH -> { 692 allowClassesFromClasspath = false 693 } 694 ARG_DELETE_EMPTY_REMOVED_SIGNATURES -> deleteEmptyRemovedSignatures = true 695 ARG_EXTRACT_ANNOTATIONS -> 696 externalAnnotations = stringToNewFile(getValue(args, ++index)) 697 698 // Extracting API levels 699 ARG_APPLY_API_LEVELS -> { 700 applyApiLevelsXml = 701 if (apiLevelsGenerationOptions.generateApiLevelXml != null) { 702 // If generating the API file at the same time, it doesn't have 703 // to already exist 704 stringToNewFile(getValue(args, ++index)) 705 } else { 706 stringToExistingFile(getValue(args, ++index)) 707 } 708 } 709 ARG_JAVA_SOURCE -> { 710 val value = getValue(args, ++index) 711 javaLanguageLevelAsString = value 712 } 713 ARG_KOTLIN_SOURCE -> { 714 val value = getValue(args, ++index) 715 kotlinLanguageLevelAsString = value 716 } 717 ARG_JDK_HOME -> { 718 jdkHome = stringToExistingDir(getValue(args, ++index)) 719 } 720 ARG_SDK_HOME -> { 721 sdkHome = stringToExistingDir(getValue(args, ++index)) 722 } 723 ARG_COMPILE_SDK_VERSION -> { 724 compileSdkVersion = getValue(args, ++index) 725 } 726 ARG_USE_K2_UAST -> useK2Uast = true 727 ARG_PROJECT -> { 728 projectDescription = stringToExistingFile(getValue(args, ++index)) 729 } 730 "--temp-folder" -> { 731 tempFolder = stringToNewOrExistingDir(getValue(args, ++index)) 732 } 733 734 // Option only meant for tests (not documented); doesn't work in all cases (to do 735 // that we'd 736 // need JNA to call libc) 737 "--pwd" -> { 738 val pwd = stringToExistingDir(getValue(args, ++index)).absoluteFile 739 System.setProperty("user.dir", pwd.path) 740 } 741 else -> { 742 if (arg.startsWith("-")) { 743 // Some other argument: display usage info and exit 744 throw NoSuchOption(givenName = arg) 745 } else { 746 // All args that don't start with "-" are taken to be filenames 747 mutableSources.addAll(stringToExistingFiles(arg)) 748 } 749 } 750 } 751 752 ++index 753 } 754 755 // Initialize the reporters. 756 val baseline = generalReportingOptions.baseline 757 val reporterUnknown = 758 createReporter( 759 executionEnvironment = executionEnvironment, 760 baseline = baseline, 761 errorMessage = null, 762 ) 763 764 val reporterApiLint = 765 createReporter( 766 executionEnvironment = executionEnvironment, 767 baseline = apiLintOptions.baseline ?: baseline, 768 errorMessage = apiLintOptions.errorMessage, 769 ) 770 771 // [Reporter] for "check-compatibility:*:released". 772 // i.e. 773 // [ARG_CHECK_COMPATIBILITY_API_RELEASED] and 774 // [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED]. 775 val reporterCompatibilityReleased = 776 createReporter( 777 executionEnvironment = executionEnvironment, 778 baseline = compatibilityCheckOptions.baseline ?: baseline, 779 errorMessage = compatibilityCheckOptions.errorMessage, 780 ) 781 782 // A Reporter that will redirect issues to the appropriate reporter based on the issue's 783 // Category. 784 reporter = 785 CategoryRedirectingReporter( 786 defaultReporter = reporterUnknown, 787 apiLintReporter = reporterApiLint, 788 compatibilityReporter = reporterCompatibilityReleased, 789 ) 790 791 // Build "all baselines" and "all reporters" 792 793 // Baselines are nullable, so selectively add to the list. 794 allBaselines = 795 listOfNotNull(baseline, apiLintOptions.baseline, compatibilityCheckOptions.baseline) 796 797 // Reporters are non-null. 798 allReporters = 799 listOf( 800 reporterUnknown, 801 reporterApiLint, 802 reporterCompatibilityReleased, 803 ) 804 805 updateClassPath() 806 807 // Make sure that any config files are processed. 808 configFileOptions.config 809 } 810 811 /** 812 * Create a [Reporter] that checks for known issues in [baseline] and prints [errorMessage], if 813 * provided, when errors have been reported. 814 */ createReporternull815 private fun createReporter( 816 executionEnvironment: ExecutionEnvironment, 817 baseline: Baseline?, 818 errorMessage: String?, 819 ) = 820 DefaultReporter( 821 environment = executionEnvironment.reporterEnvironment, 822 issueConfiguration = issueConfiguration, 823 baseline = baseline, 824 errorMessage = errorMessage, 825 reportableFilter = reportableFilter, 826 config = issueReportingOptions.reporterConfig, 827 ) 828 829 /** Update the classpath to insert android.jar or JDK classpath elements if necessary */ 830 private fun updateClassPath() { 831 val sdkHome = sdkHome 832 val jdkHome = jdkHome 833 834 if ( 835 sdkHome != null && 836 compileSdkVersion != null && 837 classpath.none { it.name == FN_FRAMEWORK_LIBRARY } 838 ) { 839 val jar = File(sdkHome, "platforms/android-$compileSdkVersion") 840 if (jar.isFile) { 841 mutableClassPath.add(jar) 842 } else { 843 cliError( 844 "Could not find android.jar for API level $compileSdkVersion in SDK $sdkHome: $jar does not exist" 845 ) 846 } 847 if (jdkHome != null) { 848 cliError("Do not specify both $ARG_SDK_HOME and $ARG_JDK_HOME") 849 } 850 } else if (jdkHome != null) { 851 val isJre = !isJdkFolder(jdkHome) 852 val roots = JavaSdkUtil.getJdkClassesRoots(jdkHome.toPath(), isJre).map { it.toFile() } 853 mutableClassPath.addAll(roots) 854 } 855 } 856 getValuenull857 private fun getValue(args: Array<String>, index: Int): String { 858 if (index >= args.size) { 859 cliError("Missing argument for ${args[index - 1]}") 860 } 861 return args[index] 862 } 863 stringToExistingDirsOrJarsnull864 private fun stringToExistingDirsOrJars(value: String): List<File> { 865 val files = mutableListOf<File>() 866 for (path in value.split(File.pathSeparatorChar)) { 867 val file = fileForPathInner(path) 868 if (!file.isDirectory && !(file.path.endsWith(SdkConstants.DOT_JAR) && file.isFile)) { 869 cliError("$file is not a jar or directory") 870 } 871 files.add(file) 872 } 873 return files 874 } 875 stringToExistingDirsOrFilesnull876 private fun stringToExistingDirsOrFiles(value: String): List<File> { 877 val files = mutableListOf<File>() 878 for (path in value.split(File.pathSeparatorChar)) { 879 val file = fileForPathInner(path) 880 if (!file.exists()) { 881 cliError("$file does not exist") 882 } 883 files.add(file) 884 } 885 return files 886 } 887 888 @Suppress("unused") stringToExistingFileOrDirnull889 private fun stringToExistingFileOrDir(value: String): File { 890 val file = fileForPathInner(value) 891 if (!file.exists()) { 892 cliError("$file is not a file or directory") 893 } 894 return file 895 } 896 stringToExistingFilesnull897 private fun stringToExistingFiles(value: String): List<File> { 898 return value 899 .split(File.pathSeparatorChar) 900 .map { fileForPathInner(it) } 901 .map { file -> 902 if (!file.isFile) { 903 cliError("$file is not a file") 904 } 905 file 906 } 907 } 908 stringToNewOrExistingDirnull909 private fun stringToNewOrExistingDir(value: String): File { 910 val dir = fileForPathInner(value) 911 if (!dir.isDirectory) { 912 val ok = dir.mkdirs() 913 if (!ok) { 914 cliError("Could not create $dir") 915 } 916 } 917 return dir 918 } 919 } 920 921 object OptionsHelp { getUsagenull922 fun getUsage(terminal: Terminal, width: Int): String { 923 val usage = StringWriter() 924 val printWriter = PrintWriter(usage) 925 usage(printWriter, terminal, width) 926 return usage.toString() 927 } 928 usagenull929 private fun usage(out: PrintWriter, terminal: Terminal, width: Int) { 930 val args = 931 arrayOf( 932 "", 933 "API sources:", 934 "$ARG_SOURCE_FILES <files>", 935 "A comma separated list of source files to be parsed. Can also be " + 936 "@ followed by a path to a text file containing paths to the full set of files to parse.", 937 "$ARG_CLASS_PATH <paths>", 938 "One or more directories or jars (separated by " + 939 "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " + 940 "source files", 941 "$ARG_PROJECT <xmlfile>", 942 "Project description written in XML according to Lint's project model.", 943 "$ARG_MERGE_QUALIFIER_ANNOTATIONS <file>", 944 "An external annotations file to merge and overlay " + 945 "the sources, or a directory of such files. Should be used for annotations intended for " + 946 "inclusion in the API to be written out, e.g. nullability. Formats supported are: IntelliJ's " + 947 "external annotations database format, .jar or .zip files containing those, Android signature " + 948 "files, and Java stub files.", 949 "$ARG_MERGE_INCLUSION_ANNOTATIONS <file>", 950 "An external annotations file to merge and overlay " + 951 "the sources, or a directory of such files. Should be used for annotations which determine " + 952 "inclusion in the API to be written out, i.e. show and hide. The only format supported is " + 953 "Java stub files.", 954 ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS, 955 "Triggers validation of nullability annotations " + 956 "for any class where $ARG_MERGE_QUALIFIER_ANNOTATIONS includes a Java stub file.", 957 ARG_VALIDATE_NULLABILITY_FROM_LIST, 958 "Triggers validation of nullability annotations " + 959 "for any class listed in the named file (one top-level class per line, # prefix for comment line).", 960 "$ARG_NULLABILITY_WARNINGS_TXT <file>", 961 "Specifies where to write warnings encountered during " + 962 "validation of nullability annotations. (Does not trigger validation by itself.)", 963 ARG_NULLABILITY_ERRORS_NON_FATAL, 964 "Specifies that errors encountered during validation of " + 965 "nullability annotations should not be treated as errors. They will be written out to the " + 966 "file specified in $ARG_NULLABILITY_WARNINGS_TXT instead.", 967 "$ARG_JAVA_SOURCE <level>", 968 "Sets the source level for Java source files; default is $DEFAULT_JAVA_LANGUAGE_LEVEL.", 969 "$ARG_KOTLIN_SOURCE <level>", 970 "Sets the source level for Kotlin source files; default is $DEFAULT_KOTLIN_LANGUAGE_LEVEL.", 971 "$ARG_SDK_HOME <dir>", 972 "If set, locate the `android.jar` file from the given Android SDK", 973 "$ARG_COMPILE_SDK_VERSION <api>", 974 "Use the given API level", 975 "$ARG_JDK_HOME <dir>", 976 "If set, add the Java APIs from the given JDK to the classpath", 977 "$ARG_SUBTRACT_API <api file>", 978 "Subtracts the API in the given signature or jar file from the " + 979 "current API being emitted via $ARG_API, $ARG_STUBS, $ARG_DOC_STUBS, etc. " + 980 "Note that the subtraction only applies to classes; it does not subtract members.", 981 ARG_IGNORE_CLASSES_ON_CLASSPATH, 982 "Prevents references to classes on the classpath from being added to " + 983 "the generated stub files.", 984 ARG_SKIP_READING_COMMENTS, 985 "Ignore any comments in source files.", 986 "", 987 "Extracting Signature Files:", 988 // TODO: Document --show-annotation! 989 "$ARG_PROGUARD <file>", 990 "Write a ProGuard keep file for the API", 991 "$ARG_SDK_VALUES <dir>", 992 "Write SDK values files to the given directory", 993 "", 994 "Generating Stubs:", 995 "$ARG_DOC_STUBS <dir>", 996 "Generate documentation stub source files for the API. Documentation stub " + 997 "files are similar to regular stub files, but there are some differences. For example, in " + 998 "the stub files, we'll use special annotations like @RecentlyNonNull instead of @NonNull to " + 999 "indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " + 1000 "just list this as @NonNull. Another difference is that @doconly elements are included in " + 1001 "documentation stubs, but not regular stubs, etc.", 1002 "$ARG_PASS_THROUGH_ANNOTATION <annotation classes>", 1003 "A comma separated list of fully qualified names of " + 1004 "annotation classes that must be passed through unchanged.", 1005 "$ARG_EXCLUDE_ANNOTATION <annotation classes>", 1006 "A comma separated list of fully qualified names of " + 1007 "annotation classes that must be stripped from metalava's outputs.", 1008 ARG_ENHANCE_DOCUMENTATION, 1009 "Enhance documentation in various ways, for example auto-generating documentation based on source " + 1010 "annotations present in the code. This is implied by --doc-stubs.", 1011 ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, 1012 "Exclude element documentation (javadoc and kdoc) " + 1013 "from the generated stubs. (Copyright notices are not affected by this, they are always included. " + 1014 "Documentation stubs (--doc-stubs) are not affected.)", 1015 "", 1016 "Extracting Annotations:", 1017 "$ARG_EXTRACT_ANNOTATIONS <zipfile>", 1018 "Extracts source annotations from the source files and writes " + 1019 "them into the given zip file", 1020 ARG_INCLUDE_SOURCE_RETENTION, 1021 "If true, include source-retention annotations in the stub files. Does " + 1022 "not apply to signature files. Source retention annotations are extracted into the external " + 1023 "annotations files instead.", 1024 "", 1025 "Injecting API Levels:", 1026 "$ARG_APPLY_API_LEVELS <api-versions.xml>", 1027 "Reads an XML file containing API level descriptions " + 1028 "and merges the information into the documentation", 1029 "", 1030 "Environment Variables:", 1031 ENV_VAR_METALAVA_DUMP_ARGV, 1032 "Set to true to have metalava emit all the arguments it was invoked with. " + 1033 "Helpful when debugging or reproducing under a debugger what the build system is doing.", 1034 ENV_VAR_METALAVA_PREPEND_ARGS, 1035 "One or more arguments (concatenated by space) to insert into the " + 1036 "command line, before the documentation flags.", 1037 ENV_VAR_METALAVA_APPEND_ARGS, 1038 "One or more arguments (concatenated by space) to append to the " + 1039 "end of the command line, after the generate documentation flags." 1040 ) 1041 1042 val indent = " ".repeat(INDENT_WIDTH) 1043 1044 var i = 0 1045 while (i < args.size) { 1046 val arg = args[i] 1047 if (arg.isEmpty()) { 1048 val groupTitle = args[i + 1] 1049 out.println("\n") 1050 out.println(terminal.colorize(groupTitle, TerminalColor.YELLOW)) 1051 } else { 1052 val description = "\n" + args[i + 1] 1053 val formattedArg = terminal.bold(arg) 1054 val invisibleChars = formattedArg.length - arg.length 1055 // +invisibleChars: the extra chars in the above are counted but don't 1056 // contribute to width so allow more space 1057 val formatString = "%1$-" + (INDENT_WIDTH + invisibleChars) + "s%2\$s" 1058 1059 val output = 1060 wrap( 1061 String.format(formatString, formattedArg, description), 1062 width + invisibleChars, 1063 width, 1064 indent 1065 ) 1066 1067 // Remove trailing whitespace 1068 val lines = output.lines() 1069 lines.forEachIndexed { index, line -> 1070 out.print(line.trimEnd()) 1071 if (index < lines.size - 1) { 1072 out.println() 1073 } 1074 } 1075 } 1076 i += 2 1077 } 1078 } 1079 } 1080