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.DOT_TXT
20 import com.android.ide.common.process.DefaultProcessExecutor
21 import com.android.ide.common.process.LoggedProcessOutputHandler
22 import com.android.ide.common.process.ProcessException
23 import com.android.ide.common.process.ProcessInfoBuilder
24 import com.android.tools.lint.LintCliClient
25 import com.android.tools.lint.UastEnvironment
26 import com.android.tools.lint.checks.ApiLookup
27 import com.android.tools.lint.checks.infrastructure.ClassName
28 import com.android.tools.lint.checks.infrastructure.TestFile
29 import com.android.tools.lint.checks.infrastructure.TestFiles.java
30 import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
31 import com.android.tools.lint.checks.infrastructure.stripComments
32 import com.android.tools.lint.client.api.LintClient
33 import com.android.tools.metalava.cli.common.ARG_COMMON_SOURCE_PATH
34 import com.android.tools.metalava.cli.common.ARG_HIDE
35 import com.android.tools.metalava.cli.common.ARG_NO_COLOR
36 import com.android.tools.metalava.cli.common.ARG_QUIET
37 import com.android.tools.metalava.cli.common.ARG_REPEAT_ERRORS_MAX
38 import com.android.tools.metalava.cli.common.ARG_SOURCE_PATH
39 import com.android.tools.metalava.cli.common.ARG_VERBOSE
40 import com.android.tools.metalava.cli.common.ExecutionEnvironment
41 import com.android.tools.metalava.cli.common.TestEnvironment
42 import com.android.tools.metalava.cli.compatibility.ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED
43 import com.android.tools.metalava.cli.compatibility.ARG_CHECK_COMPATIBILITY_API_RELEASED
44 import com.android.tools.metalava.cli.compatibility.ARG_CHECK_COMPATIBILITY_BASE_API
45 import com.android.tools.metalava.cli.compatibility.ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED
46 import com.android.tools.metalava.cli.compatibility.ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED
47 import com.android.tools.metalava.cli.compatibility.ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED
48 import com.android.tools.metalava.cli.lint.ARG_API_LINT
49 import com.android.tools.metalava.cli.lint.ARG_API_LINT_PREVIOUS_API
50 import com.android.tools.metalava.cli.lint.ARG_BASELINE_API_LINT
51 import com.android.tools.metalava.cli.lint.ARG_ERROR_MESSAGE_API_LINT
52 import com.android.tools.metalava.cli.lint.ARG_UPDATE_BASELINE_API_LINT
53 import com.android.tools.metalava.cli.signature.ARG_FORMAT
54 import com.android.tools.metalava.model.provider.Capability
55 import com.android.tools.metalava.model.psi.PsiModelOptions
56 import com.android.tools.metalava.model.source.SourceModelProvider
57 import com.android.tools.metalava.model.source.SourceSet
58 import com.android.tools.metalava.model.source.utils.DOT_KT
59 import com.android.tools.metalava.model.testing.CodebaseCreatorConfig
60 import com.android.tools.metalava.model.testing.CodebaseCreatorConfigAware
61 import com.android.tools.metalava.model.text.ApiClassResolution
62 import com.android.tools.metalava.model.text.ApiFile
63 import com.android.tools.metalava.model.text.FileFormat
64 import com.android.tools.metalava.model.text.SignatureFile
65 import com.android.tools.metalava.model.text.assertSignatureFilesMatch
66 import com.android.tools.metalava.model.text.prepareSignatureFileForTest
67 import com.android.tools.metalava.reporter.Severity
68 import com.android.tools.metalava.testing.KnownSourceFiles
69 import com.android.tools.metalava.testing.TemporaryFolderOwner
70 import com.android.tools.metalava.testing.findKotlinStdlibPaths
71 import com.android.tools.metalava.testing.getAndroidJar
72 import com.android.utils.SdkUtils
73 import com.android.utils.StdLogger
74 import com.google.common.io.Closeables
75 import com.intellij.openapi.util.Disposer
76 import java.io.ByteArrayOutputStream
77 import java.io.File
78 import java.io.FileNotFoundException
79 import java.io.PrintStream
80 import java.io.PrintWriter
81 import java.io.StringWriter
82 import java.net.URL
83 import kotlin.text.Charsets.UTF_8
84 import org.intellij.lang.annotations.Language
85 import org.junit.Assert.assertEquals
86 import org.junit.Assert.assertNotNull
87 import org.junit.Assert.assertTrue
88 import org.junit.Assert.fail
89 import org.junit.Before
90 import org.junit.Rule
91 import org.junit.rules.ErrorCollector
92 import org.junit.rules.TemporaryFolder
93 import org.junit.runner.RunWith
94
95 @RunWith(DriverTestRunner::class)
96 abstract class DriverTest : CodebaseCreatorConfigAware<SourceModelProvider>, TemporaryFolderOwner {
97 @get:Rule override val temporaryFolder = TemporaryFolder()
98
99 @get:Rule val errorCollector = ErrorCollector()
100
101 /** The [CodebaseCreatorConfig] under which this test will be run. */
102 final override lateinit var codebaseCreatorConfig: CodebaseCreatorConfig<SourceModelProvider>
103
104 /**
105 * The setting of [PsiModelOptions.useK2Uast]. Is computed lazily as it depends on
106 * [codebaseCreatorConfig] which is set after object initialization.
107 */
108 protected val isK2 by
109 lazy(LazyThreadSafetyMode.NONE) {
110 codebaseCreatorConfig.modelOptions[PsiModelOptions.useK2Uast]
111 }
112
113 @Before
114 fun setup() {
115 Disposer.setDebugMode(true)
116 }
117
118 // Makes a note to fail the test, but still allows the test to complete before failing
119 protected fun addError(error: String) {
120 errorCollector.addError(Throwable(error))
121 }
122
123 protected fun getApiFile(): File {
124 return File(temporaryFolder.root.path, "public-api.txt")
125 }
126
127 private fun runDriver(
128 // The SameParameterValue check reports that this is passed the same value because the first
129 // value that is passed is always the same but this is a varargs parameter so other values
130 // that are passed matter, and they are not the same.
131 args: Array<String>,
132 expectedFail: String,
133 reporterEnvironment: ReporterEnvironment,
134 testEnvironment: TestEnvironment,
135 ): String {
136 // Capture the actual input and output from System.out/err and compare it to the output
137 // printed through the official writer; they should be the same, otherwise we have stray
138 // print calls littered in the code!
139 val previousOut = System.out
140 val previousErr = System.err
141 try {
142 val output = TeeWriter(previousOut)
143 System.setOut(PrintStream(output))
144 val error = TeeWriter(previousErr)
145 System.setErr(PrintStream(error))
146
147 val sw = StringWriter()
148 val writer = PrintWriter(sw)
149
150 Disposer.setDebugMode(true)
151
152 val executionEnvironment =
153 ExecutionEnvironment(
154 stdout = writer,
155 stderr = writer,
156 reporterEnvironment = reporterEnvironment,
157 testEnvironment = testEnvironment,
158 )
159 val exitCode = run(executionEnvironment, args)
160 if (exitCode == 0) {
161 assertTrue(
162 "Test expected to fail but didn't. Expected failure: $expectedFail",
163 expectedFail.isEmpty()
164 )
165 } else {
166 val actualFail = cleanupString(sw.toString(), null)
167 if (
168 cleanupString(expectedFail, null).replace(".", "").trim() !=
169 actualFail.replace(".", "").trim()
170 ) {
171 val reportedCompatError =
172 actualFail.startsWith(
173 "Aborting: Found compatibility problems checking the "
174 )
175 if (
176 expectedFail == "Aborting: Found compatibility problems" &&
177 reportedCompatError
178 ) {
179 // Special case for compat checks; we don't want to force each one of them
180 // to pass in the right string (which may vary based on whether writing out
181 // the signature was passed at the same time
182 // ignore
183 } else {
184 if (reportedCompatError) {
185 // if a compatibility error was unexpectedly reported, then mark that as
186 // an error but keep going, so we can see the actual compatibility error
187 if (expectedFail.trimIndent() != actualFail) {
188 addError(
189 "ComparisonFailure: expected failure $expectedFail, actual $actualFail"
190 )
191 }
192 } else {
193 // no compatibility error; check for other errors now, and
194 // if one is found, fail right away
195 assertEquals(
196 "expectedFail does not match actual failures",
197 expectedFail.trimIndent(),
198 actualFail
199 )
200 }
201 }
202 }
203 }
204
205 val stdout = output.toString(UTF_8.name())
206 if (stdout.isNotEmpty()) {
207 addError("Unexpected write to stdout:\n $stdout")
208 }
209 val stderr = error.toString(UTF_8.name())
210 if (stderr.isNotEmpty()) {
211 addError("Unexpected write to stderr:\n $stderr")
212 }
213
214 val printedOutput = sw.toString()
215 if (printedOutput.isNotEmpty() && printedOutput.trim().isEmpty()) {
216 fail("Printed newlines with nothing else")
217 }
218
219 UastEnvironment.checkApplicationEnvironmentDisposed()
220 Disposer.assertIsEmpty(true)
221
222 return printedOutput
223 } finally {
224 System.setOut(previousOut)
225 System.setErr(previousErr)
226 }
227 }
228
229 // This is here, so we can keep a record of what was printed, to make sure we don't have any
230 // unexpected print calls in the source that are left behind after debugging and pollute the
231 // production output
232 class TeeWriter(private val otherStream: PrintStream) : ByteArrayOutputStream() {
233 override fun write(b: ByteArray, off: Int, len: Int) {
234 otherStream.write(b, off, len)
235 super.write(b, off, len)
236 }
237
238 override fun write(b: ByteArray) {
239 otherStream.write(b)
240 super.write(b)
241 }
242
243 override fun write(b: Int) {
244 otherStream.write(b)
245 super.write(b)
246 }
247 }
248
249 private fun getJdkPath(): String? {
250 val javaHome = System.getProperty("java.home")
251 if (javaHome != null) {
252 var javaHomeFile = File(javaHome)
253 if (File(javaHomeFile, "bin${File.separator}javac").exists()) {
254 return javaHome
255 } else if (javaHomeFile.name == "jre") {
256 javaHomeFile = javaHomeFile.parentFile
257 if (File(javaHomeFile, "bin${File.separator}javac").exists()) {
258 return javaHomeFile.path
259 }
260 }
261 }
262 return System.getenv("JAVA_HOME")
263 }
264
265 private fun <T> buildOptionalArgs(option: T?, converter: (T) -> Array<String>): Array<String> {
266 return if (option != null) {
267 converter(option)
268 } else {
269 emptyArray()
270 }
271 }
272
273 /** Test information related to a baseline file. */
274 data class BaselineTestInfo(
275 /**
276 * The contents of the input baseline.
277 *
278 * If this is `null` then no baseline testing is performed.
279 */
280 val inputContents: String? = null,
281
282 /** The contents of the expected updated baseline. */
283 val expectedOutputContents: String? = null,
284
285 /** Indicates whether testing of the baseline should suppress reporting of issues or not. */
286 val silentUpdate: Boolean = true,
287 ) {
288 init {
289 if (inputContents == null && expectedOutputContents != null) {
290 error("`inputContents` must be non-null as `expectedOutputContents` is non-null")
291 }
292 }
293 }
294
295 /** Represents a check that can be performed on a baseline file. */
296 @Suppress("ArrayInDataClass")
297 private data class BaselineCheck(
298 /** The option for the input baseline, used in test failure messages. */
299 val baselineOption: String,
300
301 /** The args to pass to metalava. */
302 val args: Array<String>,
303
304 /**
305 * The input/output file.
306 *
307 * If this is `null` then no check is performed.
308 */
309 val file: File?,
310
311 /** The expected contents of [file]. */
312 val expectedFileContents: String,
313 ) {
314 /** Apply the baseline check. */
315 fun apply() {
316 file ?: return
317
318 assertTrue(
319 "${file.path} does not exist even though $baselineOption was used",
320 file.exists()
321 )
322
323 val actualText = readFile(file)
324 assertEquals(
325 stripComments(
326 expectedFileContents.trimIndent(),
327 DOT_TXT,
328 stripLineComments = false
329 ),
330 actualText
331 )
332 }
333 }
334
335 private fun buildBaselineCheck(
336 baselineOption: String,
337 updateBaselineOption: String,
338 filename: String,
339 info: BaselineTestInfo,
340 ): BaselineCheck {
341 return info.inputContents?.let { inputContents ->
342 val baselineFile = temporaryFolder.newFile(filename)
343 baselineFile?.writeText(inputContents.trimIndent())
344 val args = arrayOf(baselineOption, baselineFile.path)
345
346 info.expectedOutputContents?.let { expectedOutputContents ->
347 // If silent update is request then use the same baseline file for update as for the
348 // input, otherwise create a separate update file.
349 val updateFile =
350 if (info.silentUpdate) baselineFile
351 else temporaryFolder.newFile("update-$filename")
352
353 // As expected output contents are provided add extra arguments to output the
354 // baseline and then compare the baseline file against the expected output. Use the
355 // update baseline option in any error messages.
356 BaselineCheck(
357 updateBaselineOption,
358 args + arrayOf(updateBaselineOption, updateFile.path),
359 updateFile,
360 expectedOutputContents,
361 )
362 }
363 ?:
364 // As no expected output is provided then compare the baseline file against the
365 // supplied input contents to make sure that they have not changed. Use the
366 // basic baseline option in any error messages.
367 BaselineCheck(
368 baselineOption,
369 args,
370 baselineFile,
371 inputContents,
372 )
373 }
374 ?: BaselineCheck("", emptyArray(), null, "")
375 }
376
377 @Suppress("DEPRECATION")
378 protected fun check(
379 /** Any jars to add to the class path */
380 classpath: Array<TestFile>? = null,
381 /** The API signature content (corresponds to --api) */
382 @Language("TEXT") api: String? = null,
383 /** The DEX API (corresponds to --dex-api) */
384 dexApi: String? = null,
385 /** The removed API (corresponds to --removed-api) */
386 removedApi: String? = null,
387 /** The subtract api signature content (corresponds to --subtract-api) */
388 @Language("TEXT") subtractApi: String? = null,
389 /** Expected stubs (corresponds to --stubs) */
390 stubFiles: Array<TestFile> = emptyArray(),
391 /** Expected paths of stub files created */
392 stubPaths: Array<String>? = null,
393 /**
394 * Whether the stubs should be written as documentation stubs instead of plain stubs.
395 * Decides whether the stubs include @doconly elements, uses rewritten/migration
396 * annotations, etc
397 */
398 docStubs: Boolean = false,
399 /** Signature file format */
400 format: FileFormat = FileFormat.LATEST,
401 /** All expected issues to be generated when analyzing these sources */
402 expectedIssues: String? = "",
403 /** Expected [Severity.ERROR] issues to be generated when analyzing these sources */
404 errorSeverityExpectedIssues: String? = null,
405 checkCompilation: Boolean = false,
406 /** Annotations to merge in (in .xml format) */
407 @Language("XML") mergeXmlAnnotations: String? = null,
408 /** Annotations to merge in (in .txt/.signature format) */
409 @Language("TEXT") mergeSignatureAnnotations: String? = null,
410 /** Qualifier annotations to merge in (in Java stub format) */
411 @Language("JAVA") mergeJavaStubAnnotations: String? = null,
412 /** Inclusion annotations to merge in (in Java stub format) */
413 mergeInclusionAnnotations: Array<TestFile> = emptyArray(),
414 /** Optional API signature files content to load **instead** of Java/Kotlin source files */
415 @Language("TEXT") signatureSources: Array<String> = emptyArray(),
416 apiClassResolution: ApiClassResolution = ApiClassResolution.API,
417 /**
418 * An optional API signature file content to load **instead** of Java/Kotlin source files.
419 * This is added to [signatureSources]. This argument exists for backward compatibility.
420 */
421 @Language("TEXT") signatureSource: String? = null,
422 /** An optional API jar file content to load **instead** of Java/Kotlin source files */
423 apiJar: File? = null,
424 /**
425 * An optional API signature to check the last released API's compatibility with.
426 *
427 * This can either be the name of a file or the contents of the signature file. In the
428 * latter case the contents are adjusted to make sure it is a valid signature file with a
429 * valid header and written to a file.
430 */
431 @Language("TEXT") checkCompatibilityApiReleased: String? = null,
432 /**
433 * Allow specifying multiple instances of [checkCompatibilityApiReleased].
434 *
435 * In order from narrowest to widest API.
436 */
437 checkCompatibilityApiReleasedList: List<String> = emptyList(),
438 /**
439 * An optional API signature to check the last released removed API's compatibility with.
440 *
441 * See [checkCompatibilityApiReleased].
442 */
443 @Language("TEXT") checkCompatibilityRemovedApiReleased: String? = null,
444 /**
445 * Allow specifying multiple instances of [checkCompatibilityRemovedApiReleased].
446 *
447 * In order from narrowest to widest API.
448 */
449 checkCompatibilityRemovedApiReleasedList: List<String> = emptyList(),
450 /** An optional API signature to use as the base API codebase during compat checks */
451 @Language("TEXT") checkCompatibilityBaseApi: String? = null,
452 @Language("TEXT") migrateNullsApi: String? = null,
453 /** An optional Proguard keep file to generate */
454 @Language("Proguard") proguard: String? = null,
455 /** Show annotations (--show-annotation arguments) */
456 showAnnotations: Array<String> = emptyArray(),
457 /** "Show for stub purposes" API annotation ([ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION]) */
458 showForStubPurposesAnnotations: Array<String> = emptyArray(),
459 /** Hide annotations (--hide-annotation arguments) */
460 hideAnnotations: Array<String> = emptyArray(),
461 /** No compat check meta-annotations (--no-compat-check-meta-annotation arguments) */
462 suppressCompatibilityMetaAnnotations: Array<String> = emptyArray(),
463 /** If using [showAnnotations], whether to include unannotated */
464 showUnannotated: Boolean = false,
465 /** Additional arguments to supply */
466 extraArguments: Array<String> = emptyArray(),
467 /** Expected output (stdout and stderr combined). If null, don't check. */
468 expectedOutput: String? = null,
469 /** Expected fail message and state, if any */
470 expectedFail: String? = null,
471 /** Optional manifest to load and associate with the codebase */
472 @Language("XML") manifest: String? = null,
473 /**
474 * Packages to pre-import (these will therefore NOT be included in emitted stubs, signature
475 * files etc
476 */
477 importedPackages: List<String> = emptyList(),
478 /** See [TestEnvironment.skipEmitPackages] */
479 skipEmitPackages: List<String> = listOf("java.lang", "java.util", "java.io"),
480 /** Whether we should include --showAnnotations=android.annotation.SystemApi */
481 includeSystemApiAnnotations: Boolean = false,
482 /** Whether we should warn about super classes that are stripped because they are hidden */
483 includeStrippedSuperclassWarnings: Boolean = false,
484 /** Apply level to XML */
485 applyApiLevelsXml: String? = null,
486 /** Corresponds to SDK constants file broadcast_actions.txt */
487 sdkBroadcastActions: String? = null,
488 /** Corresponds to SDK constants file activity_actions.txt */
489 sdkActivityActions: String? = null,
490 /** Corresponds to SDK constants file service_actions.txt */
491 sdkServiceActions: String? = null,
492 /** Corresponds to SDK constants file categories.txt */
493 sdkCategories: String? = null,
494 /** Corresponds to SDK constants file features.txt */
495 sdkFeatures: String? = null,
496 /** Corresponds to SDK constants file widgets.txt */
497 sdkWidgets: String? = null,
498 /**
499 * Extract annotations and check that the given packages contain the given extracted XML
500 * files
501 */
502 extractAnnotations: Map<String, String>? = null,
503 /**
504 * Creates the nullability annotations validator, and check that the report has the given
505 * lines (does not define files to be validated)
506 */
507 validateNullability: Set<String>? = null,
508 /** Enable nullability validation for the listed classes */
509 validateNullabilityFromList: String? = null,
510 /** Hook for performing additional initialization of the project directory */
511 projectSetup: ((File) -> Unit)? = null,
512 /** [ARG_BASELINE] and [ARG_UPDATE_BASELINE] */
513 baselineTestInfo: BaselineTestInfo = BaselineTestInfo(),
514 /** [ARG_BASELINE_API_LINT] and [ARG_UPDATE_BASELINE_API_LINT] */
515 baselineApiLintTestInfo: BaselineTestInfo = BaselineTestInfo(),
516 /**
517 * [ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED] and
518 * [ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED]
519 */
520 baselineCheckCompatibilityReleasedTestInfo: BaselineTestInfo = BaselineTestInfo(),
521
522 /** [ARG_ERROR_MESSAGE_API_LINT] */
523 errorMessageApiLint: String? = null,
524 /** [ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED] */
525 errorMessageCheckCompatibilityReleased: String? = null,
526
527 /**
528 * If non-null, enable API lint. If non-blank, a codebase where only new APIs not in the
529 * codebase are linted.
530 */
531 @Language("TEXT") apiLint: String? = null,
532 /** The source files to pass to the analyzer */
533 sourceFiles: Array<TestFile> = emptyArray(),
534 /** The common source files to pass to the analyzer */
535 commonSourceFiles: Array<TestFile> = emptyArray(),
536 /** [ARG_REPEAT_ERRORS_MAX] */
537 repeatErrorsMax: Int = 0
538 ) {
539 // Ensure different API clients don't interfere with each other
540 try {
541 val method = ApiLookup::class.java.getDeclaredMethod("dispose")
542 method.isAccessible = true
543 method.invoke(null)
544 } catch (ignore: Throwable) {
545 ignore.printStackTrace()
546 }
547
548 // Ensure that lint infrastructure (for UAST) knows it's dealing with a test
549 LintCliClient(LintClient.CLIENT_UNIT_TESTS)
550
551 // Verify that a test that provided kotlin code is only being run against a provider that
552 // supports kotlin code.
553 val anyKotlin =
554 sourceFiles.any { it.targetPath.endsWith(DOT_KT) } ||
555 commonSourceFiles.any { it.targetPath.endsWith(DOT_KT) }
556 if (anyKotlin && Capability.KOTLIN !in codebaseCreatorConfig.creator.capabilities) {
557 error(
558 "Provider ${codebaseCreatorConfig.providerName} does not support Kotlin; please add `@RequiresCapabilities(Capability.KOTLIN)` to the test"
559 )
560 }
561
562 val releasedApiCheck =
563 CompatibilityCheckRequest.create(
564 optionName = ARG_CHECK_COMPATIBILITY_API_RELEASED,
565 fileOrSignatureContents = checkCompatibilityApiReleased,
566 fileOrSignatureContentsList = checkCompatibilityApiReleasedList,
567 newBasename = "released-api.txt",
568 )
569 val releasedRemovedApiCheck =
570 CompatibilityCheckRequest.create(
571 optionName = ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED,
572 fileOrSignatureContents = checkCompatibilityRemovedApiReleased,
573 fileOrSignatureContentsList = checkCompatibilityRemovedApiReleasedList,
574 newBasename = "removed-released-api.txt",
575 )
576
577 val actualExpectedFail =
578 when {
579 expectedFail != null -> expectedFail
580 (releasedApiCheck.required() || releasedRemovedApiCheck.required()) &&
581 expectedIssues?.contains(": error:") == true -> {
582 "Aborting: Found compatibility problems"
583 }
584 else -> ""
585 }
586
587 // Unit test which checks that a signature file is as expected
588 val androidJar = getAndroidJar()
589
590 val project = createProject(sourceFiles + commonSourceFiles)
591
592 val sourcePathDir = File(project, "src")
593 if (!sourcePathDir.isDirectory) {
594 sourcePathDir.mkdirs()
595 }
596
597 var sourcePath = sourcePathDir.path
598 var commonSourcePath: String? = null
599
600 // Make it easy to configure a source path with more than one source root: src and src2
601 if (sourceFiles.any { it.targetPath.startsWith("src2") }) {
602 sourcePath = sourcePath + File.pathSeparator + sourcePath + "2"
603 }
604
605 fun pathUnderProject(path: String): String = File(project, path).path
606
607 if (commonSourceFiles.isNotEmpty()) {
608 // Assume common/source are placed in different folders, e.g., commonMain, androidMain
609 sourcePath =
610 pathUnderProject(sourceFiles.first().targetPath.substringBefore("src") + "src")
611 commonSourcePath =
612 pathUnderProject(
613 commonSourceFiles.first().targetPath.substringBefore("src") + "src"
614 )
615 }
616
617 val apiClassResolutionArgs =
618 arrayOf(ARG_API_CLASS_RESOLUTION, apiClassResolution.optionValue)
619
620 val sourceList =
621 if (signatureSources.isNotEmpty() || signatureSource != null) {
622 sourcePathDir.mkdirs()
623
624 // if signatureSource is set, add it to signatureSources.
625 val sources = signatureSources.toMutableList()
626 signatureSource?.let { sources.add(it) }
627
628 var num = 0
629 val args = mutableListOf<String>()
630 sources.forEach { file ->
631 val signatureFile =
632 File(project, "load-api${ if (++num == 1) "" else num.toString() }.txt")
633 signatureFile.writeSignatureText(file)
634 args.add(signatureFile.path)
635 }
636 if (!includeStrippedSuperclassWarnings) {
637 args.add(ARG_HIDE)
638 args.add("HiddenSuperclass") // Suppress warning #111
639 }
640 args.toTypedArray()
641 } else if (apiJar != null) {
642 sourcePathDir.mkdirs()
643 assert(sourceFiles.isEmpty()) {
644 "Shouldn't combine sources with API jar file loads"
645 }
646 arrayOf(apiJar.path)
647 } else {
648 (sourceFiles + commonSourceFiles)
649 .asSequence()
650 .map { pathUnderProject(it.targetPath) }
651 .toList()
652 .toTypedArray()
653 }
654
655 val classpathArgs: Array<String> =
656 if (classpath != null) {
657 val classpathString =
658 classpath
659 .map { it.createFile(project) }
660 .map { it.path }
661 .joinToString(separator = File.pathSeparator) { it }
662
663 arrayOf(ARG_CLASS_PATH, classpathString)
664 } else {
665 emptyArray()
666 }
667
668 val allReportedIssues = StringBuilder()
669 val errorSeverityReportedIssues = StringBuilder()
670 val reporterEnvironment =
671 object : ReporterEnvironment {
672 override val rootFolder = project
673
674 override fun printReport(message: String, severity: Severity) {
675 val cleanedUpMessage = cleanupString(message, rootFolder).trim()
676 if (severity == Severity.ERROR) {
677 errorSeverityReportedIssues.append(cleanedUpMessage).append('\n')
678 }
679 allReportedIssues.append(cleanedUpMessage).append('\n')
680 }
681 }
682
683 val mergeAnnotationsArgs =
684 if (mergeXmlAnnotations != null) {
685 val merged = File(project, "merged-annotations.xml")
686 merged.writeText(mergeXmlAnnotations.trimIndent())
687 arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path)
688 } else {
689 emptyArray()
690 }
691
692 val signatureAnnotationsArgs =
693 if (mergeSignatureAnnotations != null) {
694 val merged = File(project, "merged-annotations.txt")
695 merged.writeText(mergeSignatureAnnotations.trimIndent())
696 arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path)
697 } else {
698 emptyArray()
699 }
700
701 val javaStubAnnotationsArgs =
702 if (mergeJavaStubAnnotations != null) {
703 // We need to place the qualifier class into its proper package location
704 // to make the parsing machinery happy
705 val cls = ClassName(mergeJavaStubAnnotations)
706 val pkg = cls.packageName
707 val relative = pkg?.replace('.', File.separatorChar) ?: "."
708 val merged = File(project, "qualifier/$relative/${cls.className}.java")
709 merged.parentFile.mkdirs()
710 merged.writeText(mergeJavaStubAnnotations.trimIndent())
711 arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path)
712 } else {
713 emptyArray()
714 }
715
716 val inclusionAnnotationsArgs =
717 if (mergeInclusionAnnotations.isNotEmpty()) {
718 // Create each file in their own directory.
719 mergeInclusionAnnotations
720 .flatMapIndexed { i, testFile ->
721 val suffix = if (i == 0) "" else i.toString()
722 val targetDir = File(project, "inclusion$suffix")
723 targetDir.mkdirs()
724 testFile.createFile(targetDir)
725 listOf(ARG_MERGE_INCLUSION_ANNOTATIONS, targetDir.path)
726 }
727 .toTypedArray()
728 } else {
729 emptyArray()
730 }
731
732 val apiLintArgs =
733 if (apiLint != null) {
734 if (apiLint.isBlank()) {
735 arrayOf(ARG_API_LINT)
736 } else {
737 val file = File(project, "prev-api-lint.txt")
738 file.writeSignatureText(apiLint)
739 arrayOf(ARG_API_LINT, ARG_API_LINT_PREVIOUS_API, file.path)
740 }
741 } else {
742 emptyArray()
743 }
744
745 val checkCompatibilityBaseApiFile =
746 useExistingSignatureFileOrCreateNewFile(
747 project,
748 checkCompatibilityBaseApi,
749 "compatibility-base-api.txt"
750 )
751
752 val migrateNullsApiFile =
753 useExistingSignatureFileOrCreateNewFile(project, migrateNullsApi, "stable-api.txt")
754
755 val manifestFileArgs =
756 if (manifest != null) {
757 val file = File(project, "manifest.xml")
758 file.writeText(manifest.trimIndent())
759 arrayOf(ARG_MANIFEST, file.path)
760 } else {
761 emptyArray()
762 }
763
764 val migrateNullsArguments =
765 if (migrateNullsApiFile != null) {
766 arrayOf(ARG_MIGRATE_NULLNESS, migrateNullsApiFile.path)
767 } else {
768 emptyArray()
769 }
770
771 val checkCompatibilityBaseApiArguments =
772 if (checkCompatibilityBaseApiFile != null) {
773 arrayOf(ARG_CHECK_COMPATIBILITY_BASE_API, checkCompatibilityBaseApiFile.path)
774 } else {
775 emptyArray()
776 }
777
778 val quiet =
779 if (expectedOutput != null && !extraArguments.contains(ARG_VERBOSE)) {
780 // If comparing output, avoid noisy output such as the banner etc
781 arrayOf(ARG_QUIET)
782 } else {
783 emptyArray()
784 }
785
786 var proguardFile: File? = null
787 val proguardKeepArguments =
788 if (proguard != null) {
789 proguardFile = File(project, "proguard.cfg")
790 arrayOf(ARG_PROGUARD, proguardFile.path)
791 } else {
792 emptyArray()
793 }
794
795 val showAnnotationArguments =
796 if (showAnnotations.isNotEmpty() || includeSystemApiAnnotations) {
797 val args = mutableListOf<String>()
798 for (annotation in showAnnotations) {
799 args.add(ARG_SHOW_ANNOTATION)
800 args.add(annotation)
801 }
802 if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemApi")) {
803 args.add(ARG_SHOW_ANNOTATION)
804 args.add("android.annotation.SystemApi")
805 }
806 if (includeSystemApiAnnotations && !args.contains("android.annotation.TestApi")) {
807 args.add(ARG_SHOW_ANNOTATION)
808 args.add("android.annotation.TestApi")
809 }
810 args.toTypedArray()
811 } else {
812 emptyArray()
813 }
814
815 val hideAnnotationArguments =
816 if (hideAnnotations.isNotEmpty()) {
817 val args = mutableListOf<String>()
818 for (annotation in hideAnnotations) {
819 args.add(ARG_HIDE_ANNOTATION)
820 args.add(annotation)
821 }
822 args.toTypedArray()
823 } else {
824 emptyArray()
825 }
826
827 val showForStubPurposesAnnotationArguments =
828 if (showForStubPurposesAnnotations.isNotEmpty()) {
829 val args = mutableListOf<String>()
830 for (annotation in showForStubPurposesAnnotations) {
831 args.add(ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION)
832 args.add(annotation)
833 }
834 args.toTypedArray()
835 } else {
836 emptyArray()
837 }
838
839 val suppressCompatMetaAnnotationArguments =
840 if (suppressCompatibilityMetaAnnotations.isNotEmpty()) {
841 val args = mutableListOf<String>()
842 for (annotation in suppressCompatibilityMetaAnnotations) {
843 args.add(ARG_SUPPRESS_COMPATIBILITY_META_ANNOTATION)
844 args.add(annotation)
845 }
846 args.toTypedArray()
847 } else {
848 emptyArray()
849 }
850
851 val showUnannotatedArgs =
852 if (showUnannotated) {
853 arrayOf(ARG_SHOW_UNANNOTATED)
854 } else {
855 emptyArray()
856 }
857
858 var removedApiFile: File? = null
859 val removedArgs =
860 if (removedApi != null) {
861 removedApiFile = temporaryFolder.newFile("removed.txt")
862 arrayOf(ARG_REMOVED_API, removedApiFile.path)
863 } else {
864 emptyArray()
865 }
866
867 // Always pass apiArgs and generate API text file in runDriver
868 val apiFile: File = newFile("public-api.txt")
869 val apiArgs = arrayOf(ARG_API, apiFile.path)
870
871 var dexApiFile: File? = null
872 val dexApiArgs =
873 if (dexApi != null) {
874 dexApiFile = temporaryFolder.newFile("public-dex.txt")
875 arrayOf(ARG_DEX_API, dexApiFile.path)
876 } else {
877 emptyArray()
878 }
879
880 val subtractApiFile: File?
881 val subtractApiArgs =
882 if (subtractApi != null) {
883 subtractApiFile = temporaryFolder.newFile("subtract-api.txt")
884 subtractApiFile.writeSignatureText(subtractApi)
885 arrayOf(ARG_SUBTRACT_API, subtractApiFile.path)
886 } else {
887 emptyArray()
888 }
889
890 var stubsDir: File? = null
891 val stubsArgs =
892 if (stubFiles.isNotEmpty() || stubPaths != null) {
893 stubsDir = newFolder("stubs")
894 if (docStubs) {
895 arrayOf(ARG_DOC_STUBS, stubsDir.path)
896 } else {
897 arrayOf(ARG_STUBS, stubsDir.path)
898 }
899 } else {
900 emptyArray()
901 }
902
903 val applyApiLevelsXmlFile: File?
904 val applyApiLevelsXmlArgs =
905 if (applyApiLevelsXml != null) {
906 ApiLookup::class
907 .java
908 .getDeclaredMethod("dispose")
909 .apply { isAccessible = true }
910 .invoke(null)
911 applyApiLevelsXmlFile = temporaryFolder.newFile("api-versions.xml")
912 applyApiLevelsXmlFile?.writeText(applyApiLevelsXml.trimIndent())
913 arrayOf(ARG_APPLY_API_LEVELS, applyApiLevelsXmlFile.path)
914 } else {
915 emptyArray()
916 }
917
918 val baselineCheck =
919 buildBaselineCheck(
920 ARG_BASELINE,
921 ARG_UPDATE_BASELINE,
922 "baseline.txt",
923 baselineTestInfo,
924 )
925 val baselineApiLintCheck =
926 buildBaselineCheck(
927 ARG_BASELINE_API_LINT,
928 ARG_UPDATE_BASELINE_API_LINT,
929 "baseline-api-lint.txt",
930 baselineApiLintTestInfo,
931 )
932 val baselineCheckCompatibilityReleasedCheck =
933 buildBaselineCheck(
934 ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED,
935 ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED,
936 "baseline-check-released.txt",
937 baselineCheckCompatibilityReleasedTestInfo,
938 )
939
940 val importedPackageArgs = mutableListOf<String>()
941 importedPackages.forEach {
942 importedPackageArgs.add("--stub-import-packages")
943 importedPackageArgs.add(it)
944 }
945
946 val kotlinPathArgs = findKotlinStdlibPathArgs(sourceList)
947
948 val sdkFilesDir: File?
949 val sdkFilesArgs: Array<String>
950 if (
951 sdkBroadcastActions != null ||
952 sdkActivityActions != null ||
953 sdkServiceActions != null ||
954 sdkCategories != null ||
955 sdkFeatures != null ||
956 sdkWidgets != null
957 ) {
958 val dir = File(project, "sdk-files")
959 sdkFilesArgs = arrayOf(ARG_SDK_VALUES, dir.path)
960 sdkFilesDir = dir
961 } else {
962 sdkFilesArgs = emptyArray()
963 sdkFilesDir = null
964 }
965
966 val extractedAnnotationsZip: File?
967 val extractAnnotationsArgs =
968 if (extractAnnotations != null) {
969 extractedAnnotationsZip = temporaryFolder.newFile("extracted-annotations.zip")
970 arrayOf(ARG_EXTRACT_ANNOTATIONS, extractedAnnotationsZip.path)
971 } else {
972 extractedAnnotationsZip = null
973 emptyArray()
974 }
975
976 val validateNullabilityTxt: File?
977 val validateNullabilityArgs =
978 if (validateNullability != null) {
979 validateNullabilityTxt = temporaryFolder.newFile("validate-nullability.txt")
980 arrayOf(
981 ARG_NULLABILITY_WARNINGS_TXT,
982 validateNullabilityTxt.path,
983 ARG_NULLABILITY_ERRORS_NON_FATAL // for testing, report on errors instead of
984 // throwing
985 )
986 } else {
987 validateNullabilityTxt = null
988 emptyArray()
989 }
990 val validateNullabilityFromListFile: File?
991 val validateNullabilityFromListArgs =
992 if (validateNullabilityFromList != null) {
993 validateNullabilityFromListFile =
994 temporaryFolder.newFile("validate-nullability-classes.txt")
995 validateNullabilityFromListFile.writeText(validateNullabilityFromList)
996 arrayOf(ARG_VALIDATE_NULLABILITY_FROM_LIST, validateNullabilityFromListFile.path)
997 } else {
998 emptyArray()
999 }
1000
1001 val errorMessageApiLintArgs =
1002 buildOptionalArgs(errorMessageApiLint) { arrayOf(ARG_ERROR_MESSAGE_API_LINT, it) }
1003 val errorMessageCheckCompatibilityReleasedArgs =
1004 buildOptionalArgs(errorMessageCheckCompatibilityReleased) {
1005 arrayOf(ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED, it)
1006 }
1007
1008 val repeatErrorsMaxArgs =
1009 if (repeatErrorsMax > 0) {
1010 arrayOf(ARG_REPEAT_ERRORS_MAX, repeatErrorsMax.toString())
1011 } else {
1012 emptyArray()
1013 }
1014
1015 // Run optional additional setup steps on the project directory
1016 projectSetup?.invoke(project)
1017
1018 // Make sure that the options is initialized. Just in case access was disallowed by another
1019 // test.
1020 options = Options()
1021
1022 val args =
1023 arrayOf(
1024 ARG_NO_COLOR,
1025
1026 // Tell metalava where to store temp folder: place them under the
1027 // test root folder such that we clean up the output strings referencing
1028 // paths to the temp folder
1029 "--temp-folder",
1030 newFolder("temp").path,
1031
1032 // Annotation generation temporarily turned off by default while integrating with
1033 // SDK builds; tests need these
1034 ARG_INCLUDE_ANNOTATIONS,
1035 ARG_SOURCE_PATH,
1036 sourcePath,
1037 ARG_CLASS_PATH,
1038 androidJar.path,
1039 *classpathArgs,
1040 *kotlinPathArgs,
1041 *removedArgs,
1042 *apiArgs,
1043 *dexApiArgs,
1044 *subtractApiArgs,
1045 *stubsArgs,
1046 *quiet,
1047 *mergeAnnotationsArgs,
1048 *signatureAnnotationsArgs,
1049 *javaStubAnnotationsArgs,
1050 *inclusionAnnotationsArgs,
1051 *migrateNullsArguments,
1052 *releasedApiCheck.arguments(project),
1053 *checkCompatibilityBaseApiArguments,
1054 *releasedRemovedApiCheck.arguments(project),
1055 *proguardKeepArguments,
1056 *manifestFileArgs,
1057 *applyApiLevelsXmlArgs,
1058 *baselineCheck.args,
1059 *baselineApiLintCheck.args,
1060 *baselineCheckCompatibilityReleasedCheck.args,
1061 *showAnnotationArguments,
1062 *hideAnnotationArguments,
1063 *suppressCompatMetaAnnotationArguments,
1064 *showForStubPurposesAnnotationArguments,
1065 *showUnannotatedArgs,
1066 *sdkFilesArgs,
1067 *importedPackageArgs.toTypedArray(),
1068 *extractAnnotationsArgs,
1069 *validateNullabilityArgs,
1070 *validateNullabilityFromListArgs,
1071 format.outputFlags(),
1072 *apiClassResolutionArgs,
1073 *sourceList,
1074 *extraArguments,
1075 *errorMessageApiLintArgs,
1076 *errorMessageCheckCompatibilityReleasedArgs,
1077 *repeatErrorsMaxArgs,
1078 // Must always be last as this can consume a following argument, breaking the test.
1079 *apiLintArgs,
1080 ) +
1081 buildList {
1082 if (commonSourcePath != null) {
1083 add(ARG_COMMON_SOURCE_PATH)
1084 add(commonSourcePath)
1085 }
1086 }
1087 .toTypedArray()
1088
1089 val testEnvironment =
1090 TestEnvironment(
1091 skipEmitPackages = skipEmitPackages,
1092 sourceModelProvider = codebaseCreatorConfig.creator,
1093 modelOptions = codebaseCreatorConfig.modelOptions,
1094 )
1095
1096 val actualOutput =
1097 runDriver(
1098 args = args,
1099 expectedFail = actualExpectedFail,
1100 reporterEnvironment = reporterEnvironment,
1101 testEnvironment = testEnvironment,
1102 )
1103
1104 if (expectedIssues != null || allReportedIssues.toString() != "") {
1105 assertEquals(
1106 "expectedIssues does not match actual issues reported",
1107 expectedIssues?.trimIndent()?.trim() ?: "",
1108 allReportedIssues.toString().trim(),
1109 )
1110 }
1111 if (errorSeverityExpectedIssues != null) {
1112 assertEquals(
1113 "errorSeverityExpectedIssues does not match actual issues reported",
1114 errorSeverityExpectedIssues.trimIndent().trim(),
1115 errorSeverityReportedIssues.toString().trim(),
1116 )
1117 }
1118
1119 if (expectedOutput != null) {
1120 assertEquals(
1121 "expectedOutput does not match actual output",
1122 expectedOutput.trimIndent().trim(),
1123 actualOutput.trim()
1124 )
1125 }
1126
1127 if (api != null) {
1128 assertTrue(
1129 "${apiFile.path} does not exist even though --api was used",
1130 apiFile.exists()
1131 )
1132 assertSignatureFilesMatch(api, apiFile.readText(), expectedFormat = format)
1133 // Make sure we can read back the files we write
1134 ApiFile.parseApi(SignatureFile.fromFile(apiFile), options.annotationManager)
1135 }
1136
1137 baselineCheck.apply()
1138 baselineApiLintCheck.apply()
1139 baselineCheckCompatibilityReleasedCheck.apply()
1140
1141 if (dexApi != null && dexApiFile != null) {
1142 assertTrue(
1143 "${dexApiFile.path} does not exist even though --dex-api was used",
1144 dexApiFile.exists()
1145 )
1146 val actualText = readFile(dexApiFile)
1147 assertEquals(
1148 stripComments(dexApi, DOT_TXT, stripLineComments = false).trimIndent(),
1149 actualText
1150 )
1151 }
1152
1153 if (removedApi != null && removedApiFile != null) {
1154 assertTrue(
1155 "${removedApiFile.path} does not exist even though --removed-api was used",
1156 removedApiFile.exists()
1157 )
1158 assertSignatureFilesMatch(
1159 removedApi,
1160 removedApiFile.readText(),
1161 expectedFormat = format
1162 )
1163 // Make sure we can read back the files we write
1164 ApiFile.parseApi(SignatureFile.fromFile(removedApiFile), options.annotationManager)
1165 }
1166
1167 if (proguard != null && proguardFile != null) {
1168 assertTrue(
1169 "${proguardFile.path} does not exist even though --proguard was used",
1170 proguardFile.exists()
1171 )
1172 val expectedProguard = readFile(proguardFile)
1173 assertEquals(
1174 stripComments(proguard, DOT_TXT, stripLineComments = false).trimIndent(),
1175 expectedProguard
1176 )
1177 }
1178
1179 if (sdkBroadcastActions != null) {
1180 val actual = readFile(File(sdkFilesDir, "broadcast_actions.txt"))
1181 assertEquals(sdkBroadcastActions.trimIndent().trim(), actual.trim())
1182 }
1183
1184 if (sdkActivityActions != null) {
1185 val actual = readFile(File(sdkFilesDir, "activity_actions.txt"))
1186 assertEquals(sdkActivityActions.trimIndent().trim(), actual.trim())
1187 }
1188
1189 if (sdkServiceActions != null) {
1190 val actual = readFile(File(sdkFilesDir, "service_actions.txt"))
1191 assertEquals(sdkServiceActions.trimIndent().trim(), actual.trim())
1192 }
1193
1194 if (sdkCategories != null) {
1195 val actual = readFile(File(sdkFilesDir, "categories.txt"))
1196 assertEquals(sdkCategories.trimIndent().trim(), actual.trim())
1197 }
1198
1199 if (sdkFeatures != null) {
1200 val actual = readFile(File(sdkFilesDir, "features.txt"))
1201 assertEquals(sdkFeatures.trimIndent().trim(), actual.trim())
1202 }
1203
1204 if (sdkWidgets != null) {
1205 val actual = readFile(File(sdkFilesDir, "widgets.txt"))
1206 assertEquals(sdkWidgets.trimIndent().trim(), actual.trim())
1207 }
1208
1209 if (extractAnnotations != null && extractedAnnotationsZip != null) {
1210 assertTrue(
1211 "Using --extract-annotations but $extractedAnnotationsZip was not created",
1212 extractedAnnotationsZip.isFile
1213 )
1214 for ((pkg, xml) in extractAnnotations) {
1215 assertPackageXml(pkg, extractedAnnotationsZip, xml)
1216 }
1217 }
1218
1219 if (validateNullabilityTxt != null) {
1220 assertTrue(
1221 "Using $ARG_NULLABILITY_WARNINGS_TXT but $validateNullabilityTxt was not created",
1222 validateNullabilityTxt.isFile
1223 )
1224 val actualReport = validateNullabilityTxt.readLines().map(String::trim).toSet()
1225 assertEquals(validateNullability, actualReport)
1226 }
1227
1228 val stubsCreated =
1229 stubsDir
1230 ?.walkTopDown()
1231 ?.filter { it.isFile }
1232 ?.map { it.relativeTo(stubsDir).path }
1233 ?.sorted()
1234 ?.joinToString("\n")
1235
1236 if (stubPaths != null) {
1237 assertEquals("stub paths", stubPaths.joinToString("\n"), stubsCreated)
1238 }
1239
1240 if (stubFiles.isNotEmpty()) {
1241 for (expected in stubFiles) {
1242 val actual = File(stubsDir!!, expected.targetRelativePath)
1243 if (!actual.exists()) {
1244 throw FileNotFoundException(
1245 "Could not find a generated stub for ${expected.targetRelativePath}. " +
1246 "Found these files: \n${stubsCreated!!.prependIndent(" ")}"
1247 )
1248 }
1249 val actualContents = readFile(actual)
1250 val stubSource = if (sourceFiles.isEmpty()) "text" else "source"
1251 val message =
1252 "Generated from-$stubSource stub contents does not match expected contents"
1253 assertEquals(message, expected.contents, actualContents)
1254 }
1255 }
1256
1257 if (checkCompilation && stubsDir != null) {
1258 val generated =
1259 SourceSet.createFromSourcePath(options.reporter, listOf(stubsDir))
1260 .sources
1261 .asSequence()
1262 .map { it.path }
1263 .toList()
1264 .toTypedArray()
1265
1266 // Also need to include on the compile path annotation classes referenced in the stubs
1267 val extraAnnotationsDir = File("../stub-annotations/src/main/java")
1268 if (!extraAnnotationsDir.isDirectory) {
1269 fail(
1270 "Couldn't find $extraAnnotationsDir: Is the pwd set to the root of the metalava source code?"
1271 )
1272 fail(
1273 "Couldn't find $extraAnnotationsDir: Is the pwd set to the root of an Android source tree?"
1274 )
1275 }
1276 val extraAnnotations =
1277 SourceSet.createFromSourcePath(options.reporter, listOf(extraAnnotationsDir))
1278 .sources
1279 .asSequence()
1280 .map { it.path }
1281 .toList()
1282 .toTypedArray()
1283
1284 if (
1285 !runCommand(
1286 "${getJdkPath()}/bin/javac",
1287 arrayOf("-d", project.path, *generated, *extraAnnotations)
1288 )
1289 ) {
1290 fail("Couldn't compile stub file -- compilation problems")
1291 return
1292 }
1293 }
1294 }
1295
1296 /** Encapsulates information needed to request a compatibility check. */
1297 private class CompatibilityCheckRequest
1298 private constructor(
1299 private val optionName: String,
1300 private val fileOrSignatureContentsList: List<String>,
1301 private val newBasename: String,
1302 ) {
1303 companion object {
1304 fun create(
1305 optionName: String,
1306 fileOrSignatureContents: String?,
1307 fileOrSignatureContentsList: List<String>,
1308 newBasename: String,
1309 ): CompatibilityCheckRequest =
1310 CompatibilityCheckRequest(
1311 optionName = optionName,
1312 fileOrSignatureContentsList =
1313 listOfNotNull(fileOrSignatureContents) + fileOrSignatureContentsList,
1314 newBasename = newBasename,
1315 )
1316 }
1317
1318 /** Indicates whether the compatibility check is required. */
1319 fun required(): Boolean = fileOrSignatureContentsList.isNotEmpty()
1320
1321 /** The arguments to pass to Metalava. */
1322 fun arguments(project: File): Array<out String> {
1323 if (fileOrSignatureContentsList.isEmpty()) return emptyArray()
1324
1325 val paths =
1326 fileOrSignatureContentsList.mapNotNull {
1327 useExistingSignatureFileOrCreateNewFile(project, it, newBasename)?.path
1328 }
1329
1330 // For each path in the list generate an option with the path as the value.
1331 return paths.flatMap { listOf(optionName, it) }.toTypedArray()
1332 }
1333 }
1334
1335 /** Checks that the given zip annotations file contains the given XML package contents */
1336 private fun assertPackageXml(pkg: String, output: File, @Language("XML") expected: String) {
1337 assertNotNull(output)
1338 assertTrue(output.exists())
1339 val url =
1340 URL(
1341 "jar:" +
1342 SdkUtils.fileToUrlString(output) +
1343 "!/" +
1344 pkg.replace('.', '/') +
1345 "/annotations.xml"
1346 )
1347 val stream = url.openStream()
1348 try {
1349 val bytes = stream.readBytes()
1350 assertNotNull(bytes)
1351 val xml = String(bytes, UTF_8).replace("\r\n", "\n")
1352 assertEquals(expected.trimIndent().trim(), xml.trimIndent().trim())
1353 } finally {
1354 Closeables.closeQuietly(stream)
1355 }
1356 }
1357
1358 private fun runCommand(executable: String, args: Array<String>): Boolean {
1359 try {
1360 val logger = StdLogger(StdLogger.Level.ERROR)
1361 val processExecutor = DefaultProcessExecutor(logger)
1362 val processInfo =
1363 ProcessInfoBuilder().setExecutable(executable).addArgs(args).createProcess()
1364
1365 val processOutputHandler = LoggedProcessOutputHandler(logger)
1366 val result = processExecutor.execute(processInfo, processOutputHandler)
1367
1368 result.rethrowFailure().assertNormalExitValue()
1369 } catch (e: ProcessException) {
1370 fail(
1371 "Failed to run $executable (${e.message}): not verifying this API on the old doclava engine"
1372 )
1373 return false
1374 }
1375 return true
1376 }
1377
1378 companion object {
1379 @JvmStatic
1380 protected fun readFile(file: File): String {
1381 var apiLines: List<String> = file.readLines()
1382 apiLines = apiLines.filter { it.isNotBlank() }
1383 return apiLines.joinToString(separator = "\n") { it }.trim()
1384 }
1385
1386 /**
1387 * Get an optional signature API [File] from either a file path or its contents.
1388 *
1389 * @param project the directory in which to create a new file.
1390 * @param fileOrFileContents either a path to an existing file or the contents of the
1391 * signature file. If the latter the contents will be trimmed, updated to add a
1392 * [FileFormat.V2] header if needed and written to a new file created within [project].
1393 * @param newBasename the basename of a new file created.
1394 */
1395 private fun useExistingSignatureFileOrCreateNewFile(
1396 project: File,
1397 fileOrFileContents: String?,
1398 newBasename: String
1399 ) =
1400 fileOrFileContents?.let {
1401 val maybeFile = File(fileOrFileContents)
1402 if (maybeFile.isFile) {
1403 maybeFile
1404 } else {
1405 val file = findNonExistentFile(project, newBasename)
1406 file.writeSignatureText(fileOrFileContents)
1407 file
1408 }
1409 }
1410
1411 private fun findNonExistentFile(project: File, basename: String): File {
1412 // Split the basename into the name without any extension an optional extension.
1413 val index = basename.lastIndexOf('.')
1414 val (nameWithoutExtension, optionalExtension) =
1415 if (index == -1) {
1416 Pair(basename, "")
1417 } else {
1418 Pair(basename.substring(0, index), basename.substring(index))
1419 }
1420
1421 var count = 0
1422 do {
1423 val name =
1424 if (count == 0) basename else "$nameWithoutExtension-$count$optionalExtension"
1425 count += 1
1426
1427 val file = File(project, name)
1428 if (!file.isFile) return file
1429 } while (true)
1430 }
1431 }
1432 }
1433
FileFormatnull1434 private fun FileFormat.outputFlags(): String {
1435 return "$ARG_FORMAT=${specifier()}"
1436 }
1437
writeSignatureTextnull1438 private fun File.writeSignatureText(contents: String) {
1439 writeText(prepareSignatureFileForTest(contents, FileFormat.V2))
1440 }
1441
1442 /** Returns the paths returned by [findKotlinStdlibPaths] as metalava args expected by Options. */
findKotlinStdlibPathArgsnull1443 fun findKotlinStdlibPathArgs(sources: Array<String>): Array<String> {
1444 val kotlinPaths = findKotlinStdlibPaths(sources)
1445
1446 return if (kotlinPaths.isEmpty()) emptyArray()
1447 else
1448 arrayOf(
1449 ARG_CLASS_PATH,
1450 kotlinPaths.joinToString(separator = File.pathSeparator) { it.path }
1451 )
1452 }
1453
1454 val intRangeAnnotationSource: TestFile =
1455 java(
1456 """
1457 package android.annotation;
1458 import java.lang.annotation.*;
1459 import static java.lang.annotation.ElementType.*;
1460 import static java.lang.annotation.RetentionPolicy.SOURCE;
1461 @Retention(SOURCE)
1462 @Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
1463 public @interface IntRange {
1464 long from() default Long.MIN_VALUE;
1465 long to() default Long.MAX_VALUE;
1466 }
1467 """
1468 )
1469 .indented()
1470
1471 val intDefAnnotationSource: TestFile =
1472 java(
1473 """
1474 package android.annotation;
1475 import java.lang.annotation.Retention;
1476 import java.lang.annotation.RetentionPolicy;
1477 import java.lang.annotation.Target;
1478 import static java.lang.annotation.ElementType.*;
1479 import static java.lang.annotation.RetentionPolicy.SOURCE;
1480 @Retention(SOURCE)
1481 @Target({ANNOTATION_TYPE})
1482 public @interface IntDef {
1483 int[] value() default {};
1484 boolean flag() default false;
1485 }
1486 """
1487 )
1488 .indented()
1489
1490 val longDefAnnotationSource: TestFile =
1491 java(
1492 """
1493 package android.annotation;
1494 import java.lang.annotation.Retention;
1495 import java.lang.annotation.RetentionPolicy;
1496 import java.lang.annotation.Target;
1497 import static java.lang.annotation.ElementType.*;
1498 import static java.lang.annotation.RetentionPolicy.SOURCE;
1499 @Retention(SOURCE)
1500 @Target({ANNOTATION_TYPE})
1501 public @interface LongDef {
1502 long[] value() default {};
1503 boolean flag() default false;
1504 }
1505 """
1506 )
1507 .indented()
1508
1509 val nonNullSource = KnownSourceFiles.nonNullSource
1510 val nullableSource = KnownSourceFiles.nullableSource
1511 val libcoreNonNullSource = KnownSourceFiles.libcoreNonNullSource
1512 val libcoreNullableSource = KnownSourceFiles.libcoreNullableSource
1513
1514 val libcoreNullFromTypeParamSource: TestFile =
1515 java(
1516 """
1517 package libcore.util;
1518 import static java.lang.annotation.ElementType.*;
1519 import static java.lang.annotation.RetentionPolicy.SOURCE;
1520 import java.lang.annotation.*;
1521 @Documented
1522 @Retention(SOURCE)
1523 @Target({TYPE_USE})
1524 public @interface NullFromTypeParam {
1525 }
1526 """
1527 )
1528 .indented()
1529
1530 val requiresPermissionSource: TestFile =
1531 java(
1532 """
1533 package android.annotation;
1534 import java.lang.annotation.*;
1535 import static java.lang.annotation.ElementType.*;
1536 import static java.lang.annotation.RetentionPolicy.SOURCE;
1537 @Retention(SOURCE)
1538 @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
1539 public @interface RequiresPermission {
1540 String value() default "";
1541 String[] allOf() default {};
1542 String[] anyOf() default {};
1543 boolean conditional() default false;
1544 @Target({FIELD, METHOD, PARAMETER})
1545 @interface Read {
1546 RequiresPermission value() default @RequiresPermission;
1547 }
1548 @Target({FIELD, METHOD, PARAMETER})
1549 @interface Write {
1550 RequiresPermission value() default @RequiresPermission;
1551 }
1552 }
1553 """
1554 )
1555 .indented()
1556
1557 val requiresFeatureSource: TestFile =
1558 java(
1559 """
1560 package android.annotation;
1561 import java.lang.annotation.*;
1562 import static java.lang.annotation.ElementType.*;
1563 import static java.lang.annotation.RetentionPolicy.SOURCE;
1564 @Retention(SOURCE)
1565 @Target({TYPE,FIELD,METHOD,CONSTRUCTOR})
1566 public @interface RequiresFeature {
1567 String value();
1568 }
1569 """
1570 )
1571 .indented()
1572
1573 val requiresApiSource: TestFile =
1574 java(
1575 """
1576 package androidx.annotation;
1577 import java.lang.annotation.*;
1578 import static java.lang.annotation.ElementType.*;
1579 import static java.lang.annotation.RetentionPolicy.SOURCE;
1580 @Retention(SOURCE)
1581 @Target({TYPE,FIELD,METHOD,CONSTRUCTOR})
1582 public @interface RequiresApi {
1583 int value() default 1;
1584 int api() default 1;
1585 }
1586 """
1587 )
1588 .indented()
1589
1590 val sdkConstantSource: TestFile =
1591 java(
1592 """
1593 package android.annotation;
1594 import java.lang.annotation.*;
1595 @Target({ ElementType.FIELD })
1596 @Retention(RetentionPolicy.SOURCE)
1597 public @interface SdkConstant {
1598 enum SdkConstantType {
1599 ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE
1600 }
1601 SdkConstantType value();
1602 }
1603 """
1604 )
1605 .indented()
1606
1607 val broadcastBehaviorSource: TestFile =
1608 java(
1609 """
1610 package android.annotation;
1611 import java.lang.annotation.*;
1612 /** @hide */
1613 @Target({ ElementType.FIELD })
1614 @Retention(RetentionPolicy.SOURCE)
1615 public @interface BroadcastBehavior {
1616 boolean explicitOnly() default false;
1617 boolean registeredOnly() default false;
1618 boolean includeBackground() default false;
1619 boolean protectedBroadcast() default false;
1620 }
1621 """
1622 )
1623 .indented()
1624
1625 val androidxNonNullSource: TestFile =
1626 java(
1627 """
1628 package androidx.annotation;
1629 import java.lang.annotation.*;
1630 import static java.lang.annotation.ElementType.*;
1631 import static java.lang.annotation.RetentionPolicy.SOURCE;
1632 @SuppressWarnings("WeakerAccess")
1633 @Retention(SOURCE)
1634 @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, TYPE_USE, ANNOTATION_TYPE, PACKAGE})
1635 public @interface NonNull {
1636 }
1637 """
1638 )
1639 .indented()
1640
1641 val androidxNullableSource: TestFile =
1642 java(
1643 """
1644 package androidx.annotation;
1645 import java.lang.annotation.*;
1646 import static java.lang.annotation.ElementType.*;
1647 import static java.lang.annotation.RetentionPolicy.SOURCE;
1648 @SuppressWarnings("WeakerAccess")
1649 @Retention(SOURCE)
1650 @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, TYPE_USE, ANNOTATION_TYPE, PACKAGE})
1651 public @interface Nullable {
1652 }
1653 """
1654 )
1655 .indented()
1656
1657 val recentlyNonNullSource: TestFile =
1658 java(
1659 """
1660 package androidx.annotation;
1661 import java.lang.annotation.*;
1662 import static java.lang.annotation.ElementType.*;
1663 import static java.lang.annotation.RetentionPolicy.SOURCE;
1664 @SuppressWarnings("WeakerAccess")
1665 @Retention(SOURCE)
1666 @Target({METHOD, PARAMETER, FIELD, TYPE_USE})
1667 public @interface RecentlyNonNull {
1668 }
1669 """
1670 )
1671 .indented()
1672
1673 val recentlyNullableSource: TestFile =
1674 java(
1675 """
1676 package androidx.annotation;
1677 import java.lang.annotation.*;
1678 import static java.lang.annotation.ElementType.*;
1679 import static java.lang.annotation.RetentionPolicy.SOURCE;
1680 @SuppressWarnings("WeakerAccess")
1681 @Retention(SOURCE)
1682 @Target({METHOD, PARAMETER, FIELD, TYPE_USE})
1683 public @interface RecentlyNullable {
1684 }
1685 """
1686 )
1687 .indented()
1688
1689 val androidxIntRangeSource: TestFile =
1690 java(
1691 """
1692 package androidx.annotation;
1693 import java.lang.annotation.*;
1694 import static java.lang.annotation.ElementType.*;
1695 import static java.lang.annotation.RetentionPolicy.SOURCE;
1696 @Retention(CLASS)
1697 @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE})
1698 public @interface IntRange {
1699 long from() default Long.MIN_VALUE;
1700 long to() default Long.MAX_VALUE;
1701 }
1702 """
1703 )
1704 .indented()
1705
1706 val supportParameterName = KnownSourceFiles.supportParameterName
1707
1708 val supportDefaultValue: TestFile =
1709 java(
1710 """
1711 package androidx.annotation;
1712 import java.lang.annotation.*;
1713 import static java.lang.annotation.ElementType.*;
1714 import static java.lang.annotation.RetentionPolicy.SOURCE;
1715 @SuppressWarnings("WeakerAccess")
1716 @Retention(SOURCE)
1717 @Target({METHOD, PARAMETER, FIELD})
1718 public @interface DefaultValue {
1719 String value();
1720 }
1721 """
1722 )
1723 .indented()
1724
1725 val uiThreadSource: TestFile =
1726 java(
1727 """
1728 package androidx.annotation;
1729 import java.lang.annotation.*;
1730 import static java.lang.annotation.ElementType.*;
1731 import static java.lang.annotation.RetentionPolicy.SOURCE;
1732 /**
1733 * Denotes that the annotated method or constructor should only be called on the
1734 * UI thread. If the annotated element is a class, then all methods in the class
1735 * should be called on the UI thread.
1736 * @memberDoc This method must be called on the thread that originally created
1737 * this UI element. This is typically the main thread of your app.
1738 * @classDoc Methods in this class must be called on the thread that originally created
1739 * this UI element, unless otherwise noted. This is typically the
1740 * main thread of your app. * @hide
1741 */
1742 @SuppressWarnings({"WeakerAccess", "JavaDoc"})
1743 @Retention(SOURCE)
1744 @Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
1745 public @interface UiThread {
1746 }
1747 """
1748 )
1749 .indented()
1750
1751 val workerThreadSource: TestFile =
1752 java(
1753 """
1754 package androidx.annotation;
1755 import java.lang.annotation.*;
1756 import static java.lang.annotation.ElementType.*;
1757 import static java.lang.annotation.RetentionPolicy.SOURCE;
1758 /**
1759 * @memberDoc This method may take several seconds to complete, so it should
1760 * only be called from a worker thread.
1761 * @classDoc Methods in this class may take several seconds to complete, so it should
1762 * only be called from a worker thread unless otherwise noted.
1763 * @hide
1764 */
1765 @SuppressWarnings({"WeakerAccess", "JavaDoc"})
1766 @Retention(SOURCE)
1767 @Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
1768 public @interface WorkerThread {
1769 }
1770 """
1771 )
1772 .indented()
1773
1774 val suppressLintSource: TestFile =
1775 java(
1776 """
1777 package android.annotation;
1778
1779 import static java.lang.annotation.ElementType.*;
1780 import java.lang.annotation.*;
1781 @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
1782 @Retention(RetentionPolicy.CLASS)
1783 public @interface SuppressLint {
1784 String[] value();
1785 }
1786 """
1787 )
1788 .indented()
1789
1790 val systemServiceSource: TestFile =
1791 java(
1792 """
1793 package android.annotation;
1794 import static java.lang.annotation.ElementType.TYPE;
1795 import static java.lang.annotation.RetentionPolicy.SOURCE;
1796 import java.lang.annotation.*;
1797 @Retention(SOURCE)
1798 @Target(TYPE)
1799 public @interface SystemService {
1800 String value();
1801 }
1802 """
1803 )
1804 .indented()
1805
1806 val systemApiSource: TestFile =
1807 java(
1808 """
1809 package android.annotation;
1810 import static java.lang.annotation.ElementType.*;
1811 import java.lang.annotation.*;
1812 @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
1813 @Retention(RetentionPolicy.SOURCE)
1814 public @interface SystemApi {
1815 enum Client {
1816 /**
1817 * Specifies that the intended clients of a SystemApi are privileged apps.
1818 * This is the default value for {@link #client}.
1819 */
1820 PRIVILEGED_APPS,
1821
1822 /**
1823 * Specifies that the intended clients of a SystemApi are used by classes in
1824 * <pre>BOOTCLASSPATH</pre> in mainline modules. Mainline modules can also expose
1825 * this type of system APIs too when they're used only by the non-updatable
1826 * platform code.
1827 */
1828 MODULE_LIBRARIES,
1829
1830 /**
1831 * Specifies that the system API is available only in the system server process.
1832 * Use this to expose APIs from code loaded by the system server process <em>but</em>
1833 * not in <pre>BOOTCLASSPATH</pre>.
1834 */
1835 SYSTEM_SERVER
1836 }
1837
1838 /**
1839 * The intended client of this SystemAPI.
1840 */
1841 Client client() default android.annotation.SystemApi.Client.PRIVILEGED_APPS;
1842 }
1843 """
1844 )
1845 .indented()
1846
1847 val testApiSource: TestFile =
1848 java(
1849 """
1850 package android.annotation;
1851 import static java.lang.annotation.ElementType.*;
1852 import java.lang.annotation.*;
1853 @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
1854 @Retention(RetentionPolicy.SOURCE)
1855 public @interface TestApi {
1856 }
1857 """
1858 )
1859 .indented()
1860
1861 val widgetSource: TestFile =
1862 java(
1863 """
1864 package android.annotation;
1865 import java.lang.annotation.*;
1866 @Target({ ElementType.TYPE })
1867 @Retention(RetentionPolicy.SOURCE)
1868 public @interface Widget {
1869 }
1870 """
1871 )
1872 .indented()
1873
1874 val restrictToSource: TestFile =
1875 kotlin(
1876 """
1877 package androidx.annotation
1878
1879 import androidx.annotation.RestrictTo.Scope
1880 import java.lang.annotation.ElementType.*
1881
1882 @MustBeDocumented
1883 @Retention(AnnotationRetention.BINARY)
1884 @Target(
1885 AnnotationTarget.ANNOTATION_CLASS,
1886 AnnotationTarget.CLASS,
1887 AnnotationTarget.FUNCTION,
1888 AnnotationTarget.PROPERTY_GETTER,
1889 AnnotationTarget.PROPERTY_SETTER,
1890 AnnotationTarget.CONSTRUCTOR,
1891 AnnotationTarget.FIELD,
1892 AnnotationTarget.FILE
1893 )
1894 // Needed due to Kotlin's lack of PACKAGE annotation target
1895 // https://youtrack.jetbrains.com/issue/KT-45921
1896 @Suppress("DEPRECATED_JAVA_ANNOTATION")
1897 @java.lang.annotation.Target(ANNOTATION_TYPE, TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE)
1898 annotation class RestrictTo(vararg val value: Scope) {
1899 enum class Scope {
1900 LIBRARY,
1901 LIBRARY_GROUP,
1902 LIBRARY_GROUP_PREFIX,
1903 @Deprecated("Use LIBRARY_GROUP_PREFIX instead.")
1904 GROUP_ID,
1905 TESTS,
1906 SUBCLASSES,
1907 }
1908 }
1909 """
1910 )
1911 .indented()
1912
1913 val visibleForTestingSource: TestFile =
1914 java(
1915 """
1916 package androidx.annotation;
1917 import static java.lang.annotation.RetentionPolicy.CLASS;
1918 import java.lang.annotation.Retention;
1919 @Retention(CLASS)
1920 @SuppressWarnings("WeakerAccess")
1921 public @interface VisibleForTesting {
1922 int otherwise() default PRIVATE;
1923 int PRIVATE = 2;
1924 int PACKAGE_PRIVATE = 3;
1925 int PROTECTED = 4;
1926 int NONE = 5;
1927 }
1928 """
1929 )
1930 .indented()
1931
1932 val columnSource: TestFile =
1933 java(
1934 """
1935 package android.provider;
1936
1937 import static java.lang.annotation.ElementType.FIELD;
1938 import static java.lang.annotation.RetentionPolicy.RUNTIME;
1939
1940 import android.content.ContentProvider;
1941 import android.content.ContentValues;
1942 import android.database.Cursor;
1943
1944 import java.lang.annotation.Documented;
1945 import java.lang.annotation.Retention;
1946 import java.lang.annotation.Target;
1947
1948 @Documented
1949 @Retention(RUNTIME)
1950 @Target({FIELD})
1951 public @interface Column {
1952 int value();
1953 boolean readOnly() default false;
1954 }
1955 """
1956 )
1957 .indented()
1958
1959 val flaggedApiSource: TestFile =
1960 java(
1961 """
1962 package android.annotation;
1963
1964 import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
1965 import static java.lang.annotation.ElementType.CONSTRUCTOR;
1966 import static java.lang.annotation.ElementType.FIELD;
1967 import static java.lang.annotation.ElementType.METHOD;
1968 import static java.lang.annotation.ElementType.TYPE;
1969
1970 import java.lang.annotation.Retention;
1971 import java.lang.annotation.RetentionPolicy;
1972 import java.lang.annotation.Target;
1973
1974 /** @hide */
1975 @Target({TYPE, METHOD, CONSTRUCTOR, FIELD, ANNOTATION_TYPE})
1976 @Retention(RetentionPolicy.SOURCE)
1977 public @interface FlaggedApi {
1978 String value();
1979 }
1980 """
1981 )
1982 .indented()
1983
1984 val publishedApiSource: TestFile =
1985 kotlin(
1986 """
1987 /**
1988 * When applied to a class or a member with internal visibility allows to use it from public inline functions and
1989 * makes it effectively public.
1990 *
1991 * Public inline functions cannot use non-public API, since if they are inlined, those non-public API references
1992 * would violate access restrictions at a call site (https://kotlinlang.org/docs/reference/inline-functions.html#public-inline-restrictions).
1993 *
1994 * To overcome this restriction an `internal` declaration can be annotated with the `@PublishedApi` annotation:
1995 * - this allows to call that declaration from public inline functions;
1996 * - the declaration becomes effectively public, and this should be considered with respect to binary compatibility maintaining.
1997 */
1998 @Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
1999 @Retention(AnnotationRetention.BINARY)
2000 @MustBeDocumented
2001 @SinceKotlin("1.1")
2002 annotation class PublishedApi
2003 """
2004 )
2005 .indented()
2006
2007 val deprecatedForSdkSource: TestFile =
2008 java(
2009 """
2010 package android.annotation;
2011 import static java.lang.annotation.RetentionPolicy.SOURCE;
2012 import java.lang.annotation.Retention;
2013 /** @hide */
2014 @Retention(SOURCE)
2015 @SuppressWarnings("WeakerAccess")
2016 public @interface DeprecatedForSdk {
2017 String value();
2018 Class<?>[] allowIn() default {};
2019 }
2020 """
2021 )
2022 .indented()
2023