From bff0085ac2b8564eb847d066aedb8bb123fac706 Mon Sep 17 00:00:00 2001 From: Tor Norbye <tnorbye@google.com> Date: Tue, 5 Mar 2019 13:57:31 -0800 Subject: [PATCH] 127458147: Include IntDef annotations in current.txt Adds a new mode, off by default, which lets you either include typedef annotation references in the signature files (without the typedef classes themselves), or inline the typedef constants themselves into all the annotated sites. Usage: --typedefs-in-signatures <ref|inline> Whether to include typedef annotations in signature files. `--typedefs-in-signatures ref` will include just a reference to the typedef class, which is not itself part of the API and is not included as a class, and `--typedefs-in-signatures inline` will include the constants themselves into each usage site. You can also supply `--typedefs-in-signatures none` to explicitly turn it off, if the default ever changes. Bug: 127458147 Test: Unit tests included Change-Id: I9aee3043fb7d0a12bebe23e02ecce405723478f0 --- .../com/android/tools/metalava/Options.kt | 27 ++++ .../tools/metalava/model/AnnotationItem.kt | 16 +++ .../tools/metalava/model/AnnotationTarget.kt | 12 +- .../tools/metalava/model/ModifierList.kt | 21 ++- .../tools/metalava/ExtractAnnotationsTest.kt | 133 ++++++++++++++++-- .../com/android/tools/metalava/OptionsTest.kt | 10 ++ 6 files changed, 206 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt index 07be606..33afd52 100644 --- a/src/main/java/com/android/tools/metalava/Options.kt +++ b/src/main/java/com/android/tools/metalava/Options.kt @@ -148,6 +148,7 @@ const val ARG_STUB_PACKAGES = "--stub-packages" const val ARG_STUB_IMPORT_PACKAGES = "--stub-import-packages" const val ARG_DELETE_EMPTY_BASELINES = "--delete-empty-baselines" const val ARG_SUBTRACT_API = "--subtract-api" +const val ARG_TYPEDEFS_IN_SIGNATURES = "--typedefs-in-signatures" class Options( private val args: Array<String>, @@ -556,6 +557,15 @@ class Options( /** List of signature files to export as JDiff files */ val convertToXmlFiles: List<ConvertFile> = mutableConvertToXmlFiles + enum class TypedefMode { + NONE, + REFERENCE, + INLINE + } + + /** How to handle typedef annotations in signature files; corresponds to $ARG_TYPEDEFS_IN_SIGNATURES */ + var typedefMode = TypedefMode.NONE + /** File conversion tasks */ data class ConvertFile( val fromApiFile: File, @@ -769,6 +779,17 @@ class Options( mutableSkipEmitPackages += packages.split(File.pathSeparatorChar) } + ARG_TYPEDEFS_IN_SIGNATURES -> { + val type = getValue(args, ++index) + typedefMode = when (type) { + "ref" -> TypedefMode.REFERENCE + "inline" -> TypedefMode.INLINE + "none" -> TypedefMode.NONE + else -> throw DriverException( + stderr = "$ARG_TYPEDEFS_IN_SIGNATURES must be one of ref, inline, none; was $type") + } + } + ARG_BASELINE -> { val relative = getValue(args, ++index) assert(baselineFile == null) { "Only one baseline is allowed; found both $baselineFile and $relative" } @@ -1922,6 +1943,12 @@ class Options( "$ARG_SUBTRACT_API <api file>", "Subtracts the API in the given signature or jar file from the " + "current API being emitted via $ARG_API, $ARG_STUBS, $ARG_DOC_STUBS, etc. " + "Note that the subtraction only applies to classes; it does not subtract members.", + "$ARG_TYPEDEFS_IN_SIGNATURES <ref|inline>", "Whether to include typedef annotations in signature " + + "files. `$ARG_TYPEDEFS_IN_SIGNATURES ref` will include just a reference to the typedef class, " + + "which is not itself part of the API and is not included as a class, and " + + "`$ARG_TYPEDEFS_IN_SIGNATURES inline` will include the constants themselves into each usage " + + "site. You can also supply `$ARG_TYPEDEFS_IN_SIGNATURES none` to explicitly turn it off, if the " + + "default ever changes.", "", "\nDocumentation:", ARG_PUBLIC, "Only include elements that are public", diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt index 0ce737c..9b237b6 100644 --- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt +++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt @@ -32,6 +32,7 @@ import com.android.tools.metalava.ANDROID_NULLABLE import com.android.tools.metalava.ANDROID_SUPPORT_ANNOTATION_PREFIX import com.android.tools.metalava.Compatibility import com.android.tools.metalava.JAVA_LANG_PREFIX +import com.android.tools.metalava.Options import com.android.tools.metalava.RECENTLY_NONNULL import com.android.tools.metalava.RECENTLY_NULLABLE import com.android.tools.metalava.doclava1.ApiPredicate @@ -85,6 +86,9 @@ interface AnnotationItem { /** True if this annotation represents @IntDef, @LongDef or @StringDef */ fun isTypeDefAnnotation(): Boolean { val name = qualifiedName() ?: return false + if (!(name.endsWith("Def"))) { + return false + } return (INT_DEF_ANNOTATION.isEquals(name) || STRING_DEF_ANNOTATION.isEquals(name) || LONG_DEF_ANNOTATION.isEquals(name) || @@ -120,6 +124,12 @@ interface AnnotationItem { return codebase.findClass(qualifiedName() ?: return null) } + /** If this annotation has a typedef annotation associated with it, return it */ + fun findTypedefAnnotation(): AnnotationItem? { + val className = originalName() ?: return null + return codebase.findClass(className)?.modifiers?.annotations()?.firstOrNull { it.isTypeDefAnnotation() } + } + /** Returns the retention of this annotation */ val retention: AnnotationRetention get() { @@ -466,6 +476,12 @@ interface AnnotationItem { // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it. val cls = codebase.findClass(qualifiedName) ?: return NO_ANNOTATION_TARGETS if (!ApiPredicate().test(cls)) { + if (options.typedefMode != Options.TypedefMode.NONE) { + if (cls.modifiers.annotations().any { it.isTypeDefAnnotation() }) { + return ANNOTATION_SIGNATURE_ONLY + } + } + return NO_ANNOTATION_TARGETS } diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt b/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt index 82671f0..a4d2c72 100644 --- a/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt +++ b/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt @@ -16,6 +16,9 @@ package com.android.tools.metalava.model +import com.android.tools.metalava.Options +import com.android.tools.metalava.options + /** Various places where a given annotation can be written */ enum class AnnotationTarget { /** Write the annotation into the signature file */ @@ -80,4 +83,11 @@ val ANNOTATION_IN_DOC_STUBS_AND_EXTERNAL = setOf( val ANNOTATION_EXTERNAL = setOf(AnnotationTarget.SIGNATURE_FILE, AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) /** Write it only into the external annotations file, not the signature file */ -val ANNOTATION_EXTERNAL_ONLY = setOf(AnnotationTarget.SIGNATURE_FILE, AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) +val ANNOTATION_EXTERNAL_ONLY = if (options.typedefMode == Options.TypedefMode.INLINE || + options.typedefMode == Options.TypedefMode.NONE) // just here for compatibility purposes + setOf(AnnotationTarget.SIGNATURE_FILE, AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) +else + setOf(AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) + +/** Write it only into the he signature file */ +val ANNOTATION_SIGNATURE_ONLY = setOf(AnnotationTarget.SIGNATURE_FILE) 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 d645edf..e46e1ef 100644 --- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt +++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt @@ -493,6 +493,7 @@ interface ModifierList { continue } + var printAnnotation = annotation if (!annotation.targets().contains(target)) { continue } else if ((annotation.isNullnessAnnotation())) { @@ -502,6 +503,23 @@ interface ModifierList { } else if (annotation.qualifiedName() == "java.lang.Deprecated") { // Special cased in stubs and signature files: emitted first continue + } else if (options.typedefMode == Options.TypedefMode.INLINE) { + val typedef = annotation.findTypedefAnnotation() + if (typedef != null) { + printAnnotation = typedef + } + } else if (options.typedefMode == Options.TypedefMode.REFERENCE && + annotation.targets() === ANNOTATION_SIGNATURE_ONLY && + annotation.findTypedefAnnotation() != null) { + // For annotation references, only include the simple name + writer.write("@") + writer.write(annotation.resolve()?.simpleName() ?: annotation.qualifiedName()!!) + if (separateLines) { + writer.write("\n") + } else { + writer.write(" ") + } + continue } // Optionally filter out duplicates @@ -520,7 +538,8 @@ interface ModifierList { } } - val source = annotation.toSource(target) + val source = printAnnotation.toSource(target) + if (omitCommonPackages) { writer.write(AnnotationItem.shortenAnnotation(source)) } else { diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt index 2d92695..02cb3a8 100644 --- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt +++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt @@ -21,13 +21,9 @@ import org.junit.Test @SuppressWarnings("ALL") // Sample code class ExtractAnnotationsTest : DriverTest() { - @Test - fun `Check java typedef extraction and warning about non-source retention of typedefs`() { - check( - includeSourceRetentionAnnotations = false, - sourceFiles = *arrayOf( - java( - """ + private val sourceFiles1 = arrayOf( + java( + """ package test.pkg; import android.annotation.IntDef; @@ -70,10 +66,17 @@ class ExtractAnnotationsTest : DriverTest() { } } """ - ).indented(), - intDefAnnotationSource, - intRangeAnnotationSource - ), + ).indented(), + intDefAnnotationSource, + intRangeAnnotationSource + ) + + @Test + fun `Check java typedef extraction and warning about non-source retention of typedefs`() { + check( + includeSourceRetentionAnnotations = false, + format = FileFormat.V2, + sourceFiles = *sourceFiles1, warnings = "src/test/pkg/IntDefTest.java:11: error: This typedef annotation class should have @Retention(RetentionPolicy.SOURCE) [AnnotationExtraction]", extractAnnotations = mapOf( "test.pkg" to """ @@ -509,4 +512,112 @@ class ExtractAnnotationsTest : DriverTest() { ) ) } + + @Test + fun `No typedef signatures in api files`() { + check( + includeSourceRetentionAnnotations = false, + extraArguments = arrayOf( + ARG_HIDE_PACKAGE, "android.annotation", + ARG_TYPEDEFS_IN_SIGNATURES, "none" + ), + format = FileFormat.V2, + sourceFiles = *sourceFiles1, + api = """ + // Signature format: 2.0 + package test.pkg { + public class IntDefTest { + ctor public IntDefTest(); + method public void setFlags(Object, int); + method public void setStyle(int, int); + method public void testIntDef(int); + field public static final int STYLE_NORMAL = 0; // 0x0 + field public static final int STYLE_NO_FRAME = 2; // 0x2 + field public static final int STYLE_NO_INPUT = 3; // 0x3 + field public static final int STYLE_NO_TITLE = 1; // 0x1 + field public static final String TYPE_1 = "type1"; + field public static final String TYPE_2 = "type2"; + field public static final int UNRELATED = 3; // 0x3 + field public static final String UNRELATED_TYPE = "other"; + } + public static class IntDefTest.Inner { + ctor public IntDefTest.Inner(); + method public void setInner(int); + } + } + """ + ) + } + + @Test + fun `Inlining typedef signatures in api files`() { + check( + includeSourceRetentionAnnotations = false, + extraArguments = arrayOf( + ARG_HIDE_PACKAGE, "android.annotation", + ARG_TYPEDEFS_IN_SIGNATURES, "inline" + ), + format = FileFormat.V2, + sourceFiles = *sourceFiles1, + api = """ + // Signature format: 2.0 + package test.pkg { + public class IntDefTest { + ctor public IntDefTest(); + method public void setFlags(Object, @IntDef(value={test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 3 + 1}, flag=true) int); + method public void setStyle(@IntDef({test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}) int, int); + method public void testIntDef(int); + field public static final int STYLE_NORMAL = 0; // 0x0 + field public static final int STYLE_NO_FRAME = 2; // 0x2 + field public static final int STYLE_NO_INPUT = 3; // 0x3 + field public static final int STYLE_NO_TITLE = 1; // 0x1 + field public static final String TYPE_1 = "type1"; + field public static final String TYPE_2 = "type2"; + field public static final int UNRELATED = 3; // 0x3 + field public static final String UNRELATED_TYPE = "other"; + } + public static class IntDefTest.Inner { + ctor public IntDefTest.Inner(); + method public void setInner(@IntDef(value={test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 3 + 1}, flag=true) int); + } + } + """ + ) + } + + @Test + fun `Referencing typedef signatures in api files`() { + check( + includeSourceRetentionAnnotations = false, + extraArguments = arrayOf( + ARG_HIDE_PACKAGE, "android.annotation", + ARG_TYPEDEFS_IN_SIGNATURES, "ref" + ), + format = FileFormat.V2, + sourceFiles = *sourceFiles1, + api = """ + // Signature format: 2.0 + package test.pkg { + public class IntDefTest { + ctor public IntDefTest(); + method public void setFlags(Object, @DialogFlags int); + method public void setStyle(@DialogStyle int, int); + method public void testIntDef(int); + field public static final int STYLE_NORMAL = 0; // 0x0 + field public static final int STYLE_NO_FRAME = 2; // 0x2 + field public static final int STYLE_NO_INPUT = 3; // 0x3 + field public static final int STYLE_NO_TITLE = 1; // 0x1 + field public static final String TYPE_1 = "type1"; + field public static final String TYPE_2 = "type2"; + field public static final int UNRELATED = 3; // 0x3 + field public static final String UNRELATED_TYPE = "other"; + } + public static class IntDefTest.Inner { + ctor public IntDefTest.Inner(); + method public void setInner(@DialogFlags int); + } + } + """ + ) + } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt index d625725..c6af760 100644 --- a/src/test/java/com/android/tools/metalava/OptionsTest.kt +++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt @@ -121,6 +121,16 @@ API sources: --api, --stubs, --doc-stubs, etc. Note that the subtraction only applies to classes; it does not subtract members. +--typedefs-in-signatures <ref|inline> Whether to include typedef annotations in + signature files. `--typedefs-in-signatures ref` + will include just a reference to the typedef + class, which is not itself part of the API and + is not included as a class, and + `--typedefs-in-signatures inline` will include + the constants themselves into each usage site. + You can also supply `--typedefs-in-signatures + none` to explicitly turn it off, if the default + ever changes. Documentation: --public Only include elements that are public -- GitLab