diff --git a/.idea/dictionaries/metalava.xml b/.idea/dictionaries/metalava.xml index ce2b5a0cacf02e4b22dda205842cda134e058b01..e458e6aff1363336df5f2369c04ab0362bb67b3f 100644 --- a/.idea/dictionaries/metalava.xml +++ b/.idea/dictionaries/metalava.xml @@ -22,6 +22,7 @@ <w>doclet</w> <w>docletpath</w> <w>doconly</w> + <w>doesnt</w> <w>dokka</w> <w>droiddoc</w> <w>federationapi</w> @@ -60,6 +61,7 @@ <w>nullable</w> <w>nullness</w> <w>offlinemode</w> + <w>parcelable</w> <w>parsecomments</w> <w>prebuilts</w> <w>proguard</w> diff --git a/API-LINT.md b/API-LINT.md index f0ab779e205e235b9b8046637c3185490d6aeeeb..76b2d09277dda64a60a6685a5e4aa7d79a887c41 100644 --- a/API-LINT.md +++ b/API-LINT.md @@ -66,6 +66,22 @@ diff to make sure you're really only marking the issues you intended to include. baseline.txt +## Copying File Manually + +In the near future the build system will not allow source files to be modified +by the build. At that point you'll need to manually copy in the file instead. +During the build, before failing, metalava will emit a message like this: + + ... + metalava wrote updated baseline to out/something/baseline.txt + ... + +At that point you can copy the file to where you need: + +```sh +$ cp out/something/baseline.txt frameworks/base/api/baseline.txt +``` + ## Existing Issues You can view the exact set of existing issues (current APIs that get flagged by @@ -146,4 +162,4 @@ here's the existing distribution as of early 2019: -------------------------------------------------- 4902 -(This is generated when metalava is invoked with both --verbose and --update-baseline.) \ No newline at end of file +(This is generated when metalava is invoked with both --verbose and --update-baseline.) diff --git a/FORMAT.md b/FORMAT.md index efe73b95cfde90c21e568fbe94b396bc5fc54ec7..d27c867deb8083b9f67cf6f9efd496b7ca3f39d2 100644 --- a/FORMAT.md +++ b/FORMAT.md @@ -480,6 +480,20 @@ java.lang.reflect.Method will **not** be shortened to reflect.Method. In v3, "type use annotations" are supported which means annotations can appear within types. +### Skipping some signatures + +If a method overrides another method, and the signatures are the same, the +overriding method is left out of the signature file. This basically compares the +modifiers, ignoring some that are not API significant (such as "native"). Note +also that some modifiers are implicit; for example, if a method is implementing +a method from an interface, the interface method is implicitly abstract, so the +implementation will be included in the signature file. + +In v2, we take this one step further: If a method differs **only** from its +overridden method by "final", **and** if the containing class is final, then the +method is not included in the signature file. The same is the case for +deprecated. + ### Miscellaneous Some other minor tweaks in v2: diff --git a/src/main/java/com/android/tools/metalava/ApiLint.kt b/src/main/java/com/android/tools/metalava/ApiLint.kt index 2290f3193bd7f334e068e3b9721d6ccf1939cba9..aa1e432b34447adba87b41398db333f636a41aa1 100644 --- a/src/main/java/com/android/tools/metalava/ApiLint.kt +++ b/src/main/java/com/android/tools/metalava/ApiLint.kt @@ -143,75 +143,82 @@ import java.util.function.Predicate * The [ApiLint] analyzer checks the API against a known set of preferred API practices * by the Android API council. */ -class ApiLint { - fun check(codebase: Codebase) { - val prevCount = reporter.totalCount - - // The previous Kotlin interop tests are also part of API lint now (though they can be - // run independently as well; therefore, only run them here if not running separately) - val kotlinInterop = if (!options.checkKotlinInterop) KotlinInteropChecks() else null +class ApiLint(private var codebase: Codebase) : ApiVisitor( + // Sort by source order such that warnings follow source line number order + methodComparator = MethodItem.sourceOrderComparator, + fieldComparator = FieldItem.comparator, + ignoreShown = options.showUnannotated +) { + private fun report(id: Error, item: Item, message: String) { + // Don't flag api warnings on deprecated APIs; these are obviously already known to + // be problematic. + if (item.deprecated) { + return + } - codebase.accept(object : ApiVisitor( - // Sort by source order such that warnings follow source line number order - methodComparator = MethodItem.sourceOrderComparator, - fieldComparator = FieldItem.comparator - ) { - override fun skip(item: Item): Boolean { - return super.skip(item) || item is ClassItem && !isInteresting(item) - } - - private var isKotlin = false - - override fun visitClass(cls: ClassItem) { - val methods = cls.filteredMethods(filterEmit).asSequence() - val fields = cls.filteredFields(filterEmit, showUnannotated).asSequence() - val constructors = cls.filteredConstructors(filterEmit) - val superClass = cls.filteredSuperclass(filterReference) - val interfaces = cls.filteredInterfaceTypes(filterReference).asSequence() - val allMethods = methods.asSequence() + constructors.asSequence() - checkClass( - cls, methods, constructors, allMethods, fields, superClass, interfaces, - filterReference - ) + // With show annotations we might be flagging API that is filtered out: hide these here + val testItem = if (item is ParameterItem) item.containingMethod() else item + if (!filterEmit.test(testItem)) { + return + } - isKotlin = cls.isKotlin() - } + reporter.report(id, item, message) + } - override fun visitMethod(method: MethodItem) { - checkMethod(method, filterReference) - val returnType = method.returnType() - if (returnType != null) { - checkType(returnType, method) - } - for (parameter in method.parameters()) { - checkType(parameter.type(), parameter) - } - kotlinInterop?.checkMethod(method, isKotlin) - } + private fun check() { + val prevCount = reporter.totalCount - override fun visitField(field: FieldItem) { - checkField(field) - checkType(field.type(), field) - kotlinInterop?.checkField(field, isKotlin) - } - }) + codebase.accept(this) val apiLintIssues = reporter.totalCount - prevCount if (apiLintIssues > 0) { // We've reported API lint violations; emit some verbiage to explain // how to suppress the error rules. - options.stdout.println("$apiLintIssues new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these.") + options.stdout.println("\n$apiLintIssues new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these.") } } - private fun report(id: Error, item: Item, message: String) { - // Don't flag api warnings on deprecated APIs; these are obviously already known to - // be problematic. - if (item.deprecated) { - return + override fun skip(item: Item): Boolean { + return super.skip(item) || item is ClassItem && !isInteresting(item) + } + + // The previous Kotlin interop tests are also part of API lint now (though they can be + // run independently as well; therefore, only run them here if not running separately) + private val kotlinInterop = if (!options.checkKotlinInterop) KotlinInteropChecks() else null + + private var isKotlin = false + + override fun visitClass(cls: ClassItem) { + val methods = cls.filteredMethods(filterReference).asSequence() + val fields = cls.filteredFields(filterReference, showUnannotated).asSequence() + val constructors = cls.filteredConstructors(filterReference) + val superClass = cls.filteredSuperclass(filterReference) + val interfaces = cls.filteredInterfaceTypes(filterReference).asSequence() + val allMethods = methods.asSequence() + constructors.asSequence() + checkClass( + cls, methods, constructors, allMethods, fields, superClass, interfaces, + filterReference + ) + + isKotlin = cls.isKotlin() + } + + override fun visitMethod(method: MethodItem) { + checkMethod(method, filterReference) + val returnType = method.returnType() + if (returnType != null) { + checkType(returnType, method) + } + for (parameter in method.parameters()) { + checkType(parameter.type(), parameter) } + kotlinInterop?.checkMethod(method, isKotlin) + } - reporter.report(id, item, message) + override fun visitField(field: FieldItem) { + checkField(field) + checkType(field.type(), field) + kotlinInterop?.checkField(field, isKotlin) } private fun checkType(type: TypeItem, item: Item) { @@ -3288,5 +3295,9 @@ class ApiLint { } } } + + fun check(codebase: Codebase) { + ApiLint(codebase).check() + } } } diff --git a/src/main/java/com/android/tools/metalava/Baseline.kt b/src/main/java/com/android/tools/metalava/Baseline.kt index 885e99a111d74219acc7969901703b6ed42f570a..6867bf75857104937bdf9d2f40bdc98db8d9e277 100644 --- a/src/main/java/com/android/tools/metalava/Baseline.kt +++ b/src/main/java/com/android/tools/metalava/Baseline.kt @@ -37,7 +37,13 @@ import java.io.PrintWriter const val DEFAULT_BASELINE_NAME = "baseline.txt" -class Baseline(val file: File, var create: Boolean = !file.isFile) { +class Baseline( + val file: File, + var create: Boolean = !file.isFile, + private var format: FileFormat = FileFormat.BASELINE, + private var headerComment: String = "" +) { + /** Map from issue id to element id to message */ private val map = HashMap<Errors.Error, MutableMap<String, String>>() @@ -193,6 +199,9 @@ class Baseline(val file: File, var create: Boolean = !file.isFile) { private fun write() { if (!map.isEmpty()) { val sb = StringBuilder() + sb.append(format.header()) + sb.append(headerComment) + map.keys.asSequence().sortedBy { it.name ?: it.code.toString() }.forEach { error -> val idMap = map[error] idMap?.keys?.sorted()?.forEach { elementId -> diff --git a/src/main/java/com/android/tools/metalava/Compatibility.kt b/src/main/java/com/android/tools/metalava/Compatibility.kt index 5594e4a731f58694edc953f06afc41edcb1ea97a..32f5a1e2389ffce20a45a1fbaddd33bb01934bdf 100644 --- a/src/main/java/com/android/tools/metalava/Compatibility.kt +++ b/src/main/java/com/android/tools/metalava/Compatibility.kt @@ -31,9 +31,6 @@ class Compatibility( val compat: Boolean = COMPAT_MODE_BY_DEFAULT ) { - /** Whether to inline fields from implemented interfaces into concrete classes */ - var inlineInterfaceFields: Boolean = compat - /** In signature files, use "implements" instead of "extends" for the super class of * an interface */ var extendsForInterfaceSuperClass: Boolean = compat @@ -57,12 +54,6 @@ class Compatibility( * `@Deprecated` annotation before the modifier list */ var nonstandardModifierOrder: Boolean = compat - /** In signature files, skip the native modifier from the modifier lists */ - var skipNativeModifier: Boolean = true - - /** In signature files, skip the strictfp modifier from the modifier lists */ - var skipStrictFpModifier: Boolean = true - /** Whether to include instance methods in annotation classes for the annotation properties */ var skipAnnotationInstanceMethods: Boolean = compat @@ -96,47 +87,14 @@ class Compatibility( */ var filterThrowsClasses: Boolean = !compat - /** - * Include a single space in front of package private classes with no other modifiers - * (this doesn't align well, but is supported to make the output 100% identical to the - * doclava1 format - */ - var extraSpaceForEmptyModifiers: Boolean = compat - /** Format `Map<K,V>` as `Map<K, V>` */ var spaceAfterCommaInTypes: Boolean = compat - /** - * Doclava1 sorts classes/interfaces by class name instead of qualified name - */ - var sortClassesBySimpleName: Boolean = compat - /** * Doclava1 omits type parameters in interfaces (in signature files, not in stubs) */ var omitTypeParametersInInterfaces: Boolean = compat - /** - * Doclava1 sorted the methods like this: - * - * public final class RoundingMode extends java.lang.Enum { - * method public static java.math.RoundingMode valueOf(java.lang.String); - * method public static java.math.RoundingMode valueOf(int); - * ... - * - * Note how the two valueOf methods are out of order. With this compatibility mode, - * we try to perform the same sorting. - */ - var sortEnumValueOfMethodFirst: Boolean = compat - - /** - * Whether packages should be treated as recursive for documentation. In other words, - * if a directory has a `packages.html` file containing a `@hide` comment, then - * all "sub" packages (directories below this one) will also inherit the same comment. - * Java packages aren't supposed to work that way, but doclava does. - */ - var inheritPackageDocs: Boolean = compat - /** Force methods named "values" in enums to be marked final. This was done by * doclava1 with this comment: * @@ -175,7 +133,7 @@ class Compatibility( /** * Whether to include parameter names in the signature file */ - var parameterNames: Boolean = true + var parameterNames: Boolean = !compat /** * *Some* signatures for doclava1 wrote "<?>" as "<? extends java.lang.Object>", @@ -184,6 +142,21 @@ class Compatibility( */ var includeExtendsObjectInWildcard = compat + /** + * Whether deprecation should be shown in signature files as an annotation + * instead of a pseudo-modifier + */ + var deprecatedAsAnnotation = !compat + + /** Whether synchronized should be part of the output */ + var includeSynchronized = compat + + /** Whether we should omit common packages such as java.lang.* and kotlin.* from signature output */ + var omitCommonPackages = !compat + + /** Whether we should explicitly include retention when class even if not explicitly defined */ + var explicitlyListClassRetention = !compat + /** * If true, a @Deprecated class will automatically deprecate all its inner classes * as well. @@ -196,6 +169,12 @@ class Compatibility( */ var propagateDeprecatedMembers = !compat + /** + * If an overriding method differs from its super method only by final or deprecated + * and the containing class is final or deprecated, skip it in the signature file + */ + var hideDifferenceImplicit = !compat + /** Whether inner enums should be listed as static in the signature file. */ var staticEnums = compat @@ -224,5 +203,19 @@ class Compatibility( */ var xmlSkipEnumFields = compat + /** + * Doclava was missing annotation instance methods in JDiff reports + */ + var xmlSkipAnnotationMethods = compat + + /** Doclava lists character field values as integers instead of chars */ + var xmlCharAsInt = compat + + /** + * Doclava listed the superclass of annotations as + * java.lang.Object. + */ + var xmlAnnotationAsObject = compat + // Other examples: sometimes we sort by qualified name, sometimes by full name } \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt index dfef0ff6436eea479d5147e694a6ef55fa845231..22d359fa78afcef8888bc98ffff86c8966061eb2 100644 --- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt +++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt @@ -77,7 +77,7 @@ class CompatibilityCheck( * so in these cases we want to ignore certain changes such as considering * StringBuilder.setLength a newly added method. */ - private val comparingWithPartialSignatures = oldCodebase is TextCodebase && oldCodebase.format.major < 2 + private val comparingWithPartialSignatures = oldCodebase is TextCodebase && oldCodebase.format == FileFormat.V1 var foundProblems = false @@ -710,19 +710,6 @@ class CompatibilityCheck( val error = if (new.isInterface()) { Errors.ADDED_INTERFACE } else { - if (options.compatOutput && - new.qualifiedName() == "android.telephony.ims.feature.ImsFeature.Capabilities" - ) { - // Special case: Doclava and metalava signature files for the system api - // differ in only one way: Metalava believes ImsFeature.Capabilities should - // be in the signature file for @SystemApi, and doclava does not. However, - // this API is referenced from other system APIs that doclava does include - // (MmTelFeature.MmTelCapabilities's constructor) so it is clearly part of the - // API even if it's not listed in the signature file and we should not list - // this as an incompatible, added API. - return - } - Errors.ADDED_CLASS } handleAdded(error, new) @@ -769,7 +756,7 @@ class CompatibilityCheck( } // In old signature files, annotation methods are missing! This will show up as an added method. - if (new.containingClass().isAnnotationType() && oldCodebase is TextCodebase && oldCodebase.format.major == 1) { + if (new.containingClass().isAnnotationType() && oldCodebase is TextCodebase && oldCodebase.format == FileFormat.V1) { return } diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt index e48ae23c33b9e013aded3519da8f7823756a6df2..0bcc1c9d83c3e009e8b2314f31841e54da12338d 100644 --- a/src/main/java/com/android/tools/metalava/Driver.kt +++ b/src/main/java/com/android/tools/metalava/Driver.kt @@ -155,8 +155,11 @@ fun run( exitValue = false } - if (options.verbose && options.updateBaseline) { - options.baseline?.dumpStats(options.stdout) + if (options.updateBaseline) { + if (options.verbose) { + options.baseline?.dumpStats(options.stdout) + } + stdout.println("$PROGRAM_NAME wrote updated baseline to ${options.baseline?.file}") } options.baseline?.close() @@ -482,23 +485,50 @@ fun processNonCodebaseFlags() { kotlinStyleNulls = options.inputKotlinStyleNulls ) - TextCodebase.computeDelta(baseFile, baseApi, signatureApi) + val includeFields = + if (convert.outputFormat == FileFormat.V2) true else compatibility.includeFieldsInApiDiff + TextCodebase.computeDelta(baseFile, baseApi, signatureApi, includeFields) } else { signatureApi } - if (outputApi.isEmpty() && baseFile != null) { + if (outputApi.isEmpty() && baseFile != null && compatibility.compat) { // doclava compatibility: emits error warning instead of emitting empty <api/> element options.stdout.println("No API change detected, not generating diff") } else { - val output = convert.toXmlFile - if (output.path.endsWith(DOT_TXT)) { - createReportFile(outputApi, output, "Diff API File") { printWriter -> - SignatureWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip) + val output = convert.outputFile + if (convert.outputFormat == FileFormat.JDIFF) { + // See JDiff's XMLToAPI#nameAPI + val apiName = convert.outputFile.nameWithoutExtension.replace(' ', '_') + createReportFile(outputApi, output, "JDiff File") { printWriter -> + JDiffXmlWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip, apiName) } } else { - createReportFile(outputApi, output, "JDiff File") { printWriter -> - JDiffXmlWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip) + val prevOptions = options + val prevCompatibility = compatibility + try { + when (convert.outputFormat) { + FileFormat.V1 -> { + compatibility = Compatibility(true) + options = Options(emptyArray(), options.stdout, options.stderr) + FileFormat.V1.configureOptions(options, compatibility) + } + FileFormat.V2 -> { + compatibility = Compatibility(false) + options = Options(emptyArray(), options.stdout, options.stderr) + FileFormat.V2.configureOptions(options, compatibility) + } + else -> error("Unsupported format ${convert.outputFormat}") + } + + createReportFile(outputApi, output, "Diff API File") { printWriter -> + SignatureWriter( + printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip + ) + } + } finally { + options = prevOptions + compatibility = prevCompatibility } } } @@ -525,7 +555,7 @@ fun checkCompatibility( ) } - if (current is TextCodebase && current.format.major > 1 && options.outputFormat < 1) { + if (current is TextCodebase && current.format > FileFormat.V1 && options.outputFormat == FileFormat.V1) { throw DriverException("Cannot perform compatibility check of signature file $signatureFile in format ${current.format} without analyzing current codebase with $ARG_FORMAT=${current.format}") } @@ -734,7 +764,7 @@ private fun loadFromSources(): Codebase { if (options.checkApi) { val localTimer = Stopwatch.createStarted() - ApiLint().check(codebase) + ApiLint.check(codebase) progress("\n$PROGRAM_NAME ran api-lint in ${localTimer.elapsed(SECONDS)} seconds") } @@ -881,9 +911,6 @@ private fun createStubFiles(stubDir: File, codebase: Codebase, docStubs: Boolean val localTimer = Stopwatch.createStarted() val prevCompatibility = compatibility if (compatibility.compat) { - // if (!options.quiet) { - // options.stderr.println("Warning: Turning off compat mode when generating stubs") - // } compatibility = Compatibility(false) // But preserve the setting for whether we want to erase throws signatures (to ensure the API // stays compatible) diff --git a/src/main/java/com/android/tools/metalava/FileFormat.kt b/src/main/java/com/android/tools/metalava/FileFormat.kt new file mode 100644 index 0000000000000000000000000000000000000000..7e895e0453ec9b6b40e4ddd0d729cefd585392e2 --- /dev/null +++ b/src/main/java/com/android/tools/metalava/FileFormat.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import com.android.SdkConstants.DOT_TXT +import com.android.SdkConstants.DOT_XML + +/** File formats that metalava can emit APIs to */ +enum class FileFormat(val description: String, val version: String? = null) { + UNKNOWN("?"), + JDIFF("JDiff"), + BASELINE("Metalava baseline file", "1.0"), + SINCE_XML("Metalava API-level file", "1.0"), + + // signature formats should be last to make comparisons work (for example in [configureOptions]) + V1("Doclava signature file", "1.0"), + V2("Metalava signature file", "2.0"), + V3("Metalava signature file", "3.0"); + + /** Configures the option object such that the output format will be the given format */ + fun configureOptions(options: Options, compatibility: Compatibility) { + if (this == JDIFF) { + return + } + options.outputFormat = this + options.compatOutput = this == V1 + options.outputKotlinStyleNulls = this >= V3 + options.outputDefaultValues = this >= V2 + compatibility.omitCommonPackages = this >= V2 + options.includeSignatureFormatVersion = this >= V2 + } + + fun useKotlinStyleNulls(): Boolean { + return this >= V3 + } + + fun signatureFormatAsInt(): Int { + return when (this) { + V1 -> 1 + V2 -> 2 + V3 -> 3 + + BASELINE, + JDIFF, + SINCE_XML, + UNKNOWN -> error("this method is only allowed on signature formats, was $this") + } + } + + fun outputFlag(): String { + return if (isSignatureFormat()) { + "$ARG_FORMAT=v${signatureFormatAsInt()}" + } else { + "" + } + } + + fun preferredExtension(): String { + return when (this) { + V1, + V2, + V3 -> DOT_TXT + + BASELINE -> DOT_TXT + + JDIFF, SINCE_XML -> DOT_XML + + UNKNOWN -> "" + } + } + + fun header(): String? { + val prefix = headerPrefix() ?: return null + return prefix + version + "\n" + } + + fun headerPrefix(): String? { + return when (this) { + V1 -> null + V2, V3 -> "// Signature format: " + BASELINE -> "// Baseline format: " + JDIFF, SINCE_XML -> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + UNKNOWN -> null + } + } + + fun isSignatureFormat(): Boolean { + return this == V1 || this == V2 || this == V3 + } + + companion object { + private fun firstLine(s: String): String { + val index = s.indexOf('\n') + if (index == -1) { + return s + } + // Chop off \r if a Windows \r\n file + val end = if (index > 0 && s[index - 1] == '\r') index - 1 else index + return s.substring(0, end) + } + + fun parseHeader(fileContents: String): FileFormat { + val firstLine = firstLine(fileContents) + for (format in values()) { + val header = format.header() + if (header == null) { + if (firstLine.startsWith("package ")) { + // Old signature files + return V1 + } else if (firstLine.startsWith("<api")) { + return JDIFF + } + } else if (header.startsWith(firstLine)) { + if (format == JDIFF) { + if (!fileContents.contains("<api")) { + // The JDIFF header is the general XML header: don't accept XML documents that + // don't contain an empty API definition + return UNKNOWN + } + // Both JDiff and API-level files use <api> as the root tag (unfortunate but too late to + // change) so distinguish on whether the file contains any since elementss + if (fileContents.contains("since=")) { + return SINCE_XML + } + } + return format + } + } + + return UNKNOWN + } + } +} \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt index 5f9b32336d90a6da99cf0ae7f17c46d5c8a32cb0..d4dbe0b4eb3a935c543a8ab6b8a3c7134aeb64b1 100644 --- a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt +++ b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt @@ -44,7 +44,8 @@ class JDiffXmlWriter( private val writer: PrintWriter, filterEmit: Predicate<Item>, filterReference: Predicate<Item>, - private val preFiltered: Boolean + private val preFiltered: Boolean, + private val apiName: String? = null ) : ApiVisitor( visitConstructorsAsMethods = false, nestInnerClasses = false, @@ -56,7 +57,16 @@ class JDiffXmlWriter( showUnannotated = options.showUnannotated ) { override fun visitCodebase(codebase: Codebase) { - writer.println("<api>") + writer.print("<api") + + if (apiName != null && !options.compatOutput) { + // See JDiff's XMLToAPI#nameAPI + writer.print(" name=\"") + writer.print(apiName) + writer.print("\"") + } + + writer.println(">") } override fun afterVisitCodebase(codebase: Codebase) { @@ -102,6 +112,36 @@ class JDiffXmlWriter( writer.println("\"\n>") writeInterfaceList(cls) + + if (cls.isEnum() && compatibility.defaultEnumMethods) { + writer.println( + """ + <method name="valueOf" + return="${cls.qualifiedName()}" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.String"> + </parameter> + </method> + <method name="values" + return="${cls.qualifiedName()}[]" + abstract="false" + native="false" + synchronized="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" + > + </method>""".trimIndent() + ) + } } fun deprecation(item: Item): String { @@ -154,7 +194,11 @@ class JDiffXmlWriter( val modifiers = field.modifiers val initialValue = field.initialValue(true) val value = if (initialValue != null) { - escapeAttributeValue(CodePrinter.constantToSource(initialValue)) + if (initialValue is Char && compatibility.xmlCharAsInt) { + initialValue.toInt().toString() + } else { + escapeAttributeValue(CodePrinter.constantToSource(initialValue)) + } } else null val fullTypeName = escapeAttributeValue(field.type().toTypeString()) @@ -170,9 +214,10 @@ class JDiffXmlWriter( if (value != null) { writer.print("\"\n value=\"") writer.print(value) - } else if (compatibility.xmlShowArrayFieldsAsNull && field.type().isArray()) { + } else if (compatibility.xmlShowArrayFieldsAsNull && (field.type().isArray())) { writer.print("\"\n value=\"null") } + writer.print("\"\n static=\"") writer.print(modifiers.isStatic()) writer.print("\"\n final=\"") @@ -193,6 +238,10 @@ class JDiffXmlWriter( override fun visitMethod(method: MethodItem) { val modifiers = method.modifiers + if (method.containingClass().isAnnotationType() && compatibility.xmlSkipAnnotationMethods) { + return + } + // Note - to match doclava we don't write out the type parameter list // (method.typeParameterList()) in JDiff files! @@ -200,7 +249,7 @@ class JDiffXmlWriter( writer.print(method.name()) method.returnType()?.let { writer.print("\"\n return=\"") - writer.print(escapeAttributeValue(it.toTypeString())) + writer.print(escapeAttributeValue(formatType(it))) } writer.print("\"\n abstract=\"") writer.print(modifiers.isAbstract()) @@ -224,12 +273,22 @@ class JDiffXmlWriter( } private fun writeSuperClassAttribute(cls: ClassItem) { + if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) { + // Written in the interface section instead + return + } + val superClass = if (preFiltered) cls.superClassType() else cls.filteredSuperClassType(filterReference) val superClassString = when { + cls.isAnnotationType() -> if (compatibility.xmlAnnotationAsObject) { + JAVA_LANG_OBJECT + } else { + JAVA_LANG_ANNOTATION + } superClass != null -> { // doclava seems to include java.lang.Object for classes but not interfaces if (!cls.isClass() && superClass.isJavaLangObject()) { @@ -242,7 +301,6 @@ class JDiffXmlWriter( ) ) } - cls.isAnnotationType() -> JAVA_LANG_ANNOTATION cls.isEnum() -> JAVA_LANG_ENUM else -> return } @@ -252,10 +310,17 @@ class JDiffXmlWriter( } private fun writeInterfaceList(cls: ClassItem) { - val interfaces = if (preFiltered) + var interfaces = if (preFiltered) cls.interfaceTypes().asSequence() else cls.filteredInterfaceTypes(filterReference).asSequence() + if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) { + val superClassType = cls.superClassType() + if (superClassType?.isJavaLangObject() == false) { + interfaces += superClassType + } + } + if (interfaces.any()) { interfaces.sortedWith(TypeItem.comparator).forEach { item -> writer.print("<implements name=\"") @@ -272,12 +337,21 @@ class JDiffXmlWriter( // NOTE: We report parameter name as "null" rather than the real name to match // doclava's behavior writer.print("<parameter name=\"null\" type=\"") - writer.print(escapeAttributeValue(parameter.type().toTypeString())) + writer.print(escapeAttributeValue(formatType(parameter.type()))) writer.println("\">") writer.println("</parameter>") } } + private fun formatType(type: TypeItem): String { + val typeString = type.toTypeString() + return if (compatibility.spaceAfterCommaInTypes) { + typeString.replace(",", ", ").replace(", ", ", ") + } else { + typeString + } + } + private fun writeThrowsList(method: MethodItem) { val throws = when { preFiltered -> method.throwsTypes().asSequence() @@ -287,7 +361,11 @@ class JDiffXmlWriter( if (throws.any()) { throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type -> writer.print("<exception name=\"") - writer.print(type.fullName()) + if (options.compatOutput) { + writer.print(type.simpleName()) + } else { + writer.print(type.fullName()) + } writer.print("\" type=\"") writer.print(type.qualifiedName()) writer.println("\">") diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt index 147be0b86f490969c1d2e97b04cbc3bcd6bda86b..b2ab95cfee8dffa724d983fd16968230300cc6ca 100644 --- a/src/main/java/com/android/tools/metalava/Options.kt +++ b/src/main/java/com/android/tools/metalava/Options.kt @@ -30,6 +30,7 @@ import java.io.IOException import java.io.OutputStreamWriter import java.io.PrintWriter import java.io.StringWriter +import java.util.Locale import kotlin.reflect.KMutableProperty1 import kotlin.reflect.full.memberProperties @@ -51,6 +52,10 @@ const val ARG_API = "--api" const val ARG_XML_API = "--api-xml" const val ARG_CONVERT_TO_JDIFF = "--convert-to-jdiff" const val ARG_CONVERT_NEW_TO_JDIFF = "--convert-new-to-jdiff" +const val ARG_CONVERT_TO_V1 = "--convert-to-v1" +const val ARG_CONVERT_TO_V2 = "--convert-to-v2" +const val ARG_CONVERT_NEW_TO_V1 = "--convert-new-to-v1" +const val ARG_CONVERT_NEW_TO_V2 = "--convert-new-to-v2" const val ARG_PRIVATE_API = "--private-api" const val ARG_DEX_API = "--dex-api" const val ARG_PRIVATE_DEX_API = "--private-dex-api" @@ -133,7 +138,6 @@ const val ARG_DEX_API_MAPPING = "--dex-api-mapping" const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation" const val ARG_BASELINE = "--baseline" const val ARG_UPDATE_BASELINE = "--update-baseline" -const val ARG_CONVERT_XML = "--convert-signature-to-xml" class Options( args: Array<String>, @@ -241,16 +245,13 @@ class Options( var compatOutput = useCompatMode(args) /** Whether nullness annotations should be displayed as ?/!/empty instead of with @NonNull/@Nullable. */ - var outputKotlinStyleNulls = !compatOutput + var outputKotlinStyleNulls = false // requires v3 /** Whether default values should be included in signature files */ var outputDefaultValues = !compatOutput - /** Whether we should omit common packages such as java.lang.* and kotlin.* from signature output */ - var omitCommonPackages = !compatOutput - /** The output format version being used */ - var outputFormat: Int = 1 + var outputFormat: FileFormat = if (compatOutput) FileFormat.V1 else FileFormat.V2 /** * Whether reading signature files should assume the input is formatted as Kotlin-style nulls @@ -471,7 +472,7 @@ class Options( /** Level to include for javadoc */ var docLevel = DocLevel.PROTECTED - /** Whether to include the signature file format version number ([CURRENT_SIGNATURE_FORMAT]) in signature files */ + /** Whether to include the signature file format version header in signature files */ var includeSignatureFormatVersion: Boolean = !compatOutput /** A baseline to check against */ @@ -501,12 +502,13 @@ class Options( /** List of signature files to export as JDiff files */ val convertToXmlFiles: List<ConvertFile> = mutableConvertToXmlFiles - /** JDiff file conversion candidates */ + /** File conversion tasks */ data class ConvertFile( val fromApiFile: File, - val toXmlFile: File, + val outputFile: File, val baseApiFile: File? = null, - val strip: Boolean + val strip: Boolean = false, + val outputFormat: FileFormat = FileFormat.JDIFF ) /** Temporary folder to use instead of the JDK default, if any */ @@ -701,9 +703,14 @@ class Options( } ARG_BASELINE -> { - val file = stringToNewOrExistingFile(getValue(args, ++index)) + val relative = getValue(args, ++index) + val file = stringToNewOrExistingFile(relative) assert(baseline == null) { "Only one baseline is allowed; found both ${baseline!!.file} and $file" } - baseline = Baseline(file, updateBaseline || !file.isFile) + val headerComment = if (relative.contains("frameworks/base/")) + "// See tools/metalava/API-LINT.md for how to update this file.\n\n" + else + "" + baseline = Baseline(file, updateBaseline || !file.isFile, FileFormat.BASELINE, headerComment) } ARG_UPDATE_BASELINE -> { @@ -866,8 +873,8 @@ class Options( // Already processed above but don't flag it here as invalid } - ARG_OMIT_COMMON_PACKAGES, "$ARG_OMIT_COMMON_PACKAGES=yes" -> omitCommonPackages = true - "$ARG_OMIT_COMMON_PACKAGES=no" -> omitCommonPackages = false + ARG_OMIT_COMMON_PACKAGES, "$ARG_OMIT_COMMON_PACKAGES=yes" -> compatibility.omitCommonPackages = true + "$ARG_OMIT_COMMON_PACKAGES=no" -> compatibility.omitCommonPackages = false ARG_SKIP_JAVA_IN_COVERAGE_REPORT -> omitRuntimePackageStats = true @@ -956,23 +963,45 @@ class Options( artifactRegistrations.register(artifactId, descriptor) } - ARG_CONVERT_TO_JDIFF, "-convert2xml", "-convert2xmlnostrip" -> { - val signatureFile = stringToExistingFile(getValue(args, ++index)) - val jDiffFile = stringToNewFile(getValue(args, ++index)) + ARG_CONVERT_TO_JDIFF, + ARG_CONVERT_TO_V1, + ARG_CONVERT_TO_V2, + // doclava compatibility: + "-convert2xml", + "-convert2xmlnostrip" -> { val strip = arg == "-convert2xml" - mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, null, strip)) - } + val format = when (arg) { + ARG_CONVERT_TO_V1 -> FileFormat.V1 + ARG_CONVERT_TO_V2 -> FileFormat.V2 + else -> FileFormat.JDIFF + } - ARG_CONVERT_NEW_TO_JDIFF, "-new_api", "-new_api_no_strip" -> { - val baseFile = stringToExistingFile(getValue(args, ++index)) val signatureFile = stringToExistingFile(getValue(args, ++index)) - val jDiffFile = stringToNewFile(getValue(args, ++index)) + val outputFile = stringToNewFile(getValue(args, ++index)) + mutableConvertToXmlFiles.add(ConvertFile(signatureFile, outputFile, null, strip, format)) + } + + ARG_CONVERT_NEW_TO_JDIFF, + ARG_CONVERT_NEW_TO_V1, + ARG_CONVERT_NEW_TO_V2, + // doclava compatibility: + "-new_api", + "-new_api_no_strip" -> { + val format = when (arg) { + ARG_CONVERT_NEW_TO_V1 -> FileFormat.V1 + ARG_CONVERT_NEW_TO_V2 -> FileFormat.V2 + else -> FileFormat.JDIFF + } val strip = arg == "-new_api" if (arg != ARG_CONVERT_NEW_TO_JDIFF) { // Using old doclava flags: Compatibility behavior: don't include fields in the output compatibility.includeFieldsInApiDiff = false } - mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, baseFile, strip)) + + val baseFile = stringToExistingFile(getValue(args, ++index)) + val signatureFile = stringToExistingFile(getValue(args, ++index)) + val jDiffFile = stringToNewFile(getValue(args, ++index)) + mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, baseFile, strip, format)) } "--write-android-jar-signatures" -> { @@ -1138,7 +1167,7 @@ class Options( yesNo(arg.substring(ARG_OUTPUT_DEFAULT_VALUES.length + 1)) } } else if (arg.startsWith(ARG_OMIT_COMMON_PACKAGES)) { - omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) { + compatibility.omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) { true } else { yesNo(arg.substring(ARG_OMIT_COMMON_PACKAGES.length + 1)) @@ -1153,9 +1182,15 @@ class Options( else yesNo(arg.substring(ARG_INCLUDE_SIG_VERSION.length + 1)) } else if (arg.startsWith(ARG_FORMAT)) { when (arg) { - "$ARG_FORMAT=v1" -> setFormat(1) - "$ARG_FORMAT=v2" -> setFormat(2) - "$ARG_FORMAT=v3" -> setFormat(3) + "$ARG_FORMAT=v1" -> { + FileFormat.V1.configureOptions(this, compatibility) + } + "$ARG_FORMAT=v2" -> { + FileFormat.V2.configureOptions(this, compatibility) + } + "$ARG_FORMAT=v3" -> { + FileFormat.V3.configureOptions(this, compatibility) + } else -> throw DriverException(stderr = "Unexpected signature format; expected v1, v2 or v3") } } else if (arg.startsWith("-")) { @@ -1231,9 +1266,9 @@ class Options( artifactRegistrations.clear() } - if (baseline == null && sourcePath.isNotEmpty() && !sourcePath[0].path.isBlank()) { - val defaultBaseline = File(sourcePath[0], DEFAULT_BASELINE_NAME) - if (defaultBaseline.isFile || updateBaseline) { + if (baseline == null) { + val defaultBaseline = getDefaultBaselineFile() + if (defaultBaseline != null && (defaultBaseline.isFile || updateBaseline)) { baseline = Baseline(defaultBaseline, !defaultBaseline.isFile || updateBaseline) } } @@ -1241,15 +1276,6 @@ class Options( checkFlagConsistency() } - private fun setFormat(format: Int) { - outputFormat = format - compatOutput = format == 1 - outputKotlinStyleNulls = format >= 3 - outputDefaultValues = format >= 2 - omitCommonPackages = format >= 2 - includeSignatureFormatVersion = format >= 2 - } - private fun findCompatibilityFlag(arg: String): KMutableProperty1<Compatibility, Boolean>? { val index = arg.indexOf('=') val name = arg @@ -1264,6 +1290,27 @@ class Options( } } + /** + * Produce a default file name for the baseline. It's normally "baseline.txt", but can + * be prefixed by show annotations; e.g. @TestApi -> test-baseline.txt, @SystemApi -> system-baseline.txt, + * etc. + */ + private fun getDefaultBaselineFile(): File? { + if (sourcePath.isNotEmpty() && !sourcePath[0].path.isBlank()) { + fun annotationToPrefix(qualifiedName: String): String { + val name = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1) + return name.toLowerCase(Locale.US).removeSuffix("api") + "-" + } + val sb = StringBuilder() + showAnnotations.forEach { sb.append(annotationToPrefix(it)).append('-') } + showSingleAnnotations.forEach { sb.append(annotationToPrefix(it)).append('-') } + sb.append(DEFAULT_BASELINE_NAME) + return File(sourcePath[0], sb.toString()) + } else { + return null + } + } + private fun findAndroidJars( androidJarPatterns: List<String>, currentApiLevel: Int, @@ -1739,6 +1786,12 @@ class Options( "in the JDiff XML format. Can be specified multiple times.", "$ARG_CONVERT_NEW_TO_JDIFF <old> <new> <xml>", "Reads in the given old and new api files, " + "computes the difference, and writes out only the new parts of the API in the JDiff XML format.", + "$ARG_CONVERT_TO_V1 <sig> <sig>", "Reads in the given signature file and writes it out as a " + + "signature file in the original v1/doclava format.", + "$ARG_CONVERT_TO_V2 <sig> <sig>", "Reads in the given signature file and writes it out as a " + + "signature file in the new signature format, v2.", + "$ARG_CONVERT_NEW_TO_V2 <old> <new> <sig>", "Reads in the given old and new api files, " + + "computes the difference, and writes out only the new parts of the API in the v2 format.", "", "\nStatistics:", ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " + diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt index 939913b0ad314b5e33b103495ae3ef2f8b1f195e..56009b7bf4c12b26e26092ef59c2d1534a503549 100644 --- a/src/main/java/com/android/tools/metalava/Reporter.kt +++ b/src/main/java/com/android/tools/metalava/Reporter.kt @@ -133,7 +133,7 @@ open class Reporter(private val rootFolder: File? = null) { report(severity, item.psi(), message, id) } is TextItem -> report(severity, (item as? TextItem)?.position.toString(), message, id) - else -> report(severity, "<unknown location>", message, id) + else -> report(severity, null as String?, message, id) } } @@ -294,7 +294,9 @@ open class Reporter(private val rootFolder: File? = null) { if (color) { sb.append(terminalAttributes(bold = true)) if (!options.omitLocations) { - location?.let { sb.append(it).append(": ") } + location?.let { + sb.append(it).append(": ") + } } when (effectiveSeverity) { LINT -> sb.append(terminalAttributes(foreground = TerminalColor.CYAN)).append("lint: ") diff --git a/src/main/java/com/android/tools/metalava/SignatureFormat.kt b/src/main/java/com/android/tools/metalava/SignatureFormat.kt deleted file mode 100644 index 794eedbc5ed730b3f9e519fe9797f0d50111d385..0000000000000000000000000000000000000000 --- a/src/main/java/com/android/tools/metalava/SignatureFormat.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tools.metalava - -/** Current signature format. */ -const val CURRENT_SIGNATURE_FORMAT = "2.0" - -/** Marker comment at the beginning of the signature file */ -const val SIGNATURE_FORMAT_PREFIX = "// Signature format: " - -fun useKotlinStyleNulls(format: Int): Boolean { - return format >= 3 -} \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt index 1b9ee50c567eb7c2db280025364f4b3b89e7f3f8..c38a1a125355217db8fc674d1ef6e3ac99511d35 100644 --- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt +++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt @@ -50,8 +50,7 @@ class SignatureWriter( ) { init { if (options.includeSignatureFormatVersion) { - writer.print(SIGNATURE_FORMAT_PREFIX) - writer.println(CURRENT_SIGNATURE_FORMAT) + writer.print(options.outputFormat.header()) } } @@ -139,10 +138,6 @@ class SignatureWriter( override fun visitClass(cls: ClassItem) { writer.print(" ") - if (compatibility.extraSpaceForEmptyModifiers && cls.isPackagePrivate && cls.isPackagePrivate) { - writer.print(" ") - } - writeModifiers(cls) if (cls.isAnnotationType()) { @@ -185,7 +180,7 @@ class SignatureWriter( includeDeprecated = true, includeAnnotations = compatibility.annotationsInSignatures, skipNullnessAnnotations = options.outputKotlinStyleNulls, - omitCommonPackages = options.omitCommonPackages + omitCommonPackages = compatibility.omitCommonPackages ) } @@ -317,7 +312,7 @@ class SignatureWriter( ) // Strip java.lang. prefix? - if (options.omitCommonPackages) { + if (compatibility.omitCommonPackages) { typeString = TypeItem.shortenTypes(typeString) } diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java index 5189d34babad4fbe8966b3ff813e2372fd30883e..f1d672ec2801a305881b5f6eea117a387f439933 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java +++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java @@ -16,9 +16,8 @@ package com.android.tools.metalava.doclava1; -import com.android.ide.common.repository.GradleVersion; import com.android.tools.lint.checks.infrastructure.ClassNameKt; -import com.android.tools.metalava.SignatureFormatKt; +import com.android.tools.metalava.FileFormat; import com.android.tools.metalava.model.AnnotationItem; import com.android.tools.metalava.model.DefaultModifierList; import com.android.tools.metalava.model.TypeParameterList; @@ -37,6 +36,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.io.Files; import kotlin.Pair; +import kotlin.text.StringsKt; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -49,7 +49,6 @@ import static com.android.tools.metalava.ConstantsKt.ANDROIDX_NULLABLE; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ANNOTATION; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ENUM; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_STRING; -import static com.android.tools.metalava.SignatureFormatKt.SIGNATURE_FORMAT_PREFIX; import static com.android.tools.metalava.model.FieldItemKt.javaUnescapeString; // @@ -71,25 +70,20 @@ public class ApiFile { } } + @SuppressWarnings("StatementWithEmptyBody") @VisibleForTesting public static TextCodebase parseApi(String filename, String apiText, Boolean kotlinStyleNulls) throws ApiParseException { - GradleVersion format = null; - if (apiText.startsWith(SIGNATURE_FORMAT_PREFIX)) { - int begin = SIGNATURE_FORMAT_PREFIX.length(); - int end = apiText.indexOf('\n', begin); - if (end == -1) { - end = apiText.length(); - } else if (end > 0 && apiText.charAt(end - 1) == '\r') { - end--; - } - String formatString = apiText.substring(begin, end).trim(); - if (!formatString.isEmpty()) { - format = GradleVersion.tryParse(formatString); - if (kotlinStyleNulls == null) { - kotlinStyleNulls = SignatureFormatKt.useKotlinStyleNulls(format.getMajor()); - } + FileFormat format = FileFormat.Companion.parseHeader(apiText); + if (format.isSignatureFormat()) { + if (kotlinStyleNulls == null || !kotlinStyleNulls) { + kotlinStyleNulls = format.useKotlinStyleNulls(); } + } else if (StringsKt.isBlank(apiText)) { + // Signature files are sometimes blank, particularly with show annotations + kotlinStyleNulls = false; + } else { + throw new ApiParseException("Unknown file format of " + filename); } if (apiText.contains("/*")) { @@ -99,12 +93,8 @@ public class ApiFile { final Tokenizer tokenizer = new Tokenizer(filename, apiText.toCharArray()); final TextCodebase api = new TextCodebase(new File(filename)); api.setDescription("Codebase loaded from " + filename); - if (format != null) { - api.setFormat(format); - } - if (kotlinStyleNulls != null) { - api.setKotlinStyleNulls(kotlinStyleNulls); - } + api.setFormat(format); + api.setKotlinStyleNulls(kotlinStyleNulls); while (true) { String token = tokenizer.getToken(); @@ -180,6 +170,7 @@ public class ApiFile { token = tokenizer.requireToken(); } else if ("interface".equals(token)) { isInterface = true; + modifiers.setAbstract(true); token = tokenizer.requireToken(); } else if ("@interface".equals(token)) { // Annotation @@ -242,6 +233,10 @@ public class ApiFile { } if (JAVA_LANG_ENUM.equals(ext)) { cl.setIsEnum(true); + // Above we marked all enums as static but for a top level class it's implicit + if (!cl.fullName().contains(".")) { + cl.getModifiers().setStatic(false); + } } else if (isAnnotation) { api.mapClassToInterface(cl, JAVA_LANG_ANNOTATION); } else if (api.implementsInterface(cl, JAVA_LANG_ANNOTATION)) { @@ -423,6 +418,9 @@ public class ApiFile { name = token; method = new TextMethodItem(api, name, cl, modifiers, returnType, tokenizer.pos()); method.setDeprecated(modifiers.isDeprecated()); + if (cl.isInterface() && !modifiers.isDefault()) { + modifiers.setAbstract(true); + } method.setTypeParameterList(typeParameterList); if (typeParameterList instanceof TextTypeParameterList) { ((TextTypeParameterList) typeParameterList).setOwner(method); @@ -754,7 +752,9 @@ public class ApiFile { if (typeString.endsWith("...")) { modifiers.setVarArg(true); } - TextTypeItem typeInfo = api.obtainTypeFromString(typeString); + TextTypeItem typeInfo = api.obtainTypeFromString(typeString, + (TextClassItem) method.containingClass(), + method.typeParameterList()); String name; String publicName; diff --git a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt index 94c055ea56c2c333aff9a25a1ffb36c3e06bf73f..f90daf5068debd40671847f520449f38b76c00fd 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt +++ b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt @@ -16,10 +16,10 @@ package com.android.tools.metalava.doclava1 -import com.android.ide.common.repository.GradleVersion import com.android.tools.metalava.ApiType import com.android.tools.metalava.CodebaseComparator import com.android.tools.metalava.ComparisonVisitor +import com.android.tools.metalava.FileFormat import com.android.tools.metalava.JAVA_LANG_ANNOTATION import com.android.tools.metalava.JAVA_LANG_ENUM import com.android.tools.metalava.JAVA_LANG_OBJECT @@ -77,7 +77,7 @@ class TextCodebase(location: File) : DefaultCodebase(location) { * Signature file format version, if found. Type "GradleVersion" is misleading; it's just a convenient * version class. */ - var format: GradleVersion = GradleVersion.parse("1.0") // not specifying format: assumed to be doclava, 1.0 + var format: FileFormat = FileFormat.V1 // not specifying format: assumed to be doclava, 1.0 override fun getPackages(): PackageList { val list = ArrayList<PackageItem>(mPackages.values) @@ -340,7 +340,8 @@ class TextCodebase(location: File) : DefaultCodebase(location) { fun computeDelta( baseFile: File, baseApi: Codebase, - signatureApi: Codebase + signatureApi: Codebase, + includeFieldsInApiDiff: Boolean = compatibility.includeFieldsInApiDiff ): TextCodebase { // Compute just the delta val delta = TextCodebase(baseFile) @@ -367,7 +368,7 @@ class TextCodebase(location: File) : DefaultCodebase(location) { } override fun added(new: FieldItem) { - if (!compatibility.includeFieldsInApiDiff) { + if (!includeFieldsInApiDiff) { return } val cls = getOrAddClass(new.containingClass()) diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt index e1746c94157deab578efb620c4c77051154acfa2..e06784caea513cae3abdaaed3a75a0304492c06b 100644 --- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt @@ -21,7 +21,6 @@ import com.android.tools.metalava.ApiAnalyzer import com.android.tools.metalava.JAVA_LANG_ANNOTATION import com.android.tools.metalava.JAVA_LANG_ENUM import com.android.tools.metalava.JAVA_LANG_OBJECT -import com.android.tools.metalava.compatibility import com.android.tools.metalava.model.visitors.ApiVisitor import com.android.tools.metalava.model.visitors.ItemVisitor import com.android.tools.metalava.model.visitors.TypeVisitor @@ -375,12 +374,7 @@ interface ClassItem : Item { a.qualifiedName().compareTo(b.qualifiedName()) } - fun classNameSorter(): Comparator<in ClassItem> = - if (compatibility.sortClassesBySimpleName) { - ClassItem.comparator - } else { - ClassItem.qualifiedComparator - } + fun classNameSorter(): Comparator<in ClassItem> = ClassItem.qualifiedComparator } fun findMethod( diff --git a/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt b/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt index 65ebd48bb004306f6aa0dfacd470dae8a63af81b..21a328fabff5bd4d55100853308d5ed9cdfe5324 100644 --- a/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt +++ b/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt @@ -16,6 +16,7 @@ package com.android.tools.metalava.model +import com.android.tools.metalava.compatibility import com.android.tools.metalava.model.psi.PsiModifierItem open class DefaultModifierList( @@ -245,13 +246,24 @@ open class DefaultModifierList( override fun equivalentTo(other: ModifierList): Boolean { if (other is PsiModifierItem) { val flags2 = other.flags - val mask = EQUIVALENCE_MASK - - // Skipping the "default" flag - // TODO: Compatibility: skipNativeModifier and skipStrictFpModifier modifier flags! - // if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false - // if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false - return flags and mask == flags2 and mask + val mask = if (compatibility.includeSynchronized) COMPAT_EQUIVALENCE_MASK else EQUIVALENCE_MASK + + val masked1 = flags and mask + val masked2 = flags2 and mask + val same = masked1 xor masked2 + if (same == 0) { + return true + } else if (compatibility.hideDifferenceImplicit) { + if (same == FINAL && + // Only differ in final: not significant if implied by containing class + isFinal() && (owner as? MethodItem)?.containingClass()?.modifiers?.isFinal() == true) { + return true + } else if (same == DEPRECATED && + // Only differ in deprecated: not significant if implied by containing class + isDeprecated() && (owner as? MethodItem)?.containingClass()?.deprecated == true) { + return true + } + } } return false } @@ -309,7 +321,9 @@ open class DefaultModifierList( * to consider whether an override of a method is different from its super implementation */ private const val EQUIVALENCE_MASK = PUBLIC or PROTECTED or PRIVATE or STATIC or ABSTRACT or - FINAL or TRANSIENT or VOLATILE or SYNCHRONIZED or DEPRECATED or VARARG or + FINAL or TRANSIENT or VOLATILE or DEPRECATED or VARARG or SEALED or INTERNAL or INFIX or OPERATOR or SUSPEND + + private const val COMPAT_EQUIVALENCE_MASK = EQUIVALENCE_MASK or SYNCHRONIZED } } \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/model/MethodItem.kt b/src/main/java/com/android/tools/metalava/model/MethodItem.kt index 173842fdc0e1b854926bf55e2f969c9358251090..bdb3b899ad7cdcde416caa580c921bf0903201ca 100644 --- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt +++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt @@ -16,6 +16,7 @@ package com.android.tools.metalava.model +import com.android.tools.metalava.compatibility import com.android.tools.metalava.doclava1.TextCodebase import com.android.tools.metalava.model.visitors.ItemVisitor import com.android.tools.metalava.model.visitors.TypeVisitor @@ -111,7 +112,7 @@ interface MethodItem : MemberItem { ): LinkedHashSet<ClassItem> { for (cls in throwsTypes()) { - if (predicate.test(cls)) { + if (predicate.test(cls) || cls.isTypeParameter && !compatibility.useErasureInThrows) { classes.add(cls) } else { // Excluded, but it may have super class throwables that are included; if so, include those @@ -276,8 +277,8 @@ interface MethodItem : MemberItem { return false } - // IntentService#onStart - is it here because they vary in deprecation status? - if (method.deprecated != superMethod.deprecated) { + if (method.deprecated != superMethod.deprecated && + (!compatibility.hideDifferenceImplicit || !method.deprecated)) { return false } diff --git a/src/main/java/com/android/tools/metalava/model/ModifierList.kt b/src/main/java/com/android/tools/metalava/model/ModifierList.kt index 3ce5d30c685ad0c0eebd45cb2185d8a9437adcd3..d645edf3d9744a95b5560d0ff113883384b266cd 100644 --- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt +++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt @@ -69,10 +69,8 @@ interface ModifierList { if (isStatic() != other.isStatic()) return false if (isAbstract() != other.isAbstract()) return false - if (isFinal() != other.isFinal()) return false - if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false - if (isSynchronized() != other.isSynchronized()) return false - if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false + if (isFinal() != other.isFinal()) { return false } + if (compatibility.includeSynchronized && isSynchronized() != other.isSynchronized()) return false if (isTransient() != other.isTransient()) return false if (isVolatile() != other.isVolatile()) return false @@ -270,10 +268,6 @@ interface ModifierList { return } - if (compatibility.extraSpaceForEmptyModifiers && item.isPackagePrivate && item is MemberItem) { - writer.write(" ") - } - // Kotlin order: // https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers @@ -342,22 +336,18 @@ interface ModifierList { writer.write("abstract ") } - if (list.isNative() && (target.isStubsFile() || !compatibility.skipNativeModifier)) { + if (list.isNative() && target.isStubsFile()) { writer.write("native ") } - if (item.deprecated && includeDeprecated && !target.isStubsFile() && options.compatOutput) { + if (item.deprecated && includeDeprecated && !target.isStubsFile() && !compatibility.deprecatedAsAnnotation) { writer.write("deprecated ") } - if (list.isSynchronized() && (options.compatOutput || target.isStubsFile())) { + if (list.isSynchronized() && (compatibility.includeSynchronized || target.isStubsFile())) { writer.write("synchronized ") } - if (!compatibility.skipStrictFpModifier && list.isStrictFp()) { - writer.write("strictfp ") - } - if (list.isTransient()) { writer.write("transient ") } @@ -366,7 +356,7 @@ interface ModifierList { writer.write("volatile ") } } else { - if (item.deprecated && includeDeprecated && !target.isStubsFile() && options.compatOutput) { + if (item.deprecated && includeDeprecated && !target.isStubsFile() && !compatibility.deprecatedAsAnnotation) { writer.write("deprecated ") } @@ -434,17 +424,13 @@ interface ModifierList { writer.write("volatile ") } - if (list.isSynchronized() && (options.compatOutput || target.isStubsFile())) { + if (list.isSynchronized() && (compatibility.includeSynchronized || target.isStubsFile())) { writer.write("synchronized ") } - if (list.isNative() && (target.isStubsFile() || !compatibility.skipNativeModifier)) { + if (list.isNative() && target.isStubsFile()) { writer.write("native ") } - - if (!compatibility.skipStrictFpModifier && list.isStrictFp()) { - writer.write("strictfp ") - } } } @@ -463,7 +449,7 @@ interface ModifierList { // unless runtimeOnly is false, in which case we'd include it too // e.g. emit @Deprecated if includeDeprecated && !runtimeOnly if (item.deprecated && - (!options.compatOutput || target.isStubsFile()) && + (compatibility.deprecatedAsAnnotation || target.isStubsFile()) && (runtimeAnnotationsOnly || includeDeprecated) ) { writer.write("@Deprecated") diff --git a/src/main/java/com/android/tools/metalava/model/TypeItem.kt b/src/main/java/com/android/tools/metalava/model/TypeItem.kt index 1469e91aaac4f80bf3cadd9bd6b5a4d8fc5b9d63..7ca5ff98dd28d9983281cc3480ec1ab953e42de2 100644 --- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt @@ -21,7 +21,6 @@ import com.android.tools.metalava.JAVA_LANG_OBJECT import com.android.tools.metalava.JAVA_LANG_PREFIX import com.android.tools.metalava.JAVA_LANG_STRING import com.android.tools.metalava.compatibility -import com.android.tools.metalava.options import java.util.function.Predicate /** @@ -168,7 +167,7 @@ interface TypeItem { companion object { /** Shortens types, if configured */ fun shortenTypes(type: String): String { - if (options.omitCommonPackages) { + if (compatibility.omitCommonPackages) { var cleaned = type if (cleaned.contains("@androidx.annotation.")) { cleaned = cleaned.replace("@androidx.annotation.", "@") diff --git a/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt b/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt index bcd526beb70aba97a7252dba1d6b284ef9a58641..7fac61b694544b7a94297d08798416090d33d06c 100644 --- a/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt +++ b/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt @@ -20,4 +20,7 @@ interface TypeParameterListOwner { fun typeParameterList(): TypeParameterList /** Given a variable in this owner, resolves to a type parameter item */ fun resolveParameter(variable: String): TypeParameterItem? + + /** Parent type parameter list owner */ + fun typeParameterListOwnerParent(): TypeParameterListOwner? } \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt index 9eb7b85f40f7458dffd152d9a9a265eaab255808..bbe8f86d2e730ce13e1c21191746de1224f96802 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt @@ -29,6 +29,7 @@ import com.intellij.psi.PsiAnnotationMemberValue import com.intellij.psi.PsiArrayInitializerMemberValue import com.intellij.psi.PsiClass import com.intellij.psi.PsiClassObjectAccessExpression +import com.intellij.psi.PsiElement import com.intellij.psi.PsiField import com.intellij.psi.PsiLiteral import com.intellij.psi.PsiReference @@ -40,6 +41,7 @@ import org.jetbrains.uast.UBinaryExpression import org.jetbrains.uast.UBinaryExpressionWithType import org.jetbrains.uast.UBlockExpression import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement import org.jetbrains.uast.UExpression import org.jetbrains.uast.ULambdaExpression import org.jetbrains.uast.ULiteralExpression @@ -62,8 +64,12 @@ open class CodePrinter( /** An optional filter to use to determine if we should emit a reference to an item */ private val filterReference: Predicate<Item>? = null ) { - open fun warning(message: String) { - reporter.report(Errors.INTERNAL_ERROR, null as Item?, message) + open fun warning(message: String, psiElement: PsiElement? = null) { + reporter.report(Errors.INTERNAL_ERROR, psiElement, message) + } + + open fun warning(message: String, uElement: UElement) { + warning(message, uElement.sourcePsi ?: uElement.javaPsi) } /** Given an annotation member value, returns the corresponding Java source expression */ @@ -186,7 +192,7 @@ open class CodePrinter( val declaringClass = field.containingClass if (declaringClass == null) { - warning("No containing class found for " + field.name) + warning("No containing class found for " + field.name, field) return false } val qualifiedName = declaringClass.qualifiedName @@ -225,7 +231,7 @@ open class CodePrinter( } else -> { if (skipUnknown) { - warning("Unexpected reference to $expression") + warning("Unexpected reference to $expression", expression) return false } sb.append(expression.asSourceString()) @@ -361,7 +367,7 @@ open class CodePrinter( } } - warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression") + warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression", expression) return false } diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt index d1872c33f2511503ca4fc8c47588623771b935c8..1bc4bf6a79800865ae4d6f7d7224c855ff4c7246 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt @@ -17,7 +17,6 @@ package com.android.tools.metalava.model.psi import com.android.SdkConstants -import com.android.tools.metalava.compatibility import com.android.tools.metalava.doclava1.Errors import com.android.tools.metalava.model.ClassItem import com.android.tools.metalava.model.DefaultCodebase @@ -414,7 +413,7 @@ open class PsiBasedCodebase(location: File, override var description: String = " } } val last = pkg.lastIndexOf('.') - if (last == -1 || !compatibility.inheritPackageDocs) { + if (last == -1) { hiddenPackages[packageName] = false break } else { diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt index 09158644b5ceaec6c2911b24e9e4445798ee08d0..f3efc401295bb83d93596cb85c8a526a28873238 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt @@ -27,7 +27,6 @@ import com.android.tools.metalava.model.PackageItem import com.android.tools.metalava.model.PropertyItem import com.android.tools.metalava.model.TypeItem import com.android.tools.metalava.model.TypeParameterList -import com.android.tools.metalava.options import com.intellij.lang.jvm.types.JvmReferenceType import com.intellij.psi.PsiClass import com.intellij.psi.PsiClassType @@ -408,7 +407,7 @@ open class PsiClassItem( if (classType == ClassType.ENUM) { addEnumMethods(codebase, item, psiClass, methods) - } else if (classType == ClassType.ANNOTATION_TYPE && !options.compatOutput && + } else if (classType == ClassType.ANNOTATION_TYPE && compatibility.explicitlyListClassRetention && modifiers.findAnnotation("java.lang.annotation.Retention") == null ) { // By policy, include explicit retention policy annotation if missing diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt index 86c72e5222af8920fce17a744c27b9e28fe26c7b..f78e825140f09212389c4b69e6c03c9f553dd3ed 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt @@ -142,6 +142,10 @@ open class TextClassItem( return typeParameterList!! } + override fun typeParameterListOwnerParent(): TypeParameterListOwner? { + return containingClass + } + override fun resolveParameter(variable: String): TypeParameterItem? { if (hasTypeVariables()) { for (t in typeParameterList().typeParameters()) { diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt index e2a6699f2da5249144723b4e48e355cf68ed44ab..a1a38d2aec7f7e47141473fd2965daccb4b65f83 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt @@ -120,6 +120,10 @@ open class TextMethodItem( override fun typeParameterList(): TypeParameterList = typeParameterList + override fun typeParameterListOwnerParent(): TypeParameterListOwner? { + return containingClass() as TextClassItem? + } + override fun resolveParameter(variable: String): TypeParameterItem? { for (t in typeParameterList.typeParameters()) { if (t.simpleName() == variable) { diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt index 08ef2eecc770a0895c9303fb3102dd29f2c0fb35..d74415b4461c5605d010933bbc5b839ed2d80bcb 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt @@ -34,6 +34,7 @@ class TextTypeItem( val codebase: TextCodebase, val type: String ) : TypeItem { + override fun toString(): String = type override fun toErasedTypeString(context: Item?): String { diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index 2836f16557ab8617bed31b0f06495288434dddbd..7495ade0b2840f859dded608ee369f4eae91fc4f 100644 --- a/src/main/resources/version.properties +++ b/src/main/resources/version.properties @@ -2,4 +2,4 @@ # Version definition # This file is read by gradle build scripts, but also packaged with metalava # as a resource for the Version classes to read. -metalavaVersion=1.2.1 +metalavaVersion=1.2.2 diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt index 8998f8ce9e4e63b53eed3c01e4419762ab9d5ffa..c96c46de341a1959d47e2f577602724258795b0e 100644 --- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt @@ -84,6 +84,7 @@ class ApiFileTest : DriverTest() { fun `Parameter Names in Java`() { // Java code which explicitly specifies parameter names check( + compatibilityMode = false, // parameter names only in v2 sourceFiles = *arrayOf( java( """ @@ -115,7 +116,7 @@ class ApiFileTest : DriverTest() { fun `Default Values Names in Java`() { // Java code which explicitly specifies parameter names check( - compatibilityMode = false, + format = FileFormat.V3, sourceFiles = *arrayOf( java( """ @@ -134,6 +135,7 @@ class ApiFileTest : DriverTest() { supportDefaultValue ), api = """ + // Signature format: 3.0 package test.pkg { public class Foo { ctor public Foo(); @@ -150,6 +152,7 @@ class ApiFileTest : DriverTest() { fun `Default Values and Names in Kotlin`() { // Kotlin code which explicitly specifies parameter names check( + format = FileFormat.V3, compatibilityMode = false, sourceFiles = *arrayOf( kotlin( @@ -193,7 +196,7 @@ class ApiFileTest : DriverTest() { ) ), api = """ - // Signature format: $CURRENT_SIGNATURE_FORMAT + // Signature format: 3.0 package test.pkg { public final class Foo { ctor public Foo(); @@ -221,7 +224,7 @@ class ApiFileTest : DriverTest() { // Testing trickier default values; regression test for problem // observed in androidx.core.util with LruCache check( - compatibilityMode = false, + format = FileFormat.V3, sourceFiles = *arrayOf( kotlin( """ @@ -278,7 +281,7 @@ class ApiFileTest : DriverTest() { androidxNonNullSource ), api = """ - // Signature format: $CURRENT_SIGNATURE_FORMAT + // Signature format: 3.0 package androidx.core.util { public final class TestKt { ctor public TestKt(); @@ -295,6 +298,8 @@ class ApiFileTest : DriverTest() { @Test fun `Basic Kotlin class`() { check( + format = FileFormat.V1, + extraArguments = arrayOf("--parameter-names=true"), sourceFiles = *arrayOf( kotlin( """ @@ -431,6 +436,7 @@ class ApiFileTest : DriverTest() { @Test fun `Kotlin Reified Methods 2`() { check( + compatibilityMode = false, sourceFiles = *arrayOf( kotlin( """ @@ -465,6 +471,7 @@ class ApiFileTest : DriverTest() { @Test fun `Suspend functions`() { check( + compatibilityMode = false, sourceFiles = *arrayOf( kotlin( """ @@ -477,7 +484,7 @@ class ApiFileTest : DriverTest() { package test.pkg { public final class TestKt { ctor public TestKt(); - method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p); + method public static suspend inline Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p); } } """, @@ -488,6 +495,7 @@ class ApiFileTest : DriverTest() { @Test fun `Kotlin Generics`() { check( + format = FileFormat.V3, sourceFiles = *arrayOf( kotlin( """ @@ -500,7 +508,9 @@ class ApiFileTest : DriverTest() { """ ) ), + compatibilityMode = false, api = """ + // Signature format: 3.0 package test.pkg { public final class Bar { ctor public Bar(); @@ -731,6 +741,7 @@ class ApiFileTest : DriverTest() { fun `JvmOverloads`() { // Regression test for https://github.com/android/android-ktx/issues/366 check( + format = FileFormat.V3, compatibilityMode = false, sourceFiles = *arrayOf( kotlin( @@ -762,6 +773,7 @@ class ApiFileTest : DriverTest() { ) ), api = """ + // Signature format: 3.0 package androidx.content { public final class TestKt { ctor public TestKt(); @@ -2715,26 +2727,26 @@ class ApiFileTest : DriverTest() { privateApi = """ package test.pkg { public class Class1 implements test.pkg.MyInterface { - ctor Class1(int); + ctor Class1(int); method public void method1(); - method void method2(); + method void method2(); method private void method3(); - method void myVarargsMethod(int, java.lang.String...); - field int field3; - field float[][] field4; - field long[] field5; + method void myVarargsMethod(int, java.lang.String...); + field int field3; + field float[][] field4; + field long[] field5; field private int field6; } - class Class2 { - ctor Class2(); + class Class2 { + ctor Class2(); method public void method4(); } private class Class2.Class3 { ctor private Class2.Class3(); method public void method5(); } - class Class4 { - ctor Class4(); + class Class4 { + ctor Class4(); method public void method5(); } public abstract interface MyInterface { @@ -2835,7 +2847,7 @@ class ApiFileTest : DriverTest() { privateApi = """ package test.pkg { public class Class1 extends test.pkg.PrivateParent implements test.pkg.MyInterface { - ctor Class1(int); + ctor Class1(int); } private abstract class Class1.AmsTask extends java.util.concurrent.FutureTask { } @@ -2847,9 +2859,9 @@ class ApiFileTest : DriverTest() { enum_constant public static final test.pkg.MyEnum BAR; enum_constant public static final test.pkg.MyEnum FOO; } - class PrivateParent { - ctor PrivateParent(); - method final java.lang.String getValue(); + class PrivateParent { + ctor PrivateParent(); + method final java.lang.String getValue(); } } """, @@ -2979,4 +2991,129 @@ class ApiFileTest : DriverTest() { checkDoclava1 = false // doclava is unaware of @suppress ) } + + @Test + fun `Check skipping implicit final or deprecated override`() { + // Regression test for 122358225 + check( + compatibilityMode = false, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + + public class Parent { + public void foo1() { } + public void foo2() { } + public void foo3() { } + public void foo4() { } + } + """ + ), + java( + """ + package test.pkg; + + public final class Child1 extends Parent { + private Child1() { } + public final void foo1() { } + public void foo2() { } + } + """ + ), + java( + """ + package test.pkg; + + /** @deprecated */ + @Deprecated + public final class Child2 extends Parent { + private Child2() { } + /** @deprecated */ + @Deprecated + public void foo3() { } + public void foo4() { } + } + """ + ), + java( + """ + package test.pkg; + + /** @deprecated */ + @Deprecated + public final class Child3 extends Parent { + private Child3() { } + public final void foo1() { } + public void foo2() { } + /** @deprecated */ + @Deprecated + public void foo3() { } + /** @deprecated */ + @Deprecated + public final void foo4() { } + } + """ + ) + ), + api = """ + package test.pkg { + public final class Child1 extends test.pkg.Parent { + } + @Deprecated public final class Child2 extends test.pkg.Parent { + } + @Deprecated public final class Child3 extends test.pkg.Parent { + } + public class Parent { + ctor public Parent(); + method public void foo1(); + method public void foo2(); + method public void foo3(); + method public void foo4(); + } + } + """ + ) + } + + @Test + fun `Ignore synchronized differences`() { + check( + compatibilityMode = false, + sourceFiles = *arrayOf( + java( + """ + package test.pkg2; + + public class Parent { + public void foo1() { } + public synchronized void foo2() { } + } + """ + ), + java( + """ + package test.pkg2; + + public class Child1 extends Parent { + private Child1() { } + public synchronized void foo1() { } + public void foo2() { } + } + """ + ) + ), + api = """ + package test.pkg2 { + public class Child1 extends test.pkg2.Parent { + } + public class Parent { + ctor public Parent(); + method public void foo1(); + method public void foo2(); + } + } + """ + ) + } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt index 1b0fc39ade2bb9e79f722bbfc9075fff426feaab..deb072b657356dd1b844f79cd7600e2167f35ed5 100644 --- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt @@ -45,7 +45,7 @@ class ApiFromTextTest : DriverTest() { @Test fun `Handle lambas as default values`() { val source = """ - // Signature format: $CURRENT_SIGNATURE_FORMAT + // Signature format: 3.0 package androidx.collection { public final class LruCacheKt { ctor public LruCacheKt(); @@ -55,6 +55,7 @@ class ApiFromTextTest : DriverTest() { """ check( + format = FileFormat.V3, compatibilityMode = false, inputKotlinStyleNulls = true, signatureSource = source, @@ -66,7 +67,7 @@ class ApiFromTextTest : DriverTest() { @Test fun `Handle enum constants as default values`() { val source = """ - // Signature format: $CURRENT_SIGNATURE_FORMAT + // Signature format: 3.0 package test.pkg { public final class Foo { ctor public Foo(); @@ -89,6 +90,7 @@ class ApiFromTextTest : DriverTest() { """ check( + format = FileFormat.V3, compatibilityMode = false, inputKotlinStyleNulls = true, signatureSource = source, @@ -100,7 +102,7 @@ class ApiFromTextTest : DriverTest() { @Test fun `Handle complex expressions as default values`() { val source = """ - // Signature format: $CURRENT_SIGNATURE_FORMAT + // Signature format: 3.0 package androidx.paging { public final class PagedListConfigKt { ctor public PagedListConfigKt(); @@ -122,6 +124,7 @@ class ApiFromTextTest : DriverTest() { """ check( + format = FileFormat.V3, compatibilityMode = false, inputKotlinStyleNulls = true, signatureSource = source, @@ -286,7 +289,7 @@ class ApiFromTextTest : DriverTest() { package test.pkg { public deprecated class MyTest { ctor public deprecated MyTest(int, int); - method public static final deprecated void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action); + method public static final deprecated void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit>); field public static deprecated java.util.List<java.lang.String> LIST; } } @@ -387,7 +390,7 @@ class ApiFromTextTest : DriverTest() { } protected static abstract deprecated interface Foo.Inner3 { method public default void method3(); - method public static void method4(int); + method public static abstract void method4(int); } } """ @@ -421,20 +424,19 @@ class ApiFromTextTest : DriverTest() { fun `Loading a signature file with annotations on classes, fields, methods and parameters`() { @Language("TEXT") val source = """ + // Signature format: 3.0 package test.pkg { - @androidx.annotation.UiThread public class MyTest { + @UiThread public class MyTest { ctor public MyTest(); - method @androidx.annotation.IntRange(from=10, to=20) public int clamp(int); - method public java.lang.Double? convert(java.lang.Float myPublicName); - field public java.lang.Number? myNumber; + method @IntRange(from=10, to=20) public int clamp(int); + method public Double? convert(Float myPublicName); + field public Number? myNumber; } } """ check( - compatibilityMode = false, - inputKotlinStyleNulls = true, - omitCommonPackages = false, + format = FileFormat.V3, signatureSource = source, api = source ) @@ -509,7 +511,7 @@ class ApiFromTextTest : DriverTest() { } } package test.pkg { - public static final class Foo extends java.lang.Enum { + public final class Foo extends java.lang.Enum { enum_constant public static final test.pkg.Foo A; enum_constant public static final test.pkg.Foo B; } @@ -551,22 +553,21 @@ class ApiFromTextTest : DriverTest() { fun `Loading a signature file with default values`() { @Language("TEXT") val source = """ + // Signature format: 3.0 package test.pkg { public final class Foo { ctor public Foo(); - method public final void error(int p = 42, java.lang.Integer? int2 = null); + method public final void error(int p = 42, Integer? int2 = null); } public class Foo2 { ctor public Foo2(); - method public void foo(java.lang.String! = null, java.lang.String! = "(Hello) World", int = 42); + method public void foo(String! = null, String! = "(Hello) World", int = 42); } } """ check( - compatibilityMode = false, - inputKotlinStyleNulls = true, - omitCommonPackages = false, + format = FileFormat.V3, signatureSource = source, api = source ) @@ -575,6 +576,7 @@ class ApiFromTextTest : DriverTest() { @Test fun `Signatures with default annotation method values`() { val source = """ + // Signature format: 3.0 package libcore.util { public @interface NonNull { method public abstract int from() default java.lang.Integer.MIN_VALUE; @@ -586,8 +588,7 @@ class ApiFromTextTest : DriverTest() { """ check( - inputKotlinStyleNulls = true, - compatibilityMode = false, + format = FileFormat.V3, signatureSource = source, api = source ) @@ -596,6 +597,7 @@ class ApiFromTextTest : DriverTest() { @Test fun `Signatures with many annotations`() { val source = """ + // Signature format: 2.0 package libcore.util { @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull { method public abstract int from() default java.lang.Integer.MIN_VALUE; @@ -611,9 +613,9 @@ class ApiFromTextTest : DriverTest() { """ check( + format = FileFormat.V2, compatibilityMode = false, signatureSource = source, - outputKotlinStyleNulls = false, api = source ) } @@ -621,19 +623,20 @@ class ApiFromTextTest : DriverTest() { @Test fun `Kotlin Properties`() { val source = """ + // Signature format: 2.0 package test.pkg { public final class Kotlin { - ctor public Kotlin(java.lang.String property1, int arg2); - method public java.lang.String getProperty1(); - method public java.lang.String getProperty2(); - method public void setProperty2(java.lang.String p); - property public final java.lang.String property2; + ctor public Kotlin(String property1, int arg2); + method public String getProperty1(); + method public String getProperty2(); + method public void setProperty2(String p); + property public final String property2; } } """ check( - compatibilityMode = true, + format = FileFormat.V2, signatureSource = source, api = source ) @@ -716,10 +719,10 @@ class ApiFromTextTest : DriverTest() { package test.pkg { public final class TestKt { ctor public TestKt(); - method public static inline <T> void a(T t); - method public static inline <reified T> void b(T t); - method public static inline <reified T> void e(T t); - method public static inline <reified T> void f(T, T t); + method public static inline <T> void a(T); + method public static inline <reified T> void b(T); + method public static inline <reified T> void e(T); + method public static inline <reified T> void f(T, T); } } """ @@ -737,7 +740,7 @@ class ApiFromTextTest : DriverTest() { package test.pkg { public final class TestKt { ctor public TestKt(); - method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p); + method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>); } } """ diff --git a/src/test/java/com/android/tools/metalava/BaselineTest.kt b/src/test/java/com/android/tools/metalava/BaselineTest.kt index fed625a971260e218011bc49c4ceef986960de79..c836c690532a07b4800b6fd96522191e48afeb0c 100644 --- a/src/test/java/com/android/tools/metalava/BaselineTest.kt +++ b/src/test/java/com/android/tools/metalava/BaselineTest.kt @@ -35,6 +35,7 @@ class BaselineTest : DriverTest() { "ReferencesHidden" ), baseline = """ + // Baseline format: 1.0 BothPackageInfoAndHtml: test/visible/package-info.java: It is illegal to provide both a package-info.java file and a package.html file for the same package IgnoringSymlink: test/pkg/sub1/sub2/sub3: @@ -154,4 +155,82 @@ class BaselineTest : DriverTest() { checkDoclava1 = false ) } + + @Test + fun `Check baseline with show annotations`() { + // When using show annotations we should only reference errors that are present in the delta + check( + includeSystemApiAnnotations = true, + extraArguments = arrayOf( + ARG_SHOW_ANNOTATION, "android.annotation.TestApi", + ARG_HIDE_PACKAGE, "android.annotation", + ARG_HIDE_PACKAGE, "android.support.annotation", + ARG_API_LINT + ), + baseline = """ + // Baseline format: 1.0 + PairedRegistration: android.pkg.RegistrationMethods#registerUnpaired2Callback(Runnable): + Found registerUnpaired2Callback but not unregisterUnpaired2Callback in android.pkg.RegistrationMethods + """, + updateBaseline = true, + warnings = "", + sourceFiles = *arrayOf( + java( + """ + package android.pkg; + import android.annotation.TestApi; + + public class RegistrationMethods { + // Here we have 3 sets of correct registrations: both register and unregister are + // present from the full API that is part of the Test API. + // There is also 2 incorrect registrations: one in the base API and one in the test + // API. + // + // In the test API, we expect to only see the single missing test API one. The missing + // base one should only be in the base baseline. + // + // Furthermore, this test makes sure that when the set of methods to be consulted spans + // the two APIs (one hidden, one not) the code correctly finds both; e.g. iteration of + // the test API does see the full set of methods even if they're left out of the + // signature files and baselines. + + public void registerOk1Callback(Runnable r) { } + public void unregisterOk1Callback(Runnable r) { } + + /** @hide */ + @TestApi + public void registerOk2Callback(Runnable r) { } + /** @hide */ + @TestApi + public void unregisterOk2Callback(Runnable r) { } + + // In the Test API, both methods are present + public void registerOk3Callback(Runnable r) { } + /** @hide */ + @TestApi + public void unregisterOk3Callback(Runnable r) { } + + public void registerUnpaired1Callback(Runnable r) { } + + /** @hide */ + @TestApi + public void registerUnpaired2Callback(Runnable r) { } + } + """ + ), + testApiSource + ), + api = """ + package android.pkg { + public class RegistrationMethods { + method public void registerOk2Callback(java.lang.Runnable); + method public void registerUnpaired2Callback(java.lang.Runnable); + method public void unregisterOk2Callback(java.lang.Runnable); + method public void unregisterOk3Callback(java.lang.Runnable); + } + } + """, + checkDoclava1 = false + ) + } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt index 1853c57d3954bb0117f302ce96df1a73bcaa9770..d1aa93154befa3396cff152156d439a0836ad5a9 100644 --- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt +++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt @@ -2115,14 +2115,13 @@ CompatibilityCheckTest : DriverTest() { compatibilityMode = true, inputKotlinStyleNulls = true, checkCompatibilityApi = """ - // Signature format: 2.0 package androidx.annotation { - @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo { + public abstract class RestrictTo implements java.lang.annotation.Annotation { method public abstract androidx.annotation.RestrictTo.Scope[] value(); } - public static enum RestrictTo.Scope { - enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID; + public static final class RestrictTo.Scope extends java.lang.Enum { + enum_constant deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID; enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY; enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP; enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES; diff --git a/src/test/java/com/android/tools/metalava/ConvertTest.kt b/src/test/java/com/android/tools/metalava/ConvertTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e4a71f73ecc2996b247dbaca9ee4172135f6b9f9 --- /dev/null +++ b/src/test/java/com/android/tools/metalava/ConvertTest.kt @@ -0,0 +1,778 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import org.junit.Test + +class ConvertTest : DriverTest() { + + @Test + fun `Test conversion flag`() { + check( + compatibilityMode = true, + convertToJDiff = listOf( + ConvertData( + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + } + } + """, + outputFile = + """ + <api> + <package name="test.pkg" + > + <class name="MyTest1" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <constructor name="MyTest1" + type="test.pkg.MyTest1" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </constructor> + </class> + </package> + </api> + """ + ), + ConvertData( + fromApi = + """ + package test.pkg { + public class MyTest2 { + } + } + """, + outputFile = + """ + <api> + <package name="test.pkg" + > + <class name="MyTest2" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </class> + </package> + </api> + """ + ) + ) + ) + } + + @Test + fun `Test convert new with compat mode and api strip`() { + check( + compatibilityMode = true, + convertToJDiff = listOf( + ConvertData( + strip = true, + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """, + baseApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + field public deprecated java.lang.Number myNumber; + } + } + """, + outputFile = + """ + <api> + <package name="test.pkg" + > + <class name="MyTest1" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <method name="convert" + return="java.lang.Double" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.Float"> + </parameter> + </method> + </class> + <class name="MyTest2" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <constructor name="MyTest2" + type="test.pkg.MyTest2" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </constructor> + <method name="convert" + return="java.lang.Double" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.Float"> + </parameter> + </method> + </class> + </package> + <package name="test.pkg.new" + > + <interface name="MyInterface" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </interface> + <class name="MyTest3" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </class> + <class name="MyTest4" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <implements name="test.pkg.new.MyInterface"> + </implements> + </class> + </package> + </api> + """ + ) + ) + ) + } + + @Test + fun `Test convert new without compat mode and no strip`() { + check( + compatibilityMode = false, + convertToJDiff = listOf( + ConvertData( + strip = false, + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """, + baseApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + field public deprecated java.lang.Number myNumber; + } + } + """, + outputFile = + """ + <api name="convert-output1"> + <package name="test.pkg" + > + <class name="MyTest1" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <method name="convert" + return="java.lang.Double" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.Float"> + </parameter> + </method> + <field name="ANY_CURSOR_ITEM_TYPE" + type="java.lang.String" + transient="false" + volatile="false" + value=""vnd.android.cursor.item/*"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" + > + </field> + </class> + <class name="MyTest2" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <constructor name="MyTest2" + type="test.pkg.MyTest2" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </constructor> + <method name="convert" + return="java.lang.Double" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.Float"> + </parameter> + </method> + </class> + </package> + <package name="test.pkg.new" + > + <interface name="MyInterface" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </interface> + <class name="MyTest3" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <implements name="java.util.List"> + </implements> + </class> + <class name="MyTest4" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <implements name="test.pkg.new.MyInterface"> + </implements> + </class> + </package> + </api> + """ + ) + ) + ) + } + + @Test + fun `Test convert nothing new`() { + check( + expectedOutput = "No API change detected, not generating diff", + compatibilityMode = true, + convertToJDiff = listOf( + ConvertData( + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public class MyTest3 { + } + } + """, + baseApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public class MyTest3 { + } + } + """, + outputFile = + """ + """ + ) + ) + ) + } + + @Test + fun `Test doclava compat`() { + // A few more differences + check( + compatibilityMode = true, + convertToJDiff = listOf( + ConvertData( + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public void method(java.util.List<String>); + field protected static final java.lang.String CRLF = "\r\n"; + field protected static final byte[] CRLF_BYTES; + } + } + """, + outputFile = + """ + <api> + <package name="test.pkg" + > + <class name="MyTest1" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <constructor name="MyTest1" + type="test.pkg.MyTest1" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </constructor> + <method name="method" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.util.List<String>"> + </parameter> + </method> + <field name="CRLF" + type="java.lang.String" + transient="false" + volatile="false" + value=""\r\n"" + static="true" + final="true" + deprecated="not deprecated" + visibility="protected" + > + </field> + <field name="CRLF_BYTES" + type="byte[]" + transient="false" + volatile="false" + value="null" + static="true" + final="true" + deprecated="not deprecated" + visibility="protected" + > + </field> + </class> + </package> + </api> + """ + ) + ) + ) + } + + @Test + fun `Test convert new to v2 without compat mode and no strip`() { + check( + compatibilityMode = false, + convertToJDiff = listOf( + ConvertData( + strip = false, + format = FileFormat.V2, + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """, + baseApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + field public deprecated java.lang.Number myNumber; + } + } + """, + outputFile = + """ + // Signature format: 2.0 + package test.pkg { + public class MyTest1 { + method public Double convert(Float); + field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + } + public class MyTest2 { + ctor public MyTest2(); + method public Double convert(Float); + } + } + package test.pkg.new { + public interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """ + ) + ) + ) + } + + @Test + fun `Test convert new to v1 signatures with compat mode and no strip`() { + // Output is similar to the v2 format, but with fully qualified java.lang types + // and fields not included + check( + compatibilityMode = false, + convertToJDiff = listOf( + ConvertData( + strip = false, + format = FileFormat.V1, + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public abstract interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """, + baseApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + field public deprecated java.lang.Number myNumber; + } + } + """, + outputFile = + """ + package test.pkg { + public class MyTest1 { + method public java.lang.Double convert(java.lang.Float); + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public abstract interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """ + ) + ) + ) + } + + @Test + fun `Test convert v2 to v1`() { + check( + compatibilityMode = false, + convertToJDiff = listOf( + ConvertData( + strip = false, + format = FileFormat.V1, + fromApi = + """ + // Signature format: 2.0 + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method @Deprecated public int clamp(int); + method public Double convert(Float); + field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field @Deprecated public Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public Double convert(Float); + } + } + package test.pkg.new { + public interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """, + outputFile = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public abstract interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """ + ) + ) + ) + } + + @Test + fun `Test convert v1 to v2`() { + check( + compatibilityMode = false, + convertToJDiff = listOf( + ConvertData( + strip = false, + format = FileFormat.V2, + fromApi = + """ + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method public deprecated int clamp(int); + method public java.lang.Double convert(java.lang.Float); + field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field public deprecated java.lang.Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public java.lang.Double convert(java.lang.Float); + } + } + package test.pkg.new { + public abstract interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """, + outputFile = + """ + // Signature format: 2.0 + package test.pkg { + public class MyTest1 { + ctor public MyTest1(); + method @Deprecated public int clamp(int); + method public Double convert(Float); + field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; + field @Deprecated public Number myNumber; + } + public class MyTest2 { + ctor public MyTest2(); + method public Double convert(Float); + } + } + package test.pkg.new { + public interface MyInterface { + } + public abstract class MyTest3 implements java.util.List { + } + public abstract class MyTest4 implements test.pkg.new.MyInterface { + } + } + """ + ) + ) + ) + } +} \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt index 431e4f8178d95dc49730098ef91ee2f1b9d78fad..39a11b12899a5fa47d2407e78e58aa99e502cd45 100644 --- a/src/test/java/com/android/tools/metalava/DriverTest.kt +++ b/src/test/java/com/android/tools/metalava/DriverTest.kt @@ -57,6 +57,7 @@ import java.net.URL import kotlin.text.Charsets.UTF_8 const val CHECK_OLD_DOCLAVA_TOO = false +const val CHECK_JDIFF = true const val CHECK_STUB_COMPILATION = false abstract class DriverTest { @@ -180,12 +181,13 @@ abstract class DriverTest { return System.getenv("JAVA_HOME") } - /** JDiff file conversion candidates */ + /** File conversion tasks */ data class ConvertData( val fromApi: String, - val toXml: String, + val outputFile: String, val baseApi: String? = null, - val strip: Boolean = true + val strip: Boolean = true, + val format: FileFormat = FileFormat.JDIFF ) protected fun check( @@ -220,18 +222,18 @@ abstract class DriverTest { /** Whether the stubs should be written as documentation stubs instead of plain stubs. Decides * whether the stubs include @doconly elements, uses rewritten/migration annotations, etc */ docStubs: Boolean = false, + /** Signature file format */ + format: FileFormat? = null, /** Whether to run in doclava1 compat mode */ - compatibilityMode: Boolean = true, + compatibilityMode: Boolean = format == null || format == FileFormat.V1, /** Whether to trim the output (leading/trailing whitespace removal) */ trim: Boolean = true, /** Whether to remove blank lines in the output (the signature file usually contains a lot of these) */ stripBlankLines: Boolean = true, /** Warnings expected to be generated when analyzing these sources */ warnings: String? = "", - /** Signature file format */ - format: Int? = null, /** Whether to run doclava1 on the test output and assert that the output is identical */ - checkDoclava1: Boolean = compatibilityMode && (format == null || format < 2), + checkDoclava1: Boolean = compatibilityMode && (format == null || format == FileFormat.V1), checkCompilation: Boolean = false, /** Annotations to merge in (in .xml format) */ @Language("XML") mergeXmlAnnotations: String? = null, @@ -266,7 +268,7 @@ abstract class DriverTest { /** Additional arguments to supply */ extraArguments: Array<String> = emptyArray(), /** Whether we should emit Kotlin-style null signatures */ - outputKotlinStyleNulls: Boolean = !compatibilityMode, + outputKotlinStyleNulls: Boolean = format != null && format.useKotlinStyleNulls(), /** Whether we should interpret API files being read as having Kotlin-style nullness types */ inputKotlinStyleNulls: Boolean = false, /** Whether we should omit java.lang. etc from signature files */ @@ -716,31 +718,45 @@ abstract class DriverTest { emptyArray() } - val convertToJDiffFiles = mutableListOf<Options.ConvertFile>() - val convertToJDiffArgs = if (convertToJDiff.isNotEmpty()) { + val convertFiles = mutableListOf<Options.ConvertFile>() + val convertArgs = if (convertToJDiff.isNotEmpty()) { val args = mutableListOf<String>() var index = 1 for (convert in convertToJDiff) { val signature = convert.fromApi val base = convert.baseApi - val convertSig = temporaryFolder.newFile("jdiff-signatures$index.txt") + val convertSig = temporaryFolder.newFile("convert-signatures$index.txt") convertSig.writeText(signature.trimIndent(), Charsets.UTF_8) - val output = temporaryFolder.newFile("jdiff-output$index.xml") + val extension = convert.format.preferredExtension() + val output = temporaryFolder.newFile("convert-output$index$extension") val baseFile = if (base != null) { - val baseFile = temporaryFolder.newFile("jdiff-signatures$index-base.txt") + val baseFile = temporaryFolder.newFile("convert-signatures$index-base.txt") baseFile.writeText(base.trimIndent(), Charsets.UTF_8) baseFile } else { null } - convertToJDiffFiles += Options.ConvertFile(convertSig, output, baseFile, strip = true) + convertFiles += Options.ConvertFile(convertSig, output, baseFile, + strip = true, outputFormat = convert.format) index++ if (baseFile != null) { - args += if (convert.strip) "-new_api" else ARG_CONVERT_NEW_TO_JDIFF + args += + when { + convert.format == FileFormat.V1 -> ARG_CONVERT_NEW_TO_V1 + convert.format == FileFormat.V2 -> ARG_CONVERT_NEW_TO_V2 + convert.strip -> "-new_api" + else -> ARG_CONVERT_NEW_TO_JDIFF + } args += baseFile.path } else { - args += if (convert.strip) "-convert2xml" else ARG_CONVERT_TO_JDIFF + args += + when { + convert.format == FileFormat.V1 -> ARG_CONVERT_TO_V1 + convert.format == FileFormat.V2 -> ARG_CONVERT_TO_V2 + convert.strip -> "-convert2xml" + else -> ARG_CONVERT_TO_JDIFF + } } args += convertSig.path args += output.path @@ -882,7 +898,7 @@ abstract class DriverTest { } val signatureFormatArgs = if (format != null) { - arrayOf("$ARG_FORMAT=v$format") + arrayOf(format.outputFlag()) } else { emptyArray() } @@ -945,7 +961,7 @@ abstract class DriverTest { *checkCompatibilityRemovedReleasedArguments, *proguardKeepArguments, *manifestFileArgs, - *convertToJDiffArgs, + *convertArgs, *applyApiLevelsXmlArgs, *baselineArgs, *showAnnotationArguments, @@ -997,11 +1013,12 @@ abstract class DriverTest { assertEquals(stripComments(baseline, stripLineComments = false).trimIndent(), actualText) } - if (convertToJDiffFiles.isNotEmpty()) { + if (convertFiles.isNotEmpty()) { for (i in 0 until convertToJDiff.size) { - val expected = convertToJDiff[i].toXml - val converted = convertToJDiffFiles[i].toXmlFile + val expected = convertToJDiff[i].outputFile + val converted = convertFiles[i].outputFile if (convertToJDiff[i].baseApi != null && + compatibilityMode && actualOutput.contains("No API change detected, not generating diff")) { continue } @@ -1278,11 +1295,18 @@ abstract class DriverTest { assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText) } + if (CHECK_JDIFF && apiXmlFile != null && convertToJDiff.isNotEmpty()) { + // Parse the XML file with jdiff too + } + if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && convertToJDiff.isNotEmpty()) { var index = 1 for (convert in convertToJDiff) { + if (convert.format != FileFormat.JDIFF) { + continue + } val signature = convert.fromApi - val expectedXml = convert.toXml + val expectedXml = convert.outputFile val base = convert.baseApi val strip = convert.strip val convertSig = temporaryFolder.newFile("doclava-jdiff-signatures$index.txt") diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt index 9dcdd219b37c949f5987e61c1e7e2d0e148b6800..bad1733519bae0fd7cbbe3a1dcf3961ee35ae9a7 100644 --- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt +++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt @@ -16,7 +16,6 @@ package com.android.tools.metalava -import org.junit.Ignore import org.junit.Test @SuppressWarnings("ALL") // Sample code @@ -343,7 +342,6 @@ class ExtractAnnotationsTest : DriverTest() { ) } - @Ignore("Not working reliably -- fails when run and passes when debugged...") @Test fun `Include merged annotations in exported source annotations`() { check( @@ -352,7 +350,7 @@ class ExtractAnnotationsTest : DriverTest() { outputKotlinStyleNulls = false, includeSystemApiAnnotations = false, omitCommonPackages = false, - warnings = "error: Unexpected reference to Nonexistent.Field [AnnotationExtraction]", + warnings = "error: Unexpected reference to Nonexistent.Field [InternalError]", sourceFiles = *arrayOf( java( """ @@ -361,6 +359,17 @@ class ExtractAnnotationsTest : DriverTest() { public class MyTest { public void test(int arg) { } }""" + ), + java( + """ + package java.util; + public class Calendar { + public static final int ERA = 1; + public static final int YEAR = 2; + public static final int MONTH = 3; + public static final int WEEK_OF_YEAR = 4; + } + """ ) ), mergeXmlAnnotations = """<?xml version="1.0" encoding="UTF-8"?> diff --git a/src/test/java/com/android/tools/metalava/FileFormatTest.kt b/src/test/java/com/android/tools/metalava/FileFormatTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..98de4d27dd889e9b008ec2563d4969b104ff75ac --- /dev/null +++ b/src/test/java/com/android/tools/metalava/FileFormatTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import org.junit.Assert.assertSame +import org.junit.Test + +class FileFormatTest { + @Test + fun `Check format parsing`() { + assertSame(FileFormat.V1, FileFormat.parseHeader(""" + package test.pkg { + public class MyTest { + ctor public MyTest(); + } + } + """.trimIndent())) + + assertSame(FileFormat.V2, FileFormat.parseHeader(""" + // Signature format: 2.0 + package libcore.util { + @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull { + method public abstract int from() default java.lang.Integer.MIN_VALUE; + method public abstract int to() default java.lang.Integer.MAX_VALUE; + } + } + """.trimIndent())) + + assertSame(FileFormat.V3, FileFormat.parseHeader(""" + // Signature format: 3.0 + package androidx.collection { + public final class LruCacheKt { + ctor public LruCacheKt(); + } + } + """.trimIndent())) + + assertSame(FileFormat.V2, FileFormat.parseHeader( + "// Signature format: 2.0\r\n" + + "package libcore.util {\\r\n" + + " @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull {\r\n" + + " method public abstract int from() default java.lang.Integer.MIN_VALUE;\r\n" + + " method public abstract int to() default java.lang.Integer.MAX_VALUE;\r\n" + + " }\r\n" + + "}\r\n")) + + assertSame(FileFormat.BASELINE, FileFormat.parseHeader(""" + // Baseline format: 1.0 + BothPackageInfoAndHtml: test/visible/package-info.java: + It is illegal to provide both a package-info.java file and a package.html file for the same package + IgnoringSymlink: test/pkg/sub1/sub2/sub3: + Ignoring symlink during package.html discovery directory traversal + """.trimIndent())) + + assertSame(FileFormat.JDIFF, FileFormat.parseHeader(""" + <api> + <package name="test.pkg" + > + </api> + """.trimIndent())) + + assertSame(FileFormat.JDIFF, FileFormat.parseHeader(""" + <?xml version="1.0" encoding="utf-8"?> + <api> + <package name="test.pkg" + > + </api> + """.trimIndent())) + + assertSame(FileFormat.SINCE_XML, FileFormat.parseHeader(""" + <?xml version="1.0" encoding="utf-8"?> + <api version="2"> + <class name="android/hardware/Camera" since="1" deprecated="21"> + <method name="<init>()V"/> + <method name="addCallbackBuffer([B)V" since="8"/> + <method name="getLogo()Landroid/graphics/drawable/Drawable;"/> + <field name="ACTION_NEW_VIDEO" since="14" deprecated="25"/> + </class> + </api> + """.trimIndent())) + + assertSame(FileFormat.UNKNOWN, FileFormat.parseHeader(""" + blah blah + """.trimIndent())) + + assertSame(FileFormat.UNKNOWN, FileFormat.parseHeader(""" + <?xml version="1.0" encoding="utf-8"?> + <manifest /> + """.trimIndent())) + } +} \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt index 2118a9251c464064837a6e05f4ad54aade93d57f..efc9315915b6cf93d7a63a6b67bcad36287e18ff 100644 --- a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt +++ b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt @@ -111,6 +111,54 @@ class JDiffXmlTest : DriverTest() { ) } + @Test + fun `Abstract interfaces`() { + check( + compatibilityMode = true, + format = FileFormat.V2, + signatureSource = + """ + // Signature format: 2.0 + package test.pkg { + public interface MyBaseInterface { + method public abstract void fun(int, String); + } + } + """, + apiXml = + """ + <api> + <package name="test.pkg" + > + <interface name="MyBaseInterface" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <method name="fun" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="int"> + </parameter> + <parameter name="null" type="java.lang.String"> + </parameter> + </method> + </interface> + </package> + </api> + """ + ) + } + @Test fun `Test generics, superclasses and interfaces`() { val source = """ @@ -170,6 +218,30 @@ class JDiffXmlTest : DriverTest() { deprecated="not deprecated" visibility="public" > + <method name="valueOf" + return="test.pkg.Foo" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.String"> + </parameter> + </method> + <method name="values" + return="test.pkg.Foo[]" + abstract="false" + native="false" + synchronized="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" + > + </method> <constructor name="Foo" type="test.pkg.Foo" static="false" @@ -437,6 +509,30 @@ class JDiffXmlTest : DriverTest() { deprecated="not deprecated" visibility="public" > + <method name="valueOf" + return="test.pkg.Foo" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" + > + <parameter name="null" type="java.lang.String"> + </parameter> + </method> + <method name="values" + return="test.pkg.Foo[]" + abstract="false" + native="false" + synchronized="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" + > + </method> <constructor name="Foo" type="test.pkg.Foo" static="false" @@ -559,75 +655,6 @@ class JDiffXmlTest : DriverTest() { ) } - @Test - fun `Test conversion flag`() { - check( - compatibilityMode = true, - convertToJDiff = listOf( - ConvertData( - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - } - } - """, - toXml = - """ - <api> - <package name="test.pkg" - > - <class name="MyTest1" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <constructor name="MyTest1" - type="test.pkg.MyTest1" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </constructor> - </class> - </package> - </api> - """ - ), - ConvertData( - fromApi = - """ - package test.pkg { - public class MyTest2 { - } - } - """, - toXml = - """ - <api> - <package name="test.pkg" - > - <class name="MyTest2" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </class> - </package> - </api> - """ - ) - ) - ) - } - @Test fun `Generics in interfaces`() { check( @@ -685,7 +712,7 @@ class JDiffXmlTest : DriverTest() { > <interface name="AbstractList" extends="test.pkg.List<A,B,C>" - abstract="false" + abstract="true" static="false" final="false" deprecated="not deprecated" @@ -694,7 +721,7 @@ class JDiffXmlTest : DriverTest() { </interface> <interface name="ConcreteList" extends="test.pkg.AbstractList<D,E,F>" - abstract="false" + abstract="true" static="false" final="false" deprecated="not deprecated" @@ -702,7 +729,7 @@ class JDiffXmlTest : DriverTest() { > </interface> <interface name="List" - abstract="false" + abstract="true" static="false" final="false" deprecated="not deprecated" @@ -820,427 +847,67 @@ class JDiffXmlTest : DriverTest() { } @Test - fun `Test convert new with compat mode and api strip`() { + fun `Interface extends, compat mode`() { check( compatibilityMode = true, - convertToJDiff = listOf( - ConvertData( - strip = true, - fromApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public deprecated int clamp(int); - method public java.lang.Double convert(java.lang.Float); - field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; - field public deprecated java.lang.Number myNumber; - } - public class MyTest2 { - ctor public MyTest2(); - method public java.lang.Double convert(java.lang.Float); - } - } - package test.pkg.new { - public interface MyInterface { - } - public abstract class MyTest3 implements java.util.List { - } - public abstract class MyTest4 implements test.pkg.new.MyInterface { - } - } - """, - baseApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public deprecated int clamp(int); - field public deprecated java.lang.Number myNumber; - } - } - """, - toXml = - """ - <api> - <package name="test.pkg" - > - <class name="MyTest1" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <method name="convert" - return="java.lang.Double" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <parameter name="null" type="java.lang.Float"> - </parameter> - </method> - </class> - <class name="MyTest2" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <constructor name="MyTest2" - type="test.pkg.MyTest2" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </constructor> - <method name="convert" - return="java.lang.Double" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <parameter name="null" type="java.lang.Float"> - </parameter> - </method> - </class> - </package> - <package name="test.pkg.new" - > - <interface name="MyInterface" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </interface> - <class name="MyTest3" - extends="java.lang.Object" - abstract="true" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </class> - <class name="MyTest4" - extends="java.lang.Object" - abstract="true" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <implements name="test.pkg.new.MyInterface"> - </implements> - </class> - </package> - </api> - """ - ) - ) + format = FileFormat.V1, + signatureSource = """ + // Signature format: 2.0 + package android.companion { + public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { + } + } + """, + apiXml = + """ + <api> + <package name="android.companion" + > + <interface name="DeviceFilter" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + <implements name="android.os.Parcelable"> + </implements> + </interface> + </package> + </api> + """ ) } @Test - fun `Test convert new without compat mode and no strip`() { + fun `Interface extends, non-compat mode`() { check( compatibilityMode = false, - convertToJDiff = listOf( - ConvertData( - strip = false, - fromApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public deprecated int clamp(int); - method public java.lang.Double convert(java.lang.Float); - field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; - field public deprecated java.lang.Number myNumber; - } - public class MyTest2 { - ctor public MyTest2(); - method public java.lang.Double convert(java.lang.Float); - } - } - package test.pkg.new { - public interface MyInterface { - } - public abstract class MyTest3 implements java.util.List { - } - public abstract class MyTest4 implements test.pkg.new.MyInterface { - } - } - """, - baseApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public deprecated int clamp(int); - field public deprecated java.lang.Number myNumber; - } - } - """, - toXml = - """ - <api> - <package name="test.pkg" - > - <class name="MyTest1" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <method name="convert" - return="java.lang.Double" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <parameter name="null" type="java.lang.Float"> - </parameter> - </method> - <field name="ANY_CURSOR_ITEM_TYPE" - type="java.lang.String" - transient="false" - volatile="false" - value=""vnd.android.cursor.item/*"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" - > - </field> - </class> - <class name="MyTest2" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <constructor name="MyTest2" - type="test.pkg.MyTest2" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </constructor> - <method name="convert" - return="java.lang.Double" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <parameter name="null" type="java.lang.Float"> - </parameter> - </method> - </class> - </package> - <package name="test.pkg.new" - > - <interface name="MyInterface" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </interface> - <class name="MyTest3" - extends="java.lang.Object" - abstract="true" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <implements name="java.util.List"> - </implements> - </class> - <class name="MyTest4" - extends="java.lang.Object" - abstract="true" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <implements name="test.pkg.new.MyInterface"> - </implements> - </class> - </package> - </api> - """ - ) - ) - ) - } - - @Test - fun `Test convert nothing new`() { - check( - expectedOutput = "No API change detected, not generating diff", - compatibilityMode = true, - convertToJDiff = listOf( - ConvertData( - fromApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public deprecated int clamp(int); - method public java.lang.Double convert(java.lang.Float); - field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; - field public deprecated java.lang.Number myNumber; - } - public class MyTest2 { - ctor public MyTest2(); - method public java.lang.Double convert(java.lang.Float); - } - } - package test.pkg.new { - public class MyTest3 { - } - } - """, - baseApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public deprecated int clamp(int); - method public java.lang.Double convert(java.lang.Float); - field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; - field public deprecated java.lang.Number myNumber; - } - public class MyTest2 { - ctor public MyTest2(); - method public java.lang.Double convert(java.lang.Float); - } - } - package test.pkg.new { - public class MyTest3 { - } - } - """, - toXml = - """ - """ - ) - ) - ) - } - - @Test - fun `Test doclava compat`() { - // A few more differences - check( - compatibilityMode = true, - convertToJDiff = listOf( - ConvertData( - fromApi = - """ - package test.pkg { - public class MyTest1 { - ctor public MyTest1(); - method public void method(java.util.List<String>); - field protected static final java.lang.String CRLF = "\r\n"; - field protected static final byte[] CRLF_BYTES; - } - } - """, - toXml = - """ - <api> - <package name="test.pkg" - > - <class name="MyTest1" - extends="java.lang.Object" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <constructor name="MyTest1" - type="test.pkg.MyTest1" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - </constructor> - <method name="method" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" - > - <parameter name="null" type="java.util.List<String>"> - </parameter> - </method> - <field name="CRLF" - type="java.lang.String" - transient="false" - volatile="false" - value=""\r\n"" - static="true" - final="true" - deprecated="not deprecated" - visibility="protected" - > - </field> - <field name="CRLF_BYTES" - type="byte[]" - transient="false" - volatile="false" - value="null" - static="true" - final="true" - deprecated="not deprecated" - visibility="protected" - > - </field> - </class> - </package> - </api> - """ - ) - ) + format = FileFormat.V2, + signatureSource = """ + // Signature format: 2.0 + package android.companion { + public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { + } + } + """, + apiXml = + """ + <api> + <package name="android.companion" + > + <interface name="DeviceFilter" + extends="android.os.Parcelable" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" + > + </interface> + </package> + </api> + """ ) } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt index c43bfc6bb09f7c6d1176cfa85f4aa6c838d6faae..90332ec64124717eb0f022c5f4e79b38c92950d9 100644 --- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt +++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt @@ -23,7 +23,7 @@ class NullnessMigrationTest : DriverTest() { @Test fun `Test Kotlin-style null signatures`() { check( - compatibilityMode = false, + format = FileFormat.V3, sourceFiles = *arrayOf( java( """ @@ -42,6 +42,7 @@ class NullnessMigrationTest : DriverTest() { androidxNullableSource ), api = """ + // Signature format: 3.0 package test.pkg { public class MyTest { ctor public MyTest(); @@ -327,7 +328,7 @@ class NullnessMigrationTest : DriverTest() { @Test fun `Check type use annotations`() { check( - format = 2, // compat=false, kotlin-style-nulls=false + format = FileFormat.V2, // compat=false, kotlin-style-nulls=false sourceFiles = *arrayOf( java( """ diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt index 8dfcedc3f7ec0b9c396184af600e2ec68a5758e1..dcaf7ee90ae403e3b4ad514c20ad3b89133914a9 100644 --- a/src/test/java/com/android/tools/metalava/OptionsTest.kt +++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt @@ -231,6 +231,15 @@ JDiff: --convert-new-to-jdiff <old> <new> <xml> Reads in the given old and new api files, computes the difference, and writes out only the new parts of the API in the JDiff XML format. +--convert-to-v1 <sig> <sig> Reads in the given signature file and writes it + out as a signature file in the original + v1/doclava format. +--convert-to-v2 <sig> <sig> Reads in the given signature file and writes it + out as a signature file in the new signature + format, v2. +--convert-new-to-v2 <old> <new> <sig> Reads in the given old and new api files, + computes the difference, and writes out only the + new parts of the API in the v2 format. Statistics: --annotation-coverage-stats Whether metalava should emit coverage statistics diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt index 4c17cd51d23f5557087190a6b625410be7c33a72..c2e6832ab1a77423b6e102ce0096756d0bac6d31 100644 --- a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt +++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt @@ -246,7 +246,7 @@ class ShowAnnotationTest : DriverTest() { ), // Empty API: showUnannotated=false api = """ - """, + """.trimIndent(), includeSystemApiAnnotations = true, extraArguments = arrayOf( ARG_SHOW_ANNOTATION, "android.annotation.TestApi", diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt index dbfb6f0654810f4c77ddb1a68b40ace0a32f52d1..e48c39935b30a4a23632d0976af086d24401d473 100644 --- a/src/test/java/com/android/tools/metalava/StubsTest.kt +++ b/src/test/java/com/android/tools/metalava/StubsTest.kt @@ -1441,7 +1441,7 @@ class StubsTest : DriverTest() { package test.pkg { public class Foo { ctor public Foo(); - method public void foo(int, java.util.Map<java.lang.String, java.lang.String>!); + method public void foo(int, java.util.Map<java.lang.String, java.lang.String>); } } """ @@ -1450,7 +1450,7 @@ class StubsTest : DriverTest() { package test.pkg { public class Foo { ctor public Foo(); - method public void foo(int, java.util.Map<java.lang.String,java.lang.String>!); + method public void foo(int, java.util.Map<java.lang.String,java.lang.String>); } } """ @@ -2242,18 +2242,18 @@ class StubsTest : DriverTest() { } public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> { ctor public Generics.MyClass(); - method public java.util.Map<X,java.util.Map<Y,java.lang.String>>! createMap(java.util.List<X>!) throws java.io.IOException; - method public java.util.List<X>! foo(); + method public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X>) throws java.io.IOException; + method public java.util.List<X> foo(); } public static interface Generics.PublicInterface<A, B> { - method public java.util.Map<A,java.util.Map<B,java.lang.String>>! createMap(java.util.List<A>!) throws java.io.IOException; + method public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A>) throws java.io.IOException; } public abstract class Generics.PublicParent<A, B extends java.lang.Number> { ctor public Generics.PublicParent(); - method protected abstract java.util.List<A>! foo(); + method protected abstract java.util.List<A> foo(); } } - """, + """, source = """ package test.pkg; @SuppressWarnings({"unchecked", "deprecation", "all"}) @@ -3635,7 +3635,7 @@ class StubsTest : DriverTest() { package test.pkg { public class Foo { ctor public Foo(); - method @Deprecated protected boolean inClass(String!); + method @Deprecated protected boolean inClass(String); } } """, diff --git a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt index 6d5cbc867f4eece22558c6aea8c6b78929c4b445..112d51304ecd3259c62bf39fb117917882a2475e 100644 --- a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt +++ b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt @@ -17,14 +17,14 @@ package com.android.tools.metalava.model import com.android.tools.metalava.JAVA_LANG_STRING -import com.android.tools.metalava.options +import com.android.tools.metalava.compatibility import com.google.common.truth.Truth.assertThat import org.junit.Test class TypeItemTest { @Test fun test() { - options.omitCommonPackages = true + compatibility.omitCommonPackages = true assertThat(TypeItem.shortenTypes("@android.support.annotation.Nullable")).isEqualTo("@Nullable") assertThat(TypeItem.shortenTypes(JAVA_LANG_STRING)).isEqualTo("String") assertThat(TypeItem.shortenTypes("java.lang.reflect.Method")).isEqualTo("java.lang.reflect.Method") diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt index d11074f11add696045270b03b3fc1d1dafe3e734..476b6548033e422c2edfd7b0e05fd5ad8dd53765 100644 --- a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt +++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt @@ -77,7 +77,7 @@ class TextTypeItemTest { method public D build(); } } - """, false + """.trimIndent(), false ) val cls = codebase.findClass("androidx.navigation.NavDestinationBuilder") val method = cls?.findMethod("build", "") as TextMethodItem @@ -113,7 +113,7 @@ class TextTypeItemTest { method public D build(); } } - """, false + """.trimIndent(), false ) val cls = codebase.findClass("test.pkg.TestClass") val method = cls?.findMethod("build", "") as TextMethodItem @@ -145,7 +145,7 @@ class TextTypeItemTest { method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); } } - """, false + """.trimIndent(), false ) val cls = codebase.findClass("test.pkg.EnumMap") val method = cls?.findMethod("clone", "") as TextMethodItem diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt index 9a00d2927801e8c6d4cc42616f7a3647d7826e1f..8dc559cd463fffd8feb41b81a9390ded0278930d 100644 --- a/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt +++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt @@ -47,7 +47,7 @@ class TextTypeParameterItemTest { method public D build(); } } - """, false + """.trimIndent(), false ) val cls = codebase.findClass("androidx.navigation.NavDestinationBuilder") val method = cls?.findMethod("build", "") as TextMethodItem