Merge commit '1c67df7e75812c0da636efa56997b3ef69afb798' into dev-release Change-Id: I983dec38b0c8225b54ff2f0250a1b883cbab4be8
diff --git a/d8_r8/build.gradle.kts b/d8_r8/build.gradle.kts index 850a0c5..187f708 100644 --- a/d8_r8/build.gradle.kts +++ b/d8_r8/build.gradle.kts
@@ -19,4 +19,8 @@ val r8 by registering() { dependsOn(gradle.includedBuild("main").task(":r8WithRelocatedDeps")) } + + val r8lib by registering() { + dependsOn(gradle.includedBuild("test").task(":assembleR8LibWithRelocatedDeps")) + } }
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt index 1ec0e8b..418e0d5 100644 --- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -251,7 +251,7 @@ osFolder = "windows" } if (os.isMacOsX) { - osFolder = "osx" + osFolder = "osx/Contents/Home" } return getRoot().resolveAll("third_party", "openjdk", jdk.folder, osFolder) }
diff --git a/doc/keepanno-guide.md b/doc/keepanno-guide.md new file mode 100644 index 0000000..9970ae4 --- /dev/null +++ b/doc/keepanno-guide.md
@@ -0,0 +1,113 @@ +[comment]: <> (DO NOT EDIT - GENERATED FILE) +[comment]: <> (Changes should be made in doc/keepanno-guide.template.md) + +# Guide to Keep Annotations + +## Disclaimer + +The annotation library described here is in development and considered to be in +its prototype phase. As such it is not yet feature complete, but we are actively +working on supporting all of the use cases we know of. Once the design exits the +prototype phase, it is intended to move to an R8 independent library as part of +androidx. All feedback: criticism, comments and suggestions are very welcome! + +[File new feature requests and +bugs](https://issuetracker.google.com/issues/new?component=326788) in the +[R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788). + + + +## Introduction + +When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform +the shrinker about parts of the program that are used either externally from the +program itself or internally via reflection and therefore must be kept. + +Traditionally these aspects would be kept by writing keep rules in a +configuration file and passing that to the shrinker. + +The keep annotations described in this document represent an alternative method +using Java annotations. The motivation for using these annotations is foremost +to place the description of what to keep closer to the program point using +reflective behavior. Doing so more directly connects the reflective code with +the keep specification and makes it easier to maintain as the code develops. In +addition, the annotations are defined independent from keep rules and have a +hopefully more clear and direct meaning. + + +## Build configuration + +To use the keep annotations your build must include the library of +annotations. It is currently built as part of each R8 build and if used with R8, +you should use the matching version. You can find all archived builds at: + +``` +https://storage.googleapis.com/r8-releases/raw/<version>/keepanno-annotations.jar +``` + +Thus you may obtain version `8.2.34` by running: + +``` +wget https://storage.googleapis.com/r8-releases/raw/8.2.34/keepanno-annotations.jar +``` + +You will then need to set the system property +`com.android.tools.r8.enableKeepAnnotations` to instruct R8 to make use of the +annotations when shrinking: + +``` +java -Dcom.android.tools.r8.enableKeepAnnotations=1 \ + -cp r8.jar com.android.tools.r8.R8 \ + # ... the rest of your R8 compilation command here ... +``` + +### Annotating code using reflection + +The keep annotation library defines a family of annotations depending on your +use case. You should generally prefer [@UsesReflection](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/UsesReflection.html) where applicable. + +For example, if your program is reflectively invoking a method, you +should annotate the method that is doing the reflection. The annotation must describe the +assumptions the reflective code makes. + +In the following example, the method `foo` is looking up the method with the name +`hiddenMethod` on objects that are instances of `BaseClass`. It is then invoking the method with +no other arguments than the receiver. + +The assumptions the code makes are that all methods with the name +`hiddenMethod` and the empty list of parameters must remain valid for `getDeclaredMethod` if they +are objects that are instances of the class `BaseClass` or subclasses thereof. + + +``` +static class MyClass { + + @UsesReflection({ + @KeepTarget( + instanceOfClassConstant = BaseClass.class, + methodName = "hiddenMethod", + methodParameters = {}) + }) + public void foo(BaseClass base) throws Exception { + base.getClass().getDeclaredMethod("hiddenMethod").invoke(base); + } +} +``` + + + + +### Annotating code used by reflection (or via JNI) + + + +### Annotating APIs + + +### Migrating rules to annotations + + +### My use case is not covered! + + +### Troubleshooting
diff --git a/doc/keepanno-guide.template.md b/doc/keepanno-guide.template.md new file mode 100644 index 0000000..6e0a8df --- /dev/null +++ b/doc/keepanno-guide.template.md
@@ -0,0 +1,85 @@ +# Guide to Keep Annotations + +## Disclaimer + +The annotation library described here is in development and considered to be in +its prototype phase. As such it is not yet feature complete, but we are actively +working on supporting all of the use cases we know of. Once the design exits the +prototype phase, it is intended to move to an R8 independent library as part of +androidx. All feedback: criticism, comments and suggestions are very welcome! + +[File new feature requests and +bugs](https://issuetracker.google.com/issues/new?component=326788) in the +[R8 component](https://issuetracker.google.com/issues?q=status:open%20componentid:326788). + + + +## Introduction + +When using a Java/Kotlin shrinker such as R8 or Proguard, developers must inform +the shrinker about parts of the program that are used either externally from the +program itself or internally via reflection and therefore must be kept. + +Traditionally these aspects would be kept by writing keep rules in a +configuration file and passing that to the shrinker. + +The keep annotations described in this document represent an alternative method +using Java annotations. The motivation for using these annotations is foremost +to place the description of what to keep closer to the program point using +reflective behavior. Doing so more directly connects the reflective code with +the keep specification and makes it easier to maintain as the code develops. In +addition, the annotations are defined independent from keep rules and have a +hopefully more clear and direct meaning. + + +## Build configuration + +To use the keep annotations your build must include the library of +annotations. It is currently built as part of each R8 build and if used with R8, +you should use the matching version. You can find all archived builds at: + +``` +https://storage.googleapis.com/r8-releases/raw/<version>/keepanno-annotations.jar +``` + +Thus you may obtain version `8.2.34` by running: + +``` +wget https://storage.googleapis.com/r8-releases/raw/8.2.34/keepanno-annotations.jar +``` + +You will then need to set the system property +`com.android.tools.r8.enableKeepAnnotations` to instruct R8 to make use of the +annotations when shrinking: + +``` +java -Dcom.android.tools.r8.enableKeepAnnotations=1 \ + -cp r8.jar com.android.tools.r8.R8 \ + # ... the rest of your R8 compilation command here ... +``` + +### Annotating code using reflection + +The keep annotation library defines a family of annotations depending on your +use case. You should generally prefer `@UsesReflection` where applicable. + +[[[INCLUDE DOC:UsesReflectionOnVirtualMethod]]] + +[[[INCLUDE CODE:UsesReflectionOnVirtualMethod]]] + + + +### Annotating code used by reflection (or via JNI) + + + +### Annotating APIs + + +### Migrating rules to annotations + + +### My use case is not covered! + + +### Troubleshooting
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java index 9657a22..e97d2b5 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -31,7 +31,11 @@ @Retention(RetentionPolicy.CLASS) public @interface KeepBinding { - /** Name with which other bindings, conditions or targets can reference the bound item pattern. */ + /** + * Name with which other bindings, conditions or targets can reference the bound item pattern. + * + * @return Name of the binding. + */ String bindingName(); /** @@ -47,6 +51,8 @@ * * <p>If unspecified the default for an item with no member patterns is ONLY_CLASS and if it does * have member patterns the default is ONLY_MEMBERS + * + * @return The kind for this pattern. */ KeepItemKind kind() default KeepItemKind.DEFAULT; @@ -67,6 +73,8 @@ * </ul> * * <p>If none are specified the default is to match any class. + * + * @return The name of the binding that defines the class. */ String classFromBinding() default ""; @@ -81,6 +89,8 @@ * </ul> * * <p>If none are specified the default is to match any class name. + * + * @return The qualified class name that defines the class. */ String className() default ""; @@ -95,6 +105,8 @@ * </ul> * * <p>If none are specified the default is to match any class name. + * + * @return The class-constant that defines the class. */ Class<?> classConstant() default Object.class; @@ -113,6 +125,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The qualified class name that defines what instance-of the class must be. */ String instanceOfClassName() default ""; @@ -134,6 +148,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The qualified class name that defines what instance-of the class must be. */ String instanceOfClassNameExclusive() default ""; @@ -152,6 +168,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what instance-of the class must be. */ Class<?> instanceOfClassConstant() default Object.class; @@ -173,6 +191,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what instance-of the class must be. */ Class<?> instanceOfClassConstantExclusive() default Object.class; @@ -182,8 +202,6 @@ * <p>The pattern is exclusive in that it does not match classes that are instances of the * pattern, but only those that are instances of classes that are subclasses of the pattern. * - * <p>This property is deprecated, use instanceOfClassName instead. - * * <p>Mutually exclusive with the following other properties defining instance-of: * * <ul> @@ -196,7 +214,11 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class name that defines what the class must extend. + * @deprecated This property is deprecated, use {@link #instanceOfClassName} instead. */ + @Deprecated String extendsClassName() default ""; /** @@ -205,8 +227,6 @@ * <p>The pattern is exclusive in that it does not match classes that are instances of the * pattern, but only those that are instances of classes that are subclasses of the pattern. * - * <p>This property is deprecated, use instanceOfClassConstant instead. - * * <p>Mutually exclusive with the following other properties defining instance-of: * * <ul> @@ -219,7 +239,11 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what the class must extend. + * @deprecated This property is deprecated, use {@link #instanceOfClassConstant} instead. */ + @Deprecated Class<?> extendsClassConstant() default Object.class; /** @@ -227,6 +251,8 @@ * * <p>Mutually exclusive with all field and method properties as use restricts the match to both * types of members. + * + * @return The member access-flag constraints that must be met. */ MemberAccessFlags[] memberAccess() default {}; @@ -237,6 +263,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * method-access flags. + * + * @return The method access-flag constraints that must be met. */ MethodAccessFlags[] methodAccess() default {}; @@ -247,6 +275,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any method * name. + * + * @return The exact method name of the method. */ String methodName() default ""; @@ -257,6 +287,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any return * type. + * + * @return The qualified type name of the method return type. */ String methodReturnType() default ""; @@ -267,6 +299,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * parameters. + * + * @return The list of qualified type names of the method parameters. */ String[] methodParameters() default {"<default>"}; @@ -277,6 +311,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any * field-access flags. + * + * @return The field access-flag constraints that must be met. */ FieldAccessFlags[] fieldAccess() default {}; @@ -287,6 +323,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any field * name. + * + * @return The exact field name of the field. */ String fieldName() default ""; @@ -296,6 +334,8 @@ * <p>Mutually exclusive with all method properties. * * <p>If none, and other properties define this item as a field, the default matches any type. + * + * @return The qualified type name of the field type. */ String fieldType() default ""; }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java index 7777fd8..e539cc4 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -45,6 +45,8 @@ * </ul> * * <p>If none are specified the default is to match any class. + * + * @return The name of the binding that defines the class. */ String classFromBinding() default ""; @@ -59,6 +61,8 @@ * </ul> * * <p>If none are specified the default is to match any class name. + * + * @return The qualified class name that defines the class. */ String className() default ""; @@ -73,6 +77,8 @@ * </ul> * * <p>If none are specified the default is to match any class name. + * + * @return The class-constant that defines the class. */ Class<?> classConstant() default Object.class; @@ -91,6 +97,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The qualified class name that defines what instance-of the class must be. */ String instanceOfClassName() default ""; @@ -112,6 +120,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The qualified class name that defines what instance-of the class must be. */ String instanceOfClassNameExclusive() default ""; @@ -130,6 +140,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what instance-of the class must be. */ Class<?> instanceOfClassConstant() default Object.class; @@ -151,6 +163,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what instance-of the class must be. */ Class<?> instanceOfClassConstantExclusive() default Object.class; @@ -160,8 +174,6 @@ * <p>The pattern is exclusive in that it does not match classes that are instances of the * pattern, but only those that are instances of classes that are subclasses of the pattern. * - * <p>This property is deprecated, use instanceOfClassName instead. - * * <p>Mutually exclusive with the following other properties defining instance-of: * * <ul> @@ -174,7 +186,11 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class name that defines what the class must extend. + * @deprecated This property is deprecated, use {@link #instanceOfClassName} instead. */ + @Deprecated String extendsClassName() default ""; /** @@ -183,8 +199,6 @@ * <p>The pattern is exclusive in that it does not match classes that are instances of the * pattern, but only those that are instances of classes that are subclasses of the pattern. * - * <p>This property is deprecated, use instanceOfClassConstant instead. - * * <p>Mutually exclusive with the following other properties defining instance-of: * * <ul> @@ -197,7 +211,11 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what the class must extend. + * @deprecated This property is deprecated, use {@link #instanceOfClassConstant} instead. */ + @Deprecated Class<?> extendsClassConstant() default Object.class; /** @@ -205,6 +223,8 @@ * * <p>Mutually exclusive with all other class and member pattern properties. When a member binding * is referenced this item is defined to be that item, including its class and member patterns. + * + * @return The binding name that defines the member. */ String memberFromBinding() default ""; @@ -213,6 +233,8 @@ * * <p>Mutually exclusive with all field and method properties as use restricts the match to both * types of members. + * + * @return The member access-flag constraints that must be met. */ MemberAccessFlags[] memberAccess() default {}; @@ -223,6 +245,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * method-access flags. + * + * @return The method access-flag constraints that must be met. */ MethodAccessFlags[] methodAccess() default {}; @@ -233,6 +257,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any method * name. + * + * @return The exact method name of the method. */ String methodName() default ""; @@ -243,6 +269,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any return * type. + * + * @return The qualified type name of the method return type. */ String methodReturnType() default ""; @@ -253,6 +281,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * parameters. + * + * @return The list of qualified type names of the method parameters. */ String[] methodParameters() default {"<default>"}; @@ -263,6 +293,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any * field-access flags. + * + * @return The field access-flag constraints that must be met. */ FieldAccessFlags[] fieldAccess() default {}; @@ -273,6 +305,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any field * name. + * + * @return The exact field name of the field. */ String fieldName() default ""; @@ -282,6 +316,8 @@ * <p>Mutually exclusive with all method properties. * * <p>If none, and other properties define this item as a field, the default matches any type. + * + * @return The qualified type name of the field type. */ String fieldType() default ""; }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java index 65454fe..cd3559c 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -27,13 +27,17 @@ @Retention(RetentionPolicy.CLASS) public @interface KeepForApi { - /** Optional description to document the reason for this annotation. */ + /** + * Optional description to document the reason for this annotation. + * + * @return The descriptive message. Defaults to no description. + */ String description() default ""; /** * Additional targets to be kept as part of the API surface. * - * <p>Defaults to no additional targets. + * @return List of additional target consequences. Defaults to no additional target consequences. */ KeepTarget[] additionalTargets() default {}; @@ -46,6 +50,8 @@ * * <p>It is not possible to use ONLY_CLASS if annotating a member. Also, it is never valid to use * kind ONLY_MEMBERS as the API surface must keep the class if any member is to be accessible. + * + * @return The kind for this pattern. */ KeepItemKind kind() default KeepItemKind.DEFAULT; @@ -54,6 +60,8 @@ * * <p>Mutually exclusive with all field and method properties as use restricts the match to both * types of members. + * + * @return The member access-flag constraints that must be met. */ MemberAccessFlags[] memberAccess() default {}; @@ -64,6 +72,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * method-access flags. + * + * @return The method access-flag constraints that must be met. */ MethodAccessFlags[] methodAccess() default {}; @@ -74,6 +84,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any method * name. + * + * @return The exact method name of the method. */ String methodName() default ""; @@ -84,6 +96,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any return * type. + * + * @return The qualified type name of the method return type. */ String methodReturnType() default ""; @@ -94,6 +108,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * parameters. + * + * @return The list of qualified type names of the method parameters. */ String[] methodParameters() default {"<default>"}; @@ -104,6 +120,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any * field-access flags. + * + * @return The field access-flag constraints that must be met. */ FieldAccessFlags[] fieldAccess() default {}; @@ -114,6 +132,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any field * name. + * + * @return The exact field name of the field. */ String fieldName() default ""; @@ -123,6 +143,8 @@ * <p>Mutually exclusive with all method properties. * * <p>If none, and other properties define this item as a field, the default matches any type. + * + * @return The qualified type name of the field type. */ String fieldType() default ""; }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java index 59e7e7c..8ad5098 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -41,24 +41,34 @@ * * <p>If unspecified the default for an item with no member patterns is ONLY_CLASS and if it does * have member patterns the default is ONLY_MEMBERS + * + * @return The kind for this pattern. */ KeepItemKind kind() default KeepItemKind.DEFAULT; /** - * Define the options that do not need to be preserved for the target. + * Define the options that are allowed to be modified. + * + * <p>The specified options do not need to be preserved for the target. * * <p>Mutually exclusive with the property `disallow` also defining options. * * <p>If nothing is specified for options the default is "allow none" / "disallow all". + * + * @return Options allowed to be modified for the target. */ KeepOption[] allow() default {}; /** - * Define the options that *must* be preserved for the target. + * Define the options that are not allowed to be modified. + * + * <p>The specified options *must* be preserved for the target. * * <p>Mutually exclusive with the property `allow` also defining options. * * <p>If nothing is specified for options the default is "allow none" / "disallow all". + * + * @return Options not allowed to be modified for the target. */ KeepOption[] disallow() default {}; @@ -79,6 +89,8 @@ * </ul> * * <p>If none are specified the default is to match any class. + * + * @return The name of the binding that defines the class. */ String classFromBinding() default ""; @@ -93,6 +105,8 @@ * </ul> * * <p>If none are specified the default is to match any class name. + * + * @return The qualified class name that defines the class. */ String className() default ""; @@ -107,6 +121,8 @@ * </ul> * * <p>If none are specified the default is to match any class name. + * + * @return The class-constant that defines the class. */ Class<?> classConstant() default Object.class; @@ -125,6 +141,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The qualified class name that defines what instance-of the class must be. */ String instanceOfClassName() default ""; @@ -146,6 +164,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The qualified class name that defines what instance-of the class must be. */ String instanceOfClassNameExclusive() default ""; @@ -164,6 +184,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what instance-of the class must be. */ Class<?> instanceOfClassConstant() default Object.class; @@ -185,6 +207,8 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what instance-of the class must be. */ Class<?> instanceOfClassConstantExclusive() default Object.class; @@ -194,8 +218,6 @@ * <p>The pattern is exclusive in that it does not match classes that are instances of the * pattern, but only those that are instances of classes that are subclasses of the pattern. * - * <p>This property is deprecated, use instanceOfClassName instead. - * * <p>Mutually exclusive with the following other properties defining instance-of: * * <ul> @@ -208,7 +230,11 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class name that defines what the class must extend. + * @deprecated This property is deprecated, use {@link #instanceOfClassName} instead. */ + @Deprecated String extendsClassName() default ""; /** @@ -217,8 +243,6 @@ * <p>The pattern is exclusive in that it does not match classes that are instances of the * pattern, but only those that are instances of classes that are subclasses of the pattern. * - * <p>This property is deprecated, use instanceOfClassConstant instead. - * * <p>Mutually exclusive with the following other properties defining instance-of: * * <ul> @@ -231,7 +255,11 @@ * </ul> * * <p>If none are specified the default is to match any class instance. + * + * @return The class constant that defines what the class must extend. + * @deprecated This property is deprecated, use {@link #instanceOfClassConstant} instead. */ + @Deprecated Class<?> extendsClassConstant() default Object.class; /** @@ -239,6 +267,8 @@ * * <p>Mutually exclusive with all other class and member pattern properties. When a member binding * is referenced this item is defined to be that item, including its class and member patterns. + * + * @return The binding name that defines the member. */ String memberFromBinding() default ""; @@ -247,6 +277,8 @@ * * <p>Mutually exclusive with all field and method properties as use restricts the match to both * types of members. + * + * @return The member access-flag constraints that must be met. */ MemberAccessFlags[] memberAccess() default {}; @@ -257,6 +289,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * method-access flags. + * + * @return The method access-flag constraints that must be met. */ MethodAccessFlags[] methodAccess() default {}; @@ -267,6 +301,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any method * name. + * + * @return The exact method name of the method. */ String methodName() default ""; @@ -277,6 +313,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any return * type. + * + * @return The qualified type name of the method return type. */ String methodReturnType() default ""; @@ -287,6 +325,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * parameters. + * + * @return The list of qualified type names of the method parameters. */ String[] methodParameters() default {"<default>"}; @@ -297,6 +337,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any * field-access flags. + * + * @return The field access-flag constraints that must be met. */ FieldAccessFlags[] fieldAccess() default {}; @@ -307,6 +349,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any field * name. + * + * @return The exact field name of the field. */ String fieldName() default ""; @@ -316,6 +360,8 @@ * <p>Mutually exclusive with all method properties. * * <p>If none, and other properties define this item as a field, the default matches any type. + * + * @return The qualified type name of the field type. */ String fieldType() default ""; }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java index b1ad804..beba152 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -32,20 +32,25 @@ @Retention(RetentionPolicy.CLASS) public @interface UsedByNative { - /** Optional description to document the reason for this annotation. */ + /** + * Optional description to document the reason for this annotation. + * + * @return The descriptive message. Defaults to no description. + */ String description() default ""; /** * Conditions that should be satisfied for the annotation to be in effect. * - * <p>Defaults to no conditions, thus trivially/unconditionally satisfied. + * @return The list of preconditions. Defaults to no conditions, thus trivially/unconditionally + * satisfied. */ KeepCondition[] preconditions() default {}; /** * Additional targets to be kept in addition to the annotated class/members. * - * <p>Defaults to no additional targets. + * @return List of additional target consequences. Defaults to no additional target consequences. */ KeepTarget[] additionalTargets() default {}; @@ -61,6 +66,8 @@ * <p>When annotating a member, the default kind is {@link KeepItemKind#ONLY_MEMBERS}. * * <p>It is not possible to use ONLY_CLASS if annotating a member. + * + * @return The kind for this pattern. */ KeepItemKind kind() default KeepItemKind.DEFAULT; @@ -69,6 +76,8 @@ * * <p>Mutually exclusive with all field and method properties as use restricts the match to both * types of members. + * + * @return The member access-flag constraints that must be met. */ MemberAccessFlags[] memberAccess() default {}; @@ -79,6 +88,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * method-access flags. + * + * @return The method access-flag constraints that must be met. */ MethodAccessFlags[] methodAccess() default {}; @@ -89,6 +100,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any method * name. + * + * @return The exact method name of the method. */ String methodName() default ""; @@ -99,6 +112,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any return * type. + * + * @return The qualified type name of the method return type. */ String methodReturnType() default ""; @@ -109,6 +124,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * parameters. + * + * @return The list of qualified type names of the method parameters. */ String[] methodParameters() default {"<default>"}; @@ -119,6 +136,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any * field-access flags. + * + * @return The field access-flag constraints that must be met. */ FieldAccessFlags[] fieldAccess() default {}; @@ -129,6 +148,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any field * name. + * + * @return The exact field name of the field. */ String fieldName() default ""; @@ -138,6 +159,8 @@ * <p>Mutually exclusive with all method properties. * * <p>If none, and other properties define this item as a field, the default matches any type. + * + * @return The qualified type name of the field type. */ String fieldType() default ""; }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java index 27489cb..8c6e153 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -32,20 +32,25 @@ @Retention(RetentionPolicy.CLASS) public @interface UsedByReflection { - /** Optional description to document the reason for this annotation. */ + /** + * Optional description to document the reason for this annotation. + * + * @return The descriptive message. Defaults to no description. + */ String description() default ""; /** * Conditions that should be satisfied for the annotation to be in effect. * - * <p>Defaults to no conditions, thus trivially/unconditionally satisfied. + * @return The list of preconditions. Defaults to no conditions, thus trivially/unconditionally + * satisfied. */ KeepCondition[] preconditions() default {}; /** * Additional targets to be kept in addition to the annotated class/members. * - * <p>Defaults to no additional targets. + * @return List of additional target consequences. Defaults to no additional target consequences. */ KeepTarget[] additionalTargets() default {}; @@ -61,6 +66,8 @@ * <p>When annotating a member, the default kind is {@link KeepItemKind#ONLY_MEMBERS}. * * <p>It is not possible to use ONLY_CLASS if annotating a member. + * + * @return The kind for this pattern. */ KeepItemKind kind() default KeepItemKind.DEFAULT; @@ -69,6 +76,8 @@ * * <p>Mutually exclusive with all field and method properties as use restricts the match to both * types of members. + * + * @return The member access-flag constraints that must be met. */ MemberAccessFlags[] memberAccess() default {}; @@ -79,6 +88,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * method-access flags. + * + * @return The method access-flag constraints that must be met. */ MethodAccessFlags[] methodAccess() default {}; @@ -89,6 +100,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any method * name. + * + * @return The exact method name of the method. */ String methodName() default ""; @@ -99,6 +112,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any return * type. + * + * @return The qualified type name of the method return type. */ String methodReturnType() default ""; @@ -109,6 +124,8 @@ * * <p>If none, and other properties define this item as a method, the default matches any * parameters. + * + * @return The list of qualified type names of the method parameters. */ String[] methodParameters() default {"<default>"}; @@ -119,6 +136,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any * field-access flags. + * + * @return The field access-flag constraints that must be met. */ FieldAccessFlags[] fieldAccess() default {}; @@ -129,6 +148,8 @@ * * <p>If none, and other properties define this item as a field, the default matches any field * name. + * + * @return The exact field name of the field. */ String fieldName() default ""; @@ -138,6 +159,8 @@ * <p>Mutually exclusive with all method properties. * * <p>If none, and other properties define this item as a field, the default matches any type. + * + * @return The qualified type name of the field type. */ String fieldType() default ""; }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java index 56a215d..fd28b1a 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
@@ -63,16 +63,24 @@ @Retention(RetentionPolicy.CLASS) public @interface UsesReflection { - /** Optional description to document the reason for this annotation. */ + /** + * Optional description to document the reason for this annotation. + * + * @return The descriptive message. Defaults to no description. + */ String description() default ""; - /** Consequences that must be kept if the annotation is in effect. */ + /** + * Consequences that must be kept if the annotation is in effect. + * + * @return The list of target consequences. + */ KeepTarget[] value(); /** * Additional preconditions for the annotation to be in effect. * - * <p>Defaults to no additional preconditions. + * @return The list of additional preconditions. Defaults to no additional preconditions. */ KeepCondition[] additionalPreconditions() default {}; }
diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java index 9bb7f82..eed5427 100644 --- a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java +++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
@@ -4,29 +4,67 @@ package com.android.tools.r8; import com.android.tools.r8.keepanno.annotations.KeepForApi; +import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.utils.ArchiveBuilder; -import com.android.tools.r8.utils.OutputBuilder; +import com.android.tools.r8.utils.ExceptionDiagnostic; +import com.android.tools.r8.utils.ZipUtils; +import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; @KeepForApi public class ArchiveProtoAndroidResourceConsumer implements AndroidResourceConsumer { - private final OutputBuilder outputBuilder; + private final ArchiveBuilder archiveBuilder; + private final Path inputPath; + private Map<String, Boolean> compressionMap; public ArchiveProtoAndroidResourceConsumer(Path outputPath) { - this.outputBuilder = new ArchiveBuilder(outputPath); - this.outputBuilder.open(); + this(outputPath, null); + } + + public ArchiveProtoAndroidResourceConsumer(Path outputPath, Path inputPath) { + this.archiveBuilder = new ArchiveBuilder(outputPath); + this.archiveBuilder.open(); + this.inputPath = inputPath; + } + + private synchronized Map<String, Boolean> getCompressionMap( + DiagnosticsHandler diagnosticsHandler) { + if (compressionMap != null) { + return compressionMap; + } + if (inputPath != null) { + compressionMap = new HashMap<>(); + try { + ZipUtils.iter( + inputPath, + entry -> { + compressionMap.put(entry.getName(), entry.getMethod() != ZipEntry.STORED); + }); + } catch (IOException e) { + diagnosticsHandler.error(new ExceptionDiagnostic(e, new PathOrigin(inputPath))); + } + } else { + compressionMap = Collections.emptyMap(); + } + return compressionMap; } @Override public void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler) { - outputBuilder.addFile( + archiveBuilder.addFile( androidResource.getPath().location(), androidResource.getByteDataView(), - diagnosticsHandler); + diagnosticsHandler, + getCompressionMap(diagnosticsHandler) + .getOrDefault(androidResource.getPath().location(), true)); } @Override public void finished(DiagnosticsHandler handler) { - outputBuilder.close(handler); + archiveBuilder.close(handler); } }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 6e8edf7..6923215 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -856,15 +856,15 @@ assert appView.getDontWarnConfiguration().validate(options); options.printWarnings(); + + if (options.printTimes) { + timing.report(); + } } catch (ExecutionException e) { throw unwrapExecutionException(e); } finally { inputApp.signalFinishedToProviders(options.reporter); options.signalFinishedToConsumers(); - // Dump timings. - if (options.printTimes) { - timing.report(); - } } } @@ -906,8 +906,7 @@ LegacyResourceShrinker shrinker = resourceShrinkerBuilder.build(); ShrinkerResult shrinkerResult; if (options.resourceShrinkerConfiguration.isOptimizedShrinking()) { - shrinkerResult = - shrinker.shrinkModel(appView.getResourceShrinkerState().getR8ResourceShrinkerModel()); + shrinkerResult = shrinker.shrinkModel(appView.getResourceAnalysisResult().getModel()); } else { shrinkerResult = shrinker.run(); }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index 3e3fe4c..f806710 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.graph; -import com.android.build.shrinker.r8integration.R8ResourceShrinkerState; import com.android.tools.r8.androidapi.AndroidApiLevelCompute; import com.android.tools.r8.androidapi.ComputedApiLevel; import com.android.tools.r8.contexts.CompilationContext; @@ -13,6 +12,7 @@ import com.android.tools.r8.features.ClassToFeatureSplitMap; import com.android.tools.r8.graph.DexValue.DexValueString; import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods; +import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis.ResourceAnalysisResult; import com.android.tools.r8.graph.classmerging.MergedClassesCollection; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.lens.InitClassLens; @@ -149,7 +149,7 @@ private SeedMapper applyMappingSeedMapper; - R8ResourceShrinkerState resourceShrinkerState = null; + private ResourceAnalysisResult resourceAnalysisResult = null; // When input has been (partially) desugared these are the classes which has been library // desugared. This information is populated in the IR converter. @@ -871,12 +871,12 @@ testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums); } - public R8ResourceShrinkerState getResourceShrinkerState() { - return resourceShrinkerState; + public void setResourceAnalysisResult(ResourceAnalysisResult resourceAnalysisResult) { + this.resourceAnalysisResult = resourceAnalysisResult; } - public void setResourceShrinkerState(R8ResourceShrinkerState resourceShrinkerState) { - this.resourceShrinkerState = resourceShrinkerState; + public ResourceAnalysisResult getResourceAnalysisResult() { + return resourceAnalysisResult; } public boolean validateUnboxedEnumsHaveBeenPruned() { @@ -971,6 +971,9 @@ setProguardCompatibilityActions( getProguardCompatibilityActions().withoutPrunedItems(prunedItems, timing)); } + if (resourceAnalysisResult != null) { + resourceAnalysisResult.withoutPrunedItems(prunedItems, timing); + } if (hasRootSet()) { rootSet.pruneItems(prunedItems, timing); } @@ -1175,6 +1178,17 @@ new ThreadTask() { @Override public void run(Timing threadTiming) { + appView.resourceAnalysisResult.rewrittenWithLens(lens, threadTiming); + } + + @Override + public boolean shouldRun() { + return appView.resourceAnalysisResult != null; + } + }, + new ThreadTask() { + @Override + public void run(Timing threadTiming) { appView.setRootSet(appView.rootSet().rewrittenWithLens(lens, threadTiming)); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 92ceda8..39c4168 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -93,6 +93,7 @@ public static final String androidMediaMediaDrmDescriptorString = "Landroid/media/MediaDrm;"; public static final String androidMediaMediaMetadataRetrieverDescriptorString = "Landroid/media/MediaMetadataRetriever;"; + public static final String androidResourcesDescriptorString = "Landroid/content/res/Resources;"; /** Set of types that may be synthesized during compilation. */ private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet(); @@ -643,6 +644,13 @@ createStaticallyKnownType(androidMediaMediaDrmDescriptorString); public final DexType androidMediaMediaMetadataRetrieverType = createStaticallyKnownType(androidMediaMediaMetadataRetrieverDescriptorString); + public final DexType androidResourcesType = + createStaticallyKnownType(androidResourcesDescriptorString); + public final DexString androidResourcesGetStringName = createString("getString"); + public final DexProto androidResourcesGetStringProto = createProto(stringType, intType); + public final DexMethod androidResourcesGetStringMethod = + createMethod( + androidResourcesType, androidResourcesGetStringProto, androidResourcesGetStringName); public final StringBuildingMethods stringBuilderMethods = new StringBuildingMethods(stringBuilderType);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java index d4ebe2f..ed61adb 100644 --- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.graph.analysis; import com.android.build.shrinker.r8integration.R8ResourceShrinkerState; +import com.android.build.shrinker.r8integration.R8ResourceShrinkerState.R8ResourceShrinkerModel; import com.android.tools.r8.AndroidResourceInput; import com.android.tools.r8.AndroidResourceInput.Kind; import com.android.tools.r8.ResourceException; @@ -12,9 +13,12 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult; import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.PrunedItems; +import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.NewArrayEmpty; @@ -23,8 +27,10 @@ import com.android.tools.r8.ir.conversion.MethodConversionOptions; import com.android.tools.r8.shaking.Enqueuer; import com.android.tools.r8.shaking.EnqueuerWorklist; +import com.android.tools.r8.shaking.KeepFieldInfo; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.Timing; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import java.util.IdentityHashMap; @@ -34,18 +40,15 @@ public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis { private final R8ResourceShrinkerState resourceShrinkerState; - private final Map<DexProgramClass, RClassFieldToValueStore> fieldToValueMapping = - new IdentityHashMap<>(); + private final Map<DexType, RClassFieldToValueStore> fieldToValueMapping = new IdentityHashMap<>(); private final AppView<? extends AppInfoWithClassHierarchy> appView; + private final Enqueuer enqueuer; - @SuppressWarnings("UnusedVariable") private ResourceAccessAnalysis( - AppView<? extends AppInfoWithClassHierarchy> appView, - Enqueuer enqueuer, - R8ResourceShrinkerState resourceShrinkerState) { + AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) { this.appView = appView; - this.resourceShrinkerState = resourceShrinkerState; - appView.setResourceShrinkerState(resourceShrinkerState); + this.enqueuer = enqueuer; + this.resourceShrinkerState = new R8ResourceShrinkerState(); try { for (AndroidResourceInput androidResource : appView.options().androidResourceProvider.getAndroidResources()) { @@ -62,14 +65,14 @@ public static void register( AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) { if (enabled(appView, enqueuer)) { - enqueuer.registerFieldAccessAnalysis( - new ResourceAccessAnalysis(appView, enqueuer, new R8ResourceShrinkerState())); + enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer)); } } @Override public void done(Enqueuer enqueuer) { - appView.setResourceShrinkerState(resourceShrinkerState); + appView.setResourceAnalysisResult( + new ResourceAnalysisResult(resourceShrinkerState, fieldToValueMapping)); EnqueuerFieldAccessAnalysis.super.done(enqueuer); } @@ -93,13 +96,15 @@ return; } if (getMaybeCachedIsRClass(resolvedField.getHolder())) { - DexProgramClass holderType = resolvedField.getHolder(); + DexType holderType = resolvedField.getHolderType(); if (!fieldToValueMapping.containsKey(holderType)) { populateRClassValues(resolvedField); } assert fieldToValueMapping.containsKey(holderType); RClassFieldToValueStore rClassFieldToValueStore = fieldToValueMapping.get(holderType); IntList integers = rClassFieldToValueStore.valueMapping.get(field); + enqueuer.applyMinimumKeepInfoWhenLive( + resolvedField, KeepFieldInfo.newEmptyJoiner().disallowOptimization()); for (Integer integer : integers) { resourceShrinkerState.trace(integer); } @@ -161,7 +166,7 @@ rClassValueBuilder.addMapping(staticPut.getField(), values); } - fieldToValueMapping.put(field.getHolder(), rClassValueBuilder.build()); + fieldToValueMapping.put(field.getHolderType(), rClassValueBuilder.build()); } private final Map<DexProgramClass, Boolean> cachedClassLookups = new IdentityHashMap<>(); @@ -189,13 +194,103 @@ return isRClass; } + public static class ResourceAnalysisResult { + + private final R8ResourceShrinkerState resourceShrinkerState; + private Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap; + + private ResourceAnalysisResult( + R8ResourceShrinkerState resourceShrinkerState, + Map<DexType, RClassFieldToValueStore> rClassFieldToValueStoreMap) { + this.resourceShrinkerState = resourceShrinkerState; + this.rClassFieldToValueStoreMap = rClassFieldToValueStoreMap; + } + + public R8ResourceShrinkerModel getModel() { + return resourceShrinkerState.getR8ResourceShrinkerModel(); + } + + @SuppressWarnings("ReferenceEquality") + public void rewrittenWithLens(GraphLens lens, Timing timing) { + Map<DexType, DexType> changed = new IdentityHashMap<>(); + for (DexType dexType : rClassFieldToValueStoreMap.keySet()) { + DexType rewritten = lens.lookupClassType(dexType); + if (rewritten != dexType) { + changed.put(dexType, rewritten); + } + } + if (changed.size() > 0) { + Map<DexType, RClassFieldToValueStore> rewrittenMap = new IdentityHashMap<>(); + rClassFieldToValueStoreMap.forEach( + (type, map) -> { + rewrittenMap.put(changed.getOrDefault(type, type), map); + map.rewrittenWithLens(lens); + }); + rClassFieldToValueStoreMap = rewrittenMap; + } + } + + public void withoutPrunedItems(PrunedItems prunedItems, Timing timing) { + rClassFieldToValueStoreMap.keySet().removeIf(prunedItems::isRemoved); + rClassFieldToValueStoreMap.values().forEach(store -> store.pruneItems(prunedItems)); + } + + public String getSingleStringValueForField(ProgramField programField) { + RClassFieldToValueStore rClassFieldToValueStore = + rClassFieldToValueStoreMap.get(programField.getHolderType()); + if (rClassFieldToValueStore == null) { + return null; + } + if (!rClassFieldToValueStore.hasField(programField.getReference())) { + return null; + } + return getModel() + .getStringResourcesWithSingleValue() + .get(rClassFieldToValueStore.getResourceId(programField.getReference())); + } + } + private static class RClassFieldToValueStore { - private final Map<DexField, IntList> valueMapping; + private Map<DexField, IntList> valueMapping; private RClassFieldToValueStore(Map<DexField, IntList> valueMapping) { this.valueMapping = valueMapping; } + boolean hasField(DexField field) { + return valueMapping.containsKey(field); + } + + void pruneItems(PrunedItems prunedItems) { + valueMapping.keySet().removeIf(prunedItems::isRemoved); + } + + int getResourceId(DexField field) { + IntList integers = valueMapping.get(field); + assert integers.size() == 1; + return integers.get(0); + } + + @SuppressWarnings("ReferenceEquality") + public void rewrittenWithLens(GraphLens lens) { + Map<DexField, DexField> changed = new IdentityHashMap<>(); + valueMapping + .keySet() + .forEach( + dexField -> { + DexField rewritten = lens.lookupField(dexField); + if (rewritten != dexField) { + changed.put(dexField, rewritten); + } + }); + if (changed.size() > 0) { + Map<DexField, IntList> rewrittenMapping = new IdentityHashMap<>(); + valueMapping.forEach( + (key, value) -> rewrittenMapping.put(changed.getOrDefault(key, key), value)); + valueMapping = rewrittenMapping; + } + } + public static class Builder { private final Map<DexField, IntList> valueMapping = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index b34bf05..64f8399 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -23,6 +23,7 @@ import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.ir.code.InvokeType; import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils; +import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter; import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; import com.android.tools.r8.optimize.MemberRebindingLens; @@ -384,10 +385,15 @@ return true; } - public boolean hasCustomCodeRewritings() { + public boolean hasCustomLensCodeRewriter() { return false; } + public CustomLensCodeRewriter getCustomLensCodeRewriter() { + assert hasCustomLensCodeRewriter(); + return null; + } + public boolean isAppliedLens() { return false; }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java new file mode 100644 index 0000000..84599f0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLensWithCustomLensCodeRewriter.java
@@ -0,0 +1,51 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.graph.lens; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter; +import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap; +import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; +import java.util.Map; + +public class NestedGraphLensWithCustomLensCodeRewriter extends NestedGraphLens { + + private CustomLensCodeRewriter customLensCodeRewriter; + + public NestedGraphLensWithCustomLensCodeRewriter( + AppView<?> appView, + BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, + BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap, + BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap) { + super(appView, fieldMap, methodMap, typeMap); + } + + public NestedGraphLensWithCustomLensCodeRewriter( + AppView<?> appView, + BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, + Map<DexMethod, DexMethod> methodMap, + BidirectionalManyToManyRepresentativeMap<DexType, DexType> typeMap, + BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) { + super(appView, fieldMap, methodMap, typeMap, newMethodSignatures); + } + + @Override + public boolean hasCustomLensCodeRewriter() { + return true; + } + + public void setCustomLensCodeRewriter(CustomLensCodeRewriter customLensCodeRewriter) { + this.customLensCodeRewriter = customLensCodeRewriter; + } + + @Override + public CustomLensCodeRewriter getCustomLensCodeRewriter() { + assert customLensCodeRewriter != null; + return customLensCodeRewriter; + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java index acf1bce..11a3951 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoResourceClasses.java
@@ -11,7 +11,8 @@ @Override public boolean canMerge(DexProgramClass program) { - return !program.getSimpleName().startsWith("R$"); + String simpleName = program.getSimpleName(); + return !simpleName.startsWith("R$") && !simpleName.contains("$R$"); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 0e13017..2a10e4c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -259,7 +259,7 @@ : null; this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness); this.numberUnboxer = NumberUnboxer.create(appViewWithLiveness); - this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer); + this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness); this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter); this.outliner = Outliner.create(appViewWithLiveness); this.memberValuePropagation = new R8MemberValuePropagation(appViewWithLiveness);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index 9cb88c3..ad14d80 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -107,7 +107,7 @@ import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier; import com.android.tools.r8.ir.optimize.AffectedValues; -import com.android.tools.r8.ir.optimize.enums.EnumUnboxer; +import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter; import com.android.tools.r8.optimize.MemberRebindingAnalysis; import com.android.tools.r8.optimize.argumentpropagation.lenscoderewriter.NullCheckInserter; import com.android.tools.r8.utils.ArrayUtils; @@ -159,13 +159,11 @@ private final AppView<? extends AppInfoWithClassHierarchy> appView; private final DexItemFactory factory; - private final EnumUnboxer enumUnboxer; private final InternalOptions options; - LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) { + LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) { this.appView = appView; this.factory = appView.dexItemFactory(); - this.enumUnboxer = enumUnboxer; this.options = appView.options(); } @@ -227,10 +225,11 @@ Set<UnusedArgument> unusedArguments = Sets.newIdentityHashSet(); rewriteArguments( code, originalMethodReference, prototypeChanges, affectedPhis, unusedArguments); - if (graphLens.hasCustomCodeRewritings()) { - assert graphLens.isEnumUnboxerLens(); + if (graphLens.hasCustomLensCodeRewriter()) { assert graphLens.getPrevious() == codeLens; - affectedPhis.addAll(enumUnboxer.rewriteCode(code, methodProcessor, prototypeChanges)); + CustomLensCodeRewriter customLensCodeRewriter = graphLens.getCustomLensCodeRewriter(); + affectedPhis.addAll( + customLensCodeRewriter.rewriteCode(code, methodProcessor, prototypeChanges, graphLens)); } if (!unusedArguments.isEmpty()) { for (UnusedArgument unusedArgument : unusedArguments) { @@ -1096,10 +1095,10 @@ assert currentLens.isNonIdentityLens(); NonIdentityGraphLens currentNonIdentityLens = currentLens.asNonIdentityLens(); NonIdentityGraphLens fromInclusiveLens = currentNonIdentityLens; - if (!currentNonIdentityLens.hasCustomCodeRewritings()) { + if (!currentNonIdentityLens.hasCustomLensCodeRewriter()) { GraphLens fromInclusiveLensPredecessor = fromInclusiveLens.getPrevious(); while (fromInclusiveLensPredecessor.isNonIdentityLens() - && !fromInclusiveLensPredecessor.hasCustomCodeRewritings() + && !fromInclusiveLensPredecessor.hasCustomLensCodeRewriter() && fromInclusiveLensPredecessor != codeLens) { fromInclusiveLens = fromInclusiveLensPredecessor.asNonIdentityLens(); fromInclusiveLensPredecessor = fromInclusiveLens.getPrevious();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java index 6b6f2ad..8b4cbce 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -212,8 +212,6 @@ appView.clearMethodResolutionOptimizationInfoCollection(); - enumUnboxer.unsetRewriter(); - // All the code that should be impacted by the lenses inserted between phase 1 and phase 2 // have now been processed and rewritten, we clear code lens rewriting so that the class // staticizer and phase 3 does not perform again the rewriting.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java index b1a71cf..e270daf 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -320,6 +320,20 @@ cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface())); if (returnConversion != null) { + assert returnConversion.getArity() == 1 || returnConversion.getArity() == 2; + if (returnConversion.getArity() == 2) { + // If there is a second parameter, pass the receiver. + if (!invoke.isInvokeSuper(context.getHolderType())) { + appView + .reporter() + .error( + "Cannot generate inlined api conversion for return type for " + + invoke.getMethod() + + " in " + + context.getReference()); + } + cfInstructions.add(new CfLoad(ValueType.OBJECT, 0)); + } cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false)); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CustomLensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CustomLensCodeRewriter.java new file mode 100644 index 0000000..1f046d2 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/CustomLensCodeRewriter.java
@@ -0,0 +1,25 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize; + +import com.android.tools.r8.graph.lens.NonIdentityGraphLens; +import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Phi; +import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.google.common.collect.Sets; +import java.util.Set; + +public interface CustomLensCodeRewriter { + + CustomLensCodeRewriter EMPTY = + (code, methodProcessor, prototypeChanges, lens) -> Sets.newIdentityHashSet(); + + Set<Phi> rewriteCode( + IRCode code, + MethodProcessor methodProcessor, + RewrittenPrototypeDescription prototypeChanges, + NonIdentityGraphLens lens); +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java index cc90f9b..4e07fba 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -8,18 +8,14 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.lens.GraphLens; -import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Timing; -import java.util.Collections; -import java.util.Set; import java.util.concurrent.ExecutorService; public class EmptyEnumUnboxer extends EnumUnboxer { @@ -58,14 +54,6 @@ } @Override - public Set<Phi> rewriteCode( - IRCode code, - MethodProcessor methodProcessor, - RewrittenPrototypeDescription prototypeChanges) { - return Collections.emptySet(); - } - - @Override @SuppressWarnings("BadImport") public void rewriteWithLens() { // Intentionally empty. @@ -84,11 +72,6 @@ } @Override - public void unsetRewriter() { - // Intentionally empty. - } - - @Override public void updateEnumUnboxingCandidatesInfo() { // Intentionally empty. }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java index 48ae3b2..081d608 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -8,17 +8,14 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.lens.GraphLens; -import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.conversion.MethodProcessor; import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder; import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.Timing; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -44,9 +41,6 @@ public abstract void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues); - public abstract Set<Phi> rewriteCode( - IRCode code, MethodProcessor methodProcessor, RewrittenPrototypeDescription prototypeChanges); - public abstract void rewriteWithLens(); @SuppressWarnings("BadImport") @@ -59,7 +53,5 @@ Timing timing) throws ExecutionException; - public abstract void unsetRewriter(); - public abstract void updateEnumUnboxingCandidatesInfo(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index 5821b3a..5132141 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -166,9 +166,6 @@ checkNotNullMethodsBuilder; private final DexClassAndField ordinalField; - - private EnumUnboxingRewriter enumUnboxerRewriter; - private final boolean debugLogEnabled; private final Map<DexType, List<Reason>> debugLogs; @@ -764,13 +761,12 @@ updateOptimizationInfos(executorService, feedback, treeFixerResult, previousLens); - enumUnboxerRewriter = + enumUnboxingLens.setCustomLensCodeRewriter( new EnumUnboxingRewriter( appView, treeFixerResult.getCheckNotNullToCheckNotZeroMapping(), - enumUnboxingLens, enumDataMap, - utilityClasses); + utilityClasses)); // Ensure determinism of method-to-reprocess set. appView.testing().checkDeterminism(postMethodProcessorBuilder::dump); @@ -1785,22 +1781,4 @@ enumUnboxingCandidatesInfo.addPrunedMethod(method); methodsDependingOnLibraryModelisation.remove(method.getReference(), appView.graphLens()); } - - @Override - public Set<Phi> rewriteCode( - IRCode code, - MethodProcessor methodProcessor, - RewrittenPrototypeDescription prototypeChanges) { - // This has no effect during primary processing since the enumUnboxerRewriter is set - // in between primary and post processing. - if (enumUnboxerRewriter != null) { - return enumUnboxerRewriter.rewriteCode(code, methodProcessor, prototypeChanges); - } - return Sets.newIdentityHashSet(); - } - - @Override - public void unsetRewriter() { - enumUnboxerRewriter = null; - } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java index 95e4ea6..37b6314 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -14,7 +14,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.lens.MethodLookupResult; -import com.android.tools.r8.graph.lens.NestedGraphLens; +import com.android.tools.r8.graph.lens.NestedGraphLensWithCustomLensCodeRewriter; import com.android.tools.r8.graph.proto.ArgumentInfoCollection; import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.graph.proto.RewrittenTypeInfo; @@ -44,7 +44,7 @@ import java.util.Map; import java.util.Set; -public class EnumUnboxingLens extends NestedGraphLens { +public class EnumUnboxingLens extends NestedGraphLensWithCustomLensCodeRewriter { private final AbstractValueFactory abstractValueFactory; private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod; @@ -68,11 +68,6 @@ } @Override - public boolean hasCustomCodeRewritings() { - return true; - } - - @Override public boolean isEnumUnboxerLens() { return true; } @@ -82,6 +77,10 @@ return this; } + public EnumDataMap getUnboxedEnums() { + return unboxedEnums; + } + @Override public boolean isContextFreeForMethods(GraphLens codeLens) { if (codeLens == this) { @@ -383,7 +382,8 @@ originalCheckNotNullMethodSignature, checkNotNullMethod.getReference()); } - public EnumUnboxingLens build(AppView<?> appView, Set<DexMethod> dispatchMethods) { + public EnumUnboxingLens build( + AppView<AppInfoWithLiveness> appView, Set<DexMethod> dispatchMethods) { assert !typeMap.isEmpty(); return new EnumUnboxingLens( appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java index 46184c4..da575cd 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.graph.lens.NonIdentityGraphLens; import com.android.tools.r8.graph.proto.ArgumentInfo; import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; import com.android.tools.r8.graph.proto.RewrittenTypeInfo; @@ -42,6 +43,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter; import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData; import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification; import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification; @@ -58,27 +60,24 @@ import java.util.Map; import java.util.Set; -public class EnumUnboxingRewriter { +public class EnumUnboxingRewriter implements CustomLensCodeRewriter { private final AppView<AppInfoWithLiveness> appView; private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping; private final DexItemFactory factory; private final InternalOptions options; private final EnumDataMap unboxedEnumsData; - private final EnumUnboxingLens enumUnboxingLens; private final EnumUnboxingUtilityClasses utilityClasses; EnumUnboxingRewriter( AppView<AppInfoWithLiveness> appView, Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping, - EnumUnboxingLens enumUnboxingLens, EnumDataMap unboxedEnumsInstanceFieldData, EnumUnboxingUtilityClasses utilityClasses) { this.appView = appView; - this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping; this.factory = appView.dexItemFactory(); this.options = appView.options(); - this.enumUnboxingLens = enumUnboxingLens; + this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping; this.unboxedEnumsData = unboxedEnumsInstanceFieldData; this.utilityClasses = utilityClasses; } @@ -146,15 +145,19 @@ return convertedEnums; } - Set<Phi> rewriteCode( + @Override + public Set<Phi> rewriteCode( IRCode code, MethodProcessor methodProcessor, - RewrittenPrototypeDescription prototypeChanges) { + RewrittenPrototypeDescription prototypeChanges, + NonIdentityGraphLens graphLens) { // We should not process the enum methods, they will be removed and they may contain invalid // rewriting rules. if (unboxedEnumsData.isEmpty()) { return Sets.newIdentityHashSet(); } + assert graphLens.isEnumUnboxerLens(); + EnumUnboxingLens enumUnboxingLens = graphLens.asEnumUnboxerLens(); assert code.isConsistentSSABeforeTypesAreCorrect(appView); EnumUnboxerMethodProcessorEventConsumer eventConsumer = methodProcessor.getEventConsumer(); Set<Phi> affectedPhis = Sets.newIdentityHashSet(); @@ -192,7 +195,8 @@ blocks, block, iterator, - instruction.asInvokeMethodWithReceiver()); + instruction.asInvokeMethodWithReceiver(), + enumUnboxingLens); } else if (instruction.isNewArrayFilled()) { rewriteNewArrayFilled(instruction.asNewArrayFilled(), code, convertedEnums, iterator); } else if (instruction.isInvokeStatic()) { @@ -379,7 +383,8 @@ BasicBlockIterator blocks, BasicBlock block, InstructionListIterator iterator, - InvokeMethodWithReceiver invoke) { + InvokeMethodWithReceiver invoke, + EnumUnboxingLens enumUnboxingLens) { ProgramMethod context = code.context(); // If the receiver is null, then the invoke is not rewritten even if the receiver is an // unboxed enum, but we end up with null.ordinal() or similar which has the correct behavior. @@ -699,7 +704,7 @@ } } - public void rewriteNullCheck( + private void rewriteNullCheck( InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java index 59be01e..35e4f74 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -54,6 +54,7 @@ import com.android.tools.r8.ir.conversion.MethodConversionOptions; import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer; import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor; +import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter; import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData; import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification; import com.android.tools.r8.ir.optimize.enums.code.CheckNotZeroCode; @@ -164,6 +165,8 @@ converter.outliner.rewriteWithLens(); // Create mapping from checkNotNull() to checkNotZero() methods. + // The customLensCodeRewriter has to be non null for the duplication but is effectively unused. + lens.setCustomLensCodeRewriter(CustomLensCodeRewriter.EMPTY); BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = duplicateCheckNotNullMethods(converter, executorService); @@ -1120,7 +1123,7 @@ return methodsToProcess; } - EnumUnboxingLens getLens() { + public EnumUnboxingLens getLens() { return lens; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java index f47e771..1dc3ca2 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -50,6 +50,10 @@ register(new ObjectsMethodOptimizer(appView)); register(new StringBuilderMethodOptimizer(appView)); register(new StringMethodOptimizer(appView)); + if (appView.enableWholeProgramOptimizations() + && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking()) { + register(new ResourcesMemberOptimizer(appView)); + } if (appView.enableWholeProgramOptimizations()) { // Subtyping is required to prove the enum class is a subtype of java.lang.Enum. register(new EnumMethodOptimizer(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java new file mode 100644 index 0000000..1246516 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourcesMemberOptimizer.java
@@ -0,0 +1,147 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.optimize.library; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexClassAndMethod; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.FieldResolutionResult; +import com.android.tools.r8.graph.ProgramField; +import com.android.tools.r8.graph.TopDownClassHierarchyTraversal; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.BasicBlockIterator; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.InvokeVirtual; +import com.android.tools.r8.ir.optimize.AffectedValues; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class ResourcesMemberOptimizer extends StatelessLibraryMethodModelCollection { + + private final AppView<?> appView; + private final DexItemFactory dexItemFactory; + private Optional<Boolean> allowStringInlining = Optional.empty(); + + ResourcesMemberOptimizer(AppView<?> appView) { + this.appView = appView; + this.dexItemFactory = appView.dexItemFactory(); + } + + @SuppressWarnings("ReferenceEquality") + private synchronized boolean allowInliningOfGetStringCalls() { + if (allowStringInlining.isPresent()) { + return allowStringInlining.get(); + } + // TODO(b/312406163): Allow androidx classes that overwrite this, but don't change the value + // or have side effects. + allowStringInlining = Optional.of(true); + Map<DexClass, Boolean> cachedResults = new IdentityHashMap<>(); + TopDownClassHierarchyTraversal.forProgramClasses(appView.withClassHierarchy()) + .visit( + appView.appInfo().classes(), + clazz -> { + if (isResourcesSubtype(cachedResults, clazz)) { + DexEncodedMethod dexEncodedMethod = + clazz.lookupMethod( + dexItemFactory.androidResourcesGetStringProto, + dexItemFactory.androidResourcesGetStringName); + if (dexEncodedMethod != null) { + // TODO(b/312695444): Break out of traversal when supported. + allowStringInlining = Optional.of(false); + } + } + }); + return allowStringInlining.get(); + } + + @SuppressWarnings("ReferenceEquality") + private boolean isResourcesSubtype(Map<DexClass, Boolean> cachedLookups, DexClass dexClass) { + Boolean cachedValue = cachedLookups.get(dexClass); + if (cachedValue != null) { + return cachedValue; + } + if (dexClass.type == dexItemFactory.androidResourcesType) { + return true; + } + if (dexClass.type == dexItemFactory.objectType) { + return false; + } + + if (dexClass.superType != null) { + DexClass superClass = appView.definitionFor(dexClass.superType); + if (superClass != null) { + boolean superIsResourcesSubtype = isResourcesSubtype(cachedLookups, superClass); + cachedLookups.put(dexClass, superIsResourcesSubtype); + return superIsResourcesSubtype; + } + } + cachedLookups.put(dexClass, false); + return false; + } + + @Override + public DexType getType() { + return dexItemFactory.androidResourcesType; + } + + @Override + public InstructionListIterator optimize( + IRCode code, + BasicBlockIterator blockIterator, + InstructionListIterator instructionIterator, + InvokeMethod invoke, + DexClassAndMethod singleTarget, + AffectedValues affectedValues, + Set<BasicBlock> blocksToRemove) { + if (allowInliningOfGetStringCalls() + && singleTarget + .getReference() + .isIdenticalTo(dexItemFactory.androidResourcesGetStringMethod)) { + maybeInlineGetString(code, instructionIterator, invoke, affectedValues); + } + return instructionIterator; + } + + @SuppressWarnings("ReferenceEquality") + private void maybeInlineGetString( + IRCode code, + InstructionListIterator instructionIterator, + InvokeMethod invoke, + AffectedValues affectedValues) { + if (invoke.isInvokeVirtual()) { + InvokeVirtual invokeVirtual = invoke.asInvokeVirtual(); + DexMethod invokedMethod = invokeVirtual.getInvokedMethod(); + assert invokedMethod.isIdenticalTo(dexItemFactory.androidResourcesGetStringMethod); + assert invoke.inValues().size() == 2; + Instruction valueDefinition = invoke.getLastArgument().definition; + if (valueDefinition != null && valueDefinition.isStaticGet()) { + DexField field = valueDefinition.asStaticGet().getField(); + FieldResolutionResult fieldResolutionResult = + appView.appInfo().resolveField(field, code.context()); + ProgramField resolvedField = fieldResolutionResult.getProgramField(); + if (resolvedField != null) { + String singleStringValueForField = + appView.getResourceAnalysisResult().getSingleStringValueForField(resolvedField); + if (singleStringValueForField != null) { + DexString value = dexItemFactory.createString(singleStringValueForField); + instructionIterator.replaceCurrentInstructionWithConstString( + appView, code, value, affectedValues); + } + } + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java index a43cbe6..15092b9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -34,6 +34,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.AffectedValues; +import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ValueUtils; import com.android.tools.r8.utils.ValueUtils.ArrayValues; import com.google.common.collect.ImmutableMap; @@ -54,7 +55,12 @@ StringMethodOptimizer(AppView<?> appView) { this.appView = appView; this.dexItemFactory = appView.dexItemFactory(); - this.enableStringFormatOptimizations = appView.options().enableStringFormatOptimization; + InternalOptions options = appView.options(); + this.enableStringFormatOptimizations = + appView.enableWholeProgramOptimizations() + && !options.debug + && options.isOptimizing() + && options.isShrinking(); this.valueOfToStringAppend = ImmutableMap.<DexMethod, DexMethod>builder() .put(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index c8d9806..d1a2dda 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3778,7 +3778,7 @@ } } - private void applyMinimumKeepInfoWhenLive( + public void applyMinimumKeepInfoWhenLive( ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) { applyMinimumKeepInfoWhenLive(field, minimumKeepInfo, EnqueuerEvent.unconditional()); }
diff --git a/src/main/java/com/android/tools/r8/threading/ThreadingModule.java b/src/main/java/com/android/tools/r8/threading/ThreadingModule.java index 1525e70..7e374c4 100644 --- a/src/main/java/com/android/tools/r8/threading/ThreadingModule.java +++ b/src/main/java/com/android/tools/r8/threading/ThreadingModule.java
@@ -10,8 +10,6 @@ import com.android.tools.r8.keepanno.annotations.MemberAccessFlags; import com.android.tools.r8.keepanno.annotations.UsedByReflection; import com.android.tools.r8.keepanno.annotations.UsesReflection; -import com.android.tools.r8.threading.providers.blocking.ThreadingModuleBlockingProvider; -import com.android.tools.r8.threading.providers.singlethreaded.ThreadingModuleSingleThreadedProvider; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -43,20 +41,20 @@ // Splitting up the names to make reflective identification unlikely. // We explicitly don't want R8 to optimize out the reflective lookup. private static final String PACKAGE = "com.android.tools.r8.threading.providers"; - private static final String[] IMPLEMENTATIONS = { - "blocking.ThreadingModuleBlockingProvider", - "singlethreaded.ThreadingModuleSingleThreadedProvider" - }; + private static final String BLOCKING_PROVIDER = "blocking.ThreadingModuleBlockingProvider"; + private static final String SINGLE_THREADED_PROVIDER = + "singlethreaded.ThreadingModuleSingleThreadedProvider"; + private static final String[] IMPLEMENTATIONS = {BLOCKING_PROVIDER, SINGLE_THREADED_PROVIDER}; @UsesReflection({ @KeepTarget( kind = KeepItemKind.CLASS_AND_MEMBERS, - classConstant = ThreadingModuleBlockingProvider.class, + className = PACKAGE + "." + "blocking.ThreadingModuleBlockingProvider", methodName = "<init>", methodParameters = {}), @KeepTarget( kind = KeepItemKind.CLASS_AND_MEMBERS, - classConstant = ThreadingModuleSingleThreadedProvider.class, + className = PACKAGE + "." + "singlethreaded.ThreadingModuleSingleThreadedProvider", methodName = "<init>", methodParameters = {}) })
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java index a50dbb9..1ff1d22 100644 --- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java +++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -73,7 +73,7 @@ writeDirectoryNow(data.name, handler); } else { assert data.content != null; - writeFileNow(data.name, data.content, handler); + writeFileNow(data.name, data.content, handler, data.storeCompressed); } } } @@ -135,9 +135,9 @@ ByteDataView view = ByteDataView.of(ByteStreams.toByteArray(in)); synchronized (this) { if (AndroidApiDataAccess.isApiDatabaseEntry(name)) { - writeFileNow(name, view, handler); + writeFileNow(name, view, handler, true); } else { - delayedWrites.add(DelayedData.createFile(name, view)); + delayedWrites.add(DelayedData.createFile(name, view, true)); } } } catch (IOException e) { @@ -150,16 +150,25 @@ @Override public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) { - delayedWrites.add(DelayedData.createFile(name, ByteDataView.of(content.copyByteData()))); + addFile(name, content, handler, true); } - private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) { + public synchronized void addFile( + String name, ByteDataView content, DiagnosticsHandler handler, boolean storeCompressed) { + delayedWrites.add( + DelayedData.createFile(name, ByteDataView.of(content.copyByteData()), storeCompressed)); + } + + private void writeFileNow( + String name, ByteDataView content, DiagnosticsHandler handler, boolean compressed) { try { ZipUtils.writeToZipStream( getStream(), name, content, - AndroidApiDataAccess.isApiDatabaseEntry(name) ? ZipEntry.STORED : ZipEntry.DEFLATED); + AndroidApiDataAccess.isApiDatabaseEntry(name) || !compressed + ? ZipEntry.STORED + : ZipEntry.DEFLATED); } catch (IOException e) { handleIOException(e, handler); } @@ -168,7 +177,7 @@ private void writeNextIfAvailable(DiagnosticsHandler handler) { DelayedData data = delayedClassesDexFiles.remove(classesFileIndex); while (data != null) { - writeFileNow(data.name, data.content, handler); + writeFileNow(data.name, data.content, handler, data.storeCompressed); classesFileIndex++; data = delayedClassesDexFiles.remove(classesFileIndex); } @@ -179,13 +188,13 @@ int index, String name, ByteDataView content, DiagnosticsHandler handler) { if (index == classesFileIndex) { // Fast case, we got the file in order (or we only had one). - writeFileNow(name, content, handler); + writeFileNow(name, content, handler, true); classesFileIndex++; writeNextIfAvailable(handler); } else { // Data is released in the application writer, take a copy. - delayedClassesDexFiles.put(index, - new DelayedData(name, ByteDataView.of(content.copyByteData()), false)); + delayedClassesDexFiles.put( + index, new DelayedData(name, ByteDataView.of(content.copyByteData()), false, true)); } } @@ -203,19 +212,23 @@ public final String name; public final ByteDataView content; public final boolean isDirectory; + public final boolean storeCompressed; - public static DelayedData createFile(String name, ByteDataView content) { - return new DelayedData(name, content, false); + public static DelayedData createFile( + String name, ByteDataView content, boolean storeCompressed) { + return new DelayedData(name, content, false, storeCompressed); } public static DelayedData createDirectory(String name) { - return new DelayedData(name, null, true); + return new DelayedData(name, null, true, true); } - private DelayedData(String name, ByteDataView content, boolean isDirectory) { + private DelayedData( + String name, ByteDataView content, boolean isDirectory, boolean storeCompressed) { this.name = name; this.content = content; this.isDirectory = isDirectory; + this.storeCompressed = storeCompressed; } @Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index de093c4..ae6e72c 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -280,9 +280,6 @@ itemFactory = proguardConfiguration.getDexItemFactory(); enableTreeShaking = proguardConfiguration.isShrinking(); enableMinification = proguardConfiguration.isObfuscating(); - // TODO(b/244238384): Enable. - enableStringFormatOptimization = - System.getProperty("com.android.tools.r8.optimizeStringFormat") != null; if (!proguardConfiguration.isOptimizing()) { // TODO(b/171457102): Avoid the need for this. @@ -328,7 +325,6 @@ disableGlobalOptimizations(); enableNameReflectionOptimization = false; enableStringConcatenationOptimization = false; - enableStringFormatOptimization = false; } public void disableGlobalOptimizations() { @@ -413,7 +409,6 @@ public boolean enableNameReflectionOptimization = true; public boolean enableStringConcatenationOptimization = true; // Enabled only for R8 (not D8). - public boolean enableStringFormatOptimization; public boolean enableTreeShakingOfLibraryMethodOverrides = false; public boolean encodeChecksums = false; public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java index e02d3e0..2b11a9f 100644 --- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -37,6 +37,7 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -100,6 +101,15 @@ } } + public static void iter(Path zipFilePath, Consumer<ZipEntry> entryConsumer) throws IOException { + try (ZipFile zipFile = new ZipFile(zipFilePath.toFile(), StandardCharsets.UTF_8)) { + final Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + entryConsumer.accept(entries.nextElement()); + } + } + } + @SuppressWarnings("UnnecessaryParentheses") public static boolean containsEntry(Path zipfile, String name) throws IOException { BooleanBox result = new BooleanBox();
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java index 8273c96..e0cca4f 100644 --- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java +++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -4,7 +4,11 @@ package com.android.build.shrinker.r8integration; +import com.android.aapt.Resources.ConfigValue; +import com.android.aapt.Resources.Entry; +import com.android.aapt.Resources.Item; import com.android.aapt.Resources.ResourceTable; +import com.android.aapt.Resources.Value; import com.android.build.shrinker.NoDebugReporter; import com.android.build.shrinker.ResourceShrinkerModel; import com.android.build.shrinker.ResourceTableUtilKt; @@ -16,7 +20,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class R8ResourceShrinkerState { @@ -38,12 +44,17 @@ } public static class R8ResourceShrinkerModel extends ResourceShrinkerModel { + private final Map<Integer, String> stringResourcesWithSingleValue = new HashMap<>(); public R8ResourceShrinkerModel( ShrinkerDebugReporter debugReporter, boolean supportMultipackages) { super(debugReporter, supportMultipackages); } + public Map<Integer, String> getStringResourcesWithSingleValue() { + return stringResourcesWithSingleValue; + } + // Similar to instantiation in ProtoResourceTableGatherer, but using an inputstream. void instantiateFromResourceTable(InputStream inputStream) { try { @@ -60,14 +71,34 @@ .forEachRemaining( entryWrapper -> { ResourceType resourceType = ResourceType.fromClassName(entryWrapper.getType()); + Entry entry = entryWrapper.getEntry(); + int entryId = entryWrapper.getId(); + recordSingleValueResources(resourceType, entry, entryId); if (resourceType != ResourceType.STYLEABLE) { this.addResource( resourceType, entryWrapper.getPackageName(), - ResourcesUtil.resourceNameToFieldName(entryWrapper.getEntry().getName()), - entryWrapper.getId()); + ResourcesUtil.resourceNameToFieldName(entry.getName()), + entryId); } }); } + + private void recordSingleValueResources(ResourceType resourceType, Entry entry, int entryId) { + if (!entry.hasOverlayableItem() && entry.getConfigValueList().size() == 1) { + if (resourceType == ResourceType.STRING) { + ConfigValue configValue = entry.getConfigValue(0); + if (configValue.hasValue()) { + Value value = configValue.getValue(); + if (value.hasItem()) { + Item item = value.getItem(); + if (item.hasStr()) { + stringResourcesWithSingleValue.put(entryId, item.getStr().getValue()); + } + } + } + } + } + } } }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java index 7ae3463..d1eb192 100644 --- a/src/test/java/com/android/tools/r8/R8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -291,6 +291,11 @@ return self(); } + public T enableOptimizedShrinking() { + builder.setResourceShrinkerConfiguration(b -> b.enableOptimizedShrinkingWithR8().build()); + return self(); + } + /** * Allow info, warning, and error diagnostics. * @@ -905,8 +910,8 @@ getBuilder() .setAndroidResourceProvider( new ArchiveProtoAndroidResourceProvider(resources, new PathOrigin(resources))); - getBuilder().setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output)); - self(); + getBuilder() + .setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output, resources)); return addProgramClassFileData(testResource.getRClass().getClassFileData()); }
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java index 0ee66c7..021902d 100644 --- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java +++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -10,9 +10,12 @@ import com.android.aapt.Resources.ConfigValue; import com.android.aapt.Resources.Item; import com.android.aapt.Resources.ResourceTable; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestBase.Backend; import com.android.tools.r8.TestRuntime.CfRuntime; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.transformers.ClassTransformer; import com.android.tools.r8.transformers.MethodTransformer; import com.android.tools.r8.utils.AndroidApiLevel; @@ -34,12 +37,16 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; import org.junit.Assert; import org.junit.rules.TemporaryFolder; public class AndroidResourceTestingUtils { + private static final String RESOURCES_DESCRIPTOR = + TestBase.descriptor(com.android.tools.r8.androidresources.Resources.class); enum RClassType { STRING, @@ -53,6 +60,32 @@ } } + public static Path resourcesClassAsDex(TemporaryFolder temp) throws Exception { + return TestBase.testForD8(temp, Backend.DEX) + .addProgramClassFileData(resouresClassAsJavaClass()) + .compile() + .writeToZip(); + } + + public static byte[] resouresClassAsJavaClass() throws IOException { + return transformer(com.android.tools.r8.androidresources.Resources.class) + .setClassDescriptor(DexItemFactory.androidResourcesDescriptorString) + .replaceClassDescriptorInMethodInstructions( + RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString) + .replaceClassDescriptorInMembers( + RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString) + .transform(); + } + + public static byte[] transformResourcesReferences(Class clazz) throws IOException { + return transformer(clazz) + .replaceClassDescriptorInMethodInstructions( + RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString) + .replaceClassDescriptorInMembers( + RESOURCES_DESCRIPTOR, DexItemFactory.androidResourcesDescriptorString) + .transform(); + } + private static String rClassWithoutNamespaceAndOuter(Class clazz) { return rClassWithoutNamespaceAndOuter(clazz.getName()); } @@ -223,6 +256,8 @@ public static class AndroidTestResourceBuilder { private String manifest; private final Map<String, String> stringValues = new TreeMap<>(); + private final Set<String> stringValuesWithExtraLanguage = new TreeSet<>(); + private final Map<String, String> overlayableValues = new TreeMap<>(); private final Map<String, Integer> styleables = new TreeMap<>(); private final Map<String, byte[]> drawables = new TreeMap<>(); private final Map<String, String> xmlFiles = new TreeMap<>(); @@ -244,7 +279,7 @@ addStringValue(name, name); } if (rClassType == RClassType.DRAWABLE) { - addDrawable(name, TINY_PNG); + addDrawable(name + ".png", TINY_PNG); } if (rClassType == RClassType.STYLEABLE) { // Add 4 different values, i.e., the array will be 4 integers. @@ -282,6 +317,16 @@ return this; } + AndroidTestResourceBuilder addExtraLanguageString(String name) { + stringValuesWithExtraLanguage.add(name); + return this; + } + + AndroidTestResourceBuilder setOverlayableFor(String type, String name) { + overlayableValues.put(type, name); + return this; + } + AndroidTestResourceBuilder setPackageId(int packageId) { this.packageId = packageId; return this; @@ -298,7 +343,16 @@ Path resFolder = temp.newFolder("res").toPath(); Path valuesFolder = temp.newFolder("res", "values").toPath(); if (stringValues.size() > 0) { - FileUtils.writeTextFile(valuesFolder.resolve("strings.xml"), createStringResourceXml()); + FileUtils.writeTextFile( + valuesFolder.resolve("strings.xml"), createStringResourceXml(false)); + if (stringValuesWithExtraLanguage.size() > 0) { + Path languageValues = temp.newFolder("res", "values-da").toPath(); + FileUtils.writeTextFile( + languageValues.resolve("strings.xml"), createStringResourceXml(true)); + } + } + if (overlayableValues.size() > 0) { + FileUtils.writeTextFile(valuesFolder.resolve("overlayable.xml"), createOverlayableXml()); } if (styleables.size() > 0) { FileUtils.writeTextFile( @@ -401,11 +455,30 @@ new AndroidTestRClass(rClassJavaFile, rewrittenRClassFiles), output); } - private String createStringResourceXml() { + private String createOverlayableXml() { StringBuilder stringBuilder = new StringBuilder("<resources>\n"); + + stringBuilder.append("<overlayable name=\"OurOverlayables\">\n"); + stringBuilder.append("<policy type=\"public\">\n"); + overlayableValues.forEach( + (type, name) -> + stringBuilder.append("<item type=\"" + type + "\" name=\"" + name + "\" />\n")); + stringBuilder.append("</policy>\n"); + stringBuilder.append("</overlayable>"); + stringBuilder.append("</resources>"); + return stringBuilder.toString(); + } + + private String createStringResourceXml(boolean nonDefaultLanguage) { + StringBuilder stringBuilder = new StringBuilder("<resources>\n"); + String languagePostFix = nonDefaultLanguage ? "_da" : ""; stringValues.forEach( - (name, value) -> - stringBuilder.append("<string name=\"" + name + "\">" + value + "</string>\n")); + (name, value) -> { + if (!nonDefaultLanguage || stringValuesWithExtraLanguage.contains(name)) { + stringBuilder.append( + "<string name=\"" + name + "\">" + value + languagePostFix + "</string>\n"); + } + }); stringBuilder.append("</resources>"); return stringBuilder.toString(); } @@ -496,4 +569,5 @@ // The XML document <x/> as a proto packed with AAPT2 public static final byte[] TINY_PROTO_XML = new byte[] {0xa, 0x3, 0x1a, 0x1, 0x78, 0x1a, 0x2, 0x8, 0x1}; + }
diff --git a/src/test/java/com/android/tools/r8/androidresources/Resources.java b/src/test/java/com/android/tools/r8/androidresources/Resources.java new file mode 100644 index 0000000..ef01a99 --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/Resources.java
@@ -0,0 +1,16 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.androidresources; + +public class Resources { + public static String GET_STRING_VALUE = "GET_STRING_VALUE"; + + // Returns the GET_STRING_VALUE to be able to distinguish resource inlined values from values + // we get from this call (i.e., not inlined). Inlined values are the actual values from the + // resource table. + public String getString(int id) { + return GET_STRING_VALUE; + } +}
diff --git a/src/test/java/com/android/tools/r8/androidresources/TestArchiveCompression.java b/src/test/java/com/android/tools/r8/androidresources/TestArchiveCompression.java new file mode 100644 index 0000000..e7d0e79 --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/TestArchiveCompression.java
@@ -0,0 +1,108 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.androidresources; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource; +import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder; +import com.android.tools.r8.utils.ZipUtils; +import java.io.IOException; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TestArchiveCompression extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(); + } + + public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception { + return new AndroidTestResourceBuilder() + .withSimpleManifestAndAppNameString() + .addRClassInitializeWithDefaultValues(R.string.class, R.drawable.class) + .build(temp); + } + + @Test + public void testR8() throws Exception { + AndroidTestResource testResources = getTestResources(temp); + Path resourceZip = testResources.getResourceZip(); + assertTrue(ZipUtils.containsEntry(resourceZip, "res/drawable/foobar.png")); + assertTrue(ZipUtils.containsEntry(resourceZip, "res/drawable/unused_drawable.png")); + assertTrue(ZipUtils.containsEntry(resourceZip, "AndroidManifest.xml")); + assertTrue(ZipUtils.containsEntry(resourceZip, "resources.pb")); + + validateCompression(resourceZip); + Path resourceOutput = temp.newFile("resources_out.zip").toPath(); + testForR8(parameters.getBackend()) + .setMinApi(parameters) + .addProgramClasses(FooBar.class) + .addAndroidResources(testResources, resourceOutput) + .addKeepMainRule(FooBar.class) + .compile() + .run(parameters.getRuntime(), FooBar.class) + .assertSuccess(); + assertTrue(ZipUtils.containsEntry(resourceOutput, "res/drawable/foobar.png")); + assertFalse(ZipUtils.containsEntry(resourceOutput, "res/drawable/unused_drawable.png")); + assertTrue(ZipUtils.containsEntry(resourceOutput, "AndroidManifest.xml")); + assertTrue(ZipUtils.containsEntry(resourceOutput, "resources.pb")); + validateCompression(resourceOutput); + } + + private static void validateCompression(Path resourceZip) throws IOException { + ZipUtils.iter( + resourceZip, + entry -> { + if (entry.getName().endsWith(".png")) { + assertEquals(ZipEntry.STORED, entry.getMethod()); + } else { + assertEquals(ZipEntry.DEFLATED, entry.getMethod()); + } + }); + } + + public static class FooBar { + + public static void main(String[] args) { + if (System.currentTimeMillis() == 0) { + System.out.println(R.drawable.foobar); + System.out.println(R.string.bar); + System.out.println(R.string.foo); + } + } + } + + public static class R { + + public static class string { + + public static int bar; + public static int foo; + public static int unused_string; + } + + public static class drawable { + + public static int foobar; + public static int unused_drawable; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/androidresources/TestResourceInlining.java b/src/test/java/com/android/tools/r8/androidresources/TestResourceInlining.java new file mode 100644 index 0000000..bd97b91 --- /dev/null +++ b/src/test/java/com/android/tools/r8/androidresources/TestResourceInlining.java
@@ -0,0 +1,173 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.androidresources; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.R8TestBuilder; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource; +import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TestResourceInlining extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameter(1) + public boolean optimize; + + @Parameter(2) + public boolean addResourcesSubclass; + + @Parameters(name = "{0}, optimize: {1}, addResourcesSubclass: {2}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(), + BooleanUtils.values(), + BooleanUtils.values()); + } + + public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception { + return new AndroidTestResourceBuilder() + .withSimpleManifestAndAppNameString() + .addRClassInitializeWithDefaultValues(R.string.class) + .setOverlayableFor("string", "overlayable") + .addExtraLanguageString("bar") + .build(temp); + } + + public static Path getAndroidResourcesClassInJar(TemporaryFolder temp) throws Exception { + byte[] androidResourcesClass = + transformer(Resources.class) + .setClassDescriptor(DexItemFactory.androidResourcesDescriptorString) + .transform(); + return testForR8(temp, Backend.DEX) + .addProgramClassFileData(androidResourcesClass) + .compile() + .writeToZip(); + } + + public byte[] getResourcesSubclass() throws IOException { + return transformer(ResourcesSubclass.class) + .setSuper(DexItemFactory.androidResourcesDescriptorString) + .replaceClassDescriptorInMethodInstructions( + descriptor(Resources.class), DexItemFactory.androidResourcesDescriptorString) + .transform(); + } + + @Test + public void testR8Optimized() throws Exception { + testForR8(parameters.getBackend()) + .setMinApi(parameters) + .addProgramClassFileData( + AndroidResourceTestingUtils.transformResourcesReferences(FooBar.class)) + .applyIf( + addResourcesSubclass, + builder -> { + builder.addProgramClassFileData(getResourcesSubclass()); + builder.addKeepClassRules(ResourcesSubclass.class); + }) + .addAndroidResources(getTestResources(temp)) + .addKeepMainRule(FooBar.class) + .applyIf(optimize, R8TestBuilder::enableOptimizedShrinking) + .addRunClasspathFiles(AndroidResourceTestingUtils.resourcesClassAsDex(temp)) + .compile() + .inspectShrunkenResources( + resourceTableInspector -> { + // We should eventually remove this when optimizing + resourceTableInspector.assertContainsResourceWithName("string", "foo"); + // Has multiple values, don't inline + resourceTableInspector.assertContainsResourceWithName("string", "bar"); + // Has overlayable value, don't inline + resourceTableInspector.assertContainsResourceWithName("string", "bar"); + resourceTableInspector.assertDoesNotContainResourceWithName( + "string", "unused_string"); + }) + .inspect( + inspector -> { + // We should have removed one of the calls to getString if we are optimizing. + MethodSubject mainMethodSubject = inspector.clazz(FooBar.class).mainMethod(); + assertThat(mainMethodSubject, isPresent()); + assertEquals( + mainMethodSubject + .streamInstructions() + .filter(InstructionSubject::isInvokeVirtual) + .filter( + i -> + i.getMethod() + .holder + .descriptor + .toString() + .equals(DexItemFactory.androidResourcesDescriptorString)) + .count(), + optimize && !addResourcesSubclass ? 2 : 3); + }) + .run(parameters.getRuntime(), FooBar.class) + .applyIf( + optimize && !addResourcesSubclass, + result -> { + result.assertSuccessWithOutputLines( + "foo", Resources.GET_STRING_VALUE, Resources.GET_STRING_VALUE); + }) + .applyIf( + !optimize || addResourcesSubclass, + result -> { + result.assertSuccessWithOutputLines( + Resources.GET_STRING_VALUE, + Resources.GET_STRING_VALUE, + Resources.GET_STRING_VALUE); + }); + } + + public static class FooBar { + + public static void main(String[] args) { + Resources resources = new Resources(); + // Ensure that we correctly handle the out value propagation + String s = resources.getString(R.string.foo); + String t = "X"; + String u = System.currentTimeMillis() > 0 ? s : t; + System.out.println(u); + System.out.println(resources.getString(R.string.bar)); + System.out.println(resources.getString(R.string.overlayable)); + } + } + + public static class ResourcesSubclass extends Resources { + + @Override + public String getString(int id) { + System.out.println("foobar"); + return super.getString(id); + } + } + + public static class R { + + public static class string { + public static int foo; + public static int bar; + public static int overlayable; + public static int unused_string; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java index da0f3d1..8900bd3 100644 --- a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java +++ b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
@@ -35,8 +35,7 @@ private static final Collection<Class<?>> CLASSES = ImmutableList.of(ClassRetained.class, SourceRetained.class, RuntimeRetained.class, A.class); - private static final String EXPECTED = - StringUtils.lines("@" + RuntimeRetained.class.getName() + "()"); + private static final String EXPECTED = StringUtils.lines(RuntimeRetained.class.getName()); @Parameters(name = "{0}, intermediate:{1}") public static List<Object[]> data() { @@ -66,7 +65,7 @@ public static void main(String[] args) { for (Annotation annotation : A.class.getAnnotations()) { - System.out.println(annotation); + System.out.println(annotation.annotationType().getName()); } } }
diff --git a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java index 037cc1d..a3ea4b1 100644 --- a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java +++ b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress.java
@@ -38,7 +38,7 @@ System.out.print(index++ + ": "); } for (Annotation annotation : annotations[i]) { - System.out.println(annotation); + System.out.println(annotation.annotationType().getName()); } } }
diff --git a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java index a88c723..d4b0a0e 100644 --- a/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java +++ b/src/test/java/com/android/tools/r8/examples/regress_62300145/Regress62300145TestRunner.java
@@ -39,8 +39,8 @@ @Override public String getExpected() { return StringUtils.joinLines( - "0: @com.android.tools.r8.examples.regress_62300145.Regress$A()", - "1: @com.android.tools.r8.examples.regress_62300145.Regress$A()", + "0: com.android.tools.r8.examples.regress_62300145.Regress$A", + "1: com.android.tools.r8.examples.regress_62300145.Regress$A", "2: "); }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java index 0f49f15..fa21a9f 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java
@@ -96,7 +96,6 @@ R8TestRunResult result = testForR8(parameters.getBackend()) .addProgramClasses(MAIN, TestFormattable.class) - .addOptionsModification(options -> options.enableStringFormatOptimization = true) .enableInliningAnnotations() .addKeepMainRule(MAIN) .setMinApi(parameters) @@ -110,7 +109,6 @@ R8TestRunResult result = testForR8(parameters.getBackend()) .addProgramClasses(MAIN, TestFormattable.class) - .addOptionsModification(options -> options.enableStringFormatOptimization = true) .enableInliningAnnotations() .addKeepMainRule(MAIN) .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java index 0914396..1225db2 100644 --- a/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java +++ b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
@@ -67,25 +67,21 @@ } } - /// DOC START: UsesReflection on virtual method - /* DOC TEXT START: - <p>If for example, your program is reflectively invoking a virtual method on some base class, you - should annotate the method that is performing the reflection with an annotation describing what - assumptions the reflective code is making. + /* INCLUDE DOC: UsesReflectionOnVirtualMethod + For example, if your program is reflectively invoking a method, you + should annotate the method that is doing the reflection. The annotation must describe the + assumptions the reflective code makes. - <p>In the following example, the method `foo` is looking up the method with the name + In the following example, the method `foo` is looking up the method with the name `hiddenMethod` on objects that are instances of `BaseClass`. It is then invoking the method with - no other arguments than the receiver. + no other arguments than the receiver. - <p>The minimal requirement for this code to work is therefore that all methods with the name - `hiddenMethod` and the empty list of parameters are targeted if they are objects that are - instances of the class `BaseClass` or subclasses thereof. + The assumptions the code makes are that all methods with the name + `hiddenMethod` and the empty list of parameters must remain valid for `getDeclaredMethod` if they + are objects that are instances of the class `BaseClass` or subclasses thereof. + INCLUDE END */ - <p>By placing the `UsesReflection` annotation on the method `foo` the annotation is only in - effect if the method `foo` is determined to be used by the shrinker. - So, if `foo` turns out to be dead code then the shrinker can remove `foo` and also ignore the - keep annotation. - DOC TEXT END */ + // INCLUDE CODE: UsesReflectionOnVirtualMethod static class MyClass { @UsesReflection({ @@ -99,7 +95,7 @@ } } - // DOC END + // INCLUDE END static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java index f193da6..e83aed8 100644 --- a/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java +++ b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java
@@ -13,10 +13,16 @@ public abstract class DocPrinterBase<T> { private String title = null; + private String returnDesc = null; + private String deprecatedDesc = null; private final List<String> additionalLines = new ArrayList<>(); public abstract T self(); + private boolean isEmptyOrJustTitle() { + return returnDesc == null && deprecatedDesc == null && additionalLines.isEmpty(); + } + public T clearDocLines() { additionalLines.clear(); return self(); @@ -29,6 +35,20 @@ return self(); } + public T setDocReturn(String desc) { + returnDesc = desc; + return self(); + } + + public boolean isDeprecated() { + return deprecatedDesc != null; + } + + public T setDeprecated(String desc) { + deprecatedDesc = desc; + return self(); + } + public T addParagraph(String... lines) { return addParagraph(Arrays.asList(lines)); } @@ -67,11 +87,11 @@ } public void printDoc(Consumer<String> println) { - assert additionalLines.isEmpty() || title != null; if (title == null) { + assert isEmptyOrJustTitle(); return; } - if (additionalLines.isEmpty()) { + if (isEmptyOrJustTitle()) { println.accept("/** " + title + " */"); return; } @@ -83,6 +103,12 @@ } println.accept(" * " + line); } + if (returnDesc != null) { + println.accept(" * @return " + returnDesc); + } + if (deprecatedDesc != null) { + println.accept(" * @deprecated " + deprecatedDesc); + } println.accept(" */"); } }
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java new file mode 100644 index 0000000..f4fd1a8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
@@ -0,0 +1,247 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.keepanno.utils; + +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.keepanno.annotations.KeepBinding; +import com.android.tools.r8.keepanno.annotations.KeepCondition; +import com.android.tools.r8.keepanno.annotations.KeepEdge; +import com.android.tools.r8.keepanno.annotations.KeepForApi; +import com.android.tools.r8.keepanno.annotations.KeepTarget; +import com.android.tools.r8.keepanno.annotations.UsedByNative; +import com.android.tools.r8.keepanno.annotations.UsedByReflection; +import com.android.tools.r8.keepanno.annotations.UsesReflection; +import com.android.tools.r8.keepanno.doctests.UsesReflectionDocumentationTest; +import com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.Generator; +import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class KeepAnnoMarkdownGenerator { + + public static void generateMarkdownDoc(Generator generator) { + try { + new KeepAnnoMarkdownGenerator(generator).internalGenerateMarkdownDoc(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String JAVADOC_URL = + "https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/"; + + private static final String INCLUDE_MD_START = "[[[INCLUDE"; + private static final String INCLUDE_MD_DOC_START = "[[[INCLUDE DOC"; + private static final String INCLUDE_MD_CODE_START = "[[[INCLUDE CODE"; + private static final String INCLUDE_MD_END = "]]]"; + + private static final String INCLUDE_DOC_START = "INCLUDE DOC:"; + private static final String INCLUDE_DOC_END = "INCLUDE END"; + private static final String INCLUDE_CODE_START = "INCLUDE CODE:"; + private static final String INCLUDE_CODE_END = "INCLUDE END"; + + private final Generator generator; + private final Map<String, String> typeLinkReplacements; + private Map<String, String> docReplacements = new HashMap<>(); + private Map<String, String> codeReplacements = new HashMap<>(); + + public KeepAnnoMarkdownGenerator(Generator generator) { + this.generator = generator; + typeLinkReplacements = + getTypeLinkReplacements( + KeepEdge.class, + KeepBinding.class, + KeepTarget.class, + KeepCondition.class, + UsesReflection.class, + UsedByReflection.class, + UsedByNative.class, + KeepForApi.class); + populateCodeAndDocReplacements(UsesReflectionDocumentationTest.class); + } + + private Map<String, String> getTypeLinkReplacements(Class<?>... classes) { + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + for (Class<?> clazz : classes) { + builder.put("`@" + clazz.getSimpleName() + "`", getMdLink(clazz)); + } + return builder.build(); + } + + private void populateCodeAndDocReplacements(Class<?>... classes) { + try { + for (Class<?> clazz : classes) { + Path sourceFile = ToolHelper.getSourceFileForTestClass(clazz); + String text = FileUtils.readTextFile(sourceFile, StandardCharsets.UTF_8); + extractMarkers(text, INCLUDE_DOC_START, INCLUDE_DOC_END, docReplacements); + extractMarkers(text, INCLUDE_CODE_START, INCLUDE_CODE_END, codeReplacements); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void extractMarkers( + String text, String markerKeyStart, String markerKeyEnd, Map<String, String> replacementMap) { + int index = text.indexOf(markerKeyStart); + while (index >= 0) { + int markerTitleEnd = text.indexOf('\n', index); + if (markerTitleEnd < 0) { + throw new RuntimeException("Failed to find end marker title"); + } + int end = text.indexOf(markerKeyEnd, index); + if (end < 0) { + throw new RuntimeException("Failed to find end marker"); + } + int endBeforeNewLine = text.lastIndexOf('\n', end); + if (endBeforeNewLine < markerTitleEnd) { + throw new RuntimeException("No new-line before end marker"); + } + String markerTitle = text.substring(index + markerKeyStart.length(), markerTitleEnd); + String includeContent = text.substring(markerTitleEnd + 1, endBeforeNewLine); + String old = replacementMap.put(markerTitle.trim(), includeContent); + if (old != null) { + throw new RuntimeException("Duplicate definition of marker"); + } + index = text.indexOf(markerKeyStart, end); + } + } + + private String getMdLink(Class<?> clazz) { + String url = JAVADOC_URL + clazz.getTypeName().replace('.', '/') + ".html"; + return "[@" + clazz.getSimpleName() + "](" + url + ")"; + } + + private void println() { + generator.println(""); + } + + private void println(String line) { + generator.println(line); + } + + private void internalGenerateMarkdownDoc() throws IOException { + Path template = Paths.get("doc/keepanno-guide.template.md"); + println("[comment]: <> (DO NOT EDIT - GENERATED FILE)"); + println("[comment]: <> (Changes should be made in " + template + ")"); + println(); + List<String> readAllLines = FileUtils.readAllLines(template); + for (int i = 0; i < readAllLines.size(); i++) { + String line = readAllLines.get(i); + try { + processLine(line, generator); + } catch (Exception e) { + System.err.println("Parse error on line " + (i + 1) + ":"); + System.err.println(line); + System.err.println(e.getMessage()); + } + } + } + + private String replaceCodeAndDocMarkers(String line) { + String originalLine = line; + line = line.trim(); + if (!line.startsWith(INCLUDE_MD_START)) { + return originalLine; + } + int keyStartIndex = line.indexOf(':'); + if (!line.endsWith(INCLUDE_MD_END) || keyStartIndex < 0) { + throw new RuntimeException("Invalid include directive"); + } + String key = line.substring(keyStartIndex + 1, line.length() - INCLUDE_MD_END.length()); + if (line.startsWith(INCLUDE_MD_DOC_START)) { + return replaceDoc(key); + } + if (line.startsWith(INCLUDE_MD_CODE_START)) { + return replaceCode(key); + } + throw new RuntimeException("Unknown replacement marker"); + } + + private String replaceDoc(String key) { + String replacement = docReplacements.get(key); + if (replacement == null) { + throw new RuntimeException("No replacement defined for " + key); + } + return unindentLines(replacement, new StringBuilder()).toString(); + } + + private String replaceCode(String key) { + String replacement = codeReplacements.get(key); + if (replacement == null) { + throw new RuntimeException("No replacement defined for " + key); + } + StringBuilder builder = new StringBuilder(); + builder.append("```\n"); + unindentLines(replacement, builder); + builder.append("```\n"); + return builder.toString(); + } + + private StringBuilder unindentLines(String replacement, StringBuilder builder) { + int shortestSpacePrefix = Integer.MAX_VALUE; + List<String> lines = StringUtils.split(replacement, '\n'); + for (String line : lines) { + if (!line.isEmpty()) { + shortestSpacePrefix = Math.min(shortestSpacePrefix, findFirstNonSpaceIndex(line)); + } + } + if (shortestSpacePrefix > 0) { + for (String line : lines) { + if (!line.isEmpty()) { + builder.append(line.substring(shortestSpacePrefix)); + } + builder.append('\n'); + } + } else { + builder.append(replacement); + builder.append('\n'); + } + return builder; + } + + private int findFirstNonSpaceIndex(String line) { + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != ' ') { + return i; + } + } + return line.length(); + } + + private String tryLinkReplacements(String line) { + int index = line.indexOf("`@"); + if (index < 0) { + return null; + } + int end = line.indexOf('`', index + 1); + if (end < 0) { + throw new RuntimeException("No end marker on line: " + line); + } + String typeLink = line.substring(index, end + 1); + String replacement = typeLinkReplacements.get(typeLink); + if (replacement == null) { + throw new RuntimeException("Unknown type link: " + typeLink); + } + return line.replace(typeLink, replacement); + } + + private void processLine(String line, Generator generator) { + line = replaceCodeAndDocMarkers(line); + String replacement = tryLinkReplacements(line); + while (replacement != null) { + line = replacement; + replacement = tryLinkReplacements(line); + } + generator.println(line); + } +}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java index 67a5fe0..005c344 100644 --- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java +++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -72,6 +72,9 @@ void generate(Generator generator) { printDoc(generator::println); + if (isDeprecated()) { + generator.println("@Deprecated"); + } if (valueDefault == null) { generator.println(valueType + " " + name + "();"); } else { @@ -203,7 +206,7 @@ } } - private static class Generator { + public static class Generator { private static final List<Class<?>> ANNOTATION_IMPORTS = ImmutableList.of(ElementType.class, Retention.class, RetentionPolicy.class, Target.class); @@ -222,13 +225,14 @@ } private void println() { - // Don't indent empty lines. - writer.println(); + println(""); } - private void println(String line) { - assert line.length() > 0; - writer.print(Strings.repeat(" ", indent)); + public void println(String line) { + // Don't indent empty lines. + if (line.length() > 0) { + writer.print(Strings.repeat(" ", indent)); + } writer.println(line); } @@ -265,6 +269,7 @@ .addMember( new GroupMember("description") .setDocTitle("Optional description to document the reason for this annotation.") + .setDocReturn("The descriptive message. Defaults to no description.") .defaultEmptyString()); } @@ -279,8 +284,9 @@ new GroupMember("preconditions") .setDocTitle( "Conditions that should be satisfied for the annotation to be in effect.") - .addParagraph( - "Defaults to no conditions, thus trivially/unconditionally satisfied.") + .setDocReturn( + "The list of preconditions. " + + "Defaults to no conditions, thus trivially/unconditionally satisfied.") .defaultEmptyArray(KeepCondition.class)); } @@ -289,6 +295,7 @@ .addMember( new GroupMember("consequences") .setDocTitle("Consequences that must be kept if the annotation is in effect.") + .setDocReturn("The list of target consequences.") .requiredValueOfArrayType(KeepTarget.class)); } @@ -297,6 +304,7 @@ .addMember( new GroupMember("value") .setDocTitle("Consequences that must be kept if the annotation is in effect.") + .setDocReturn("The list of target consequences.") .requiredValueOfArrayType(KeepTarget.class)); } @@ -305,7 +313,9 @@ .addMember( new GroupMember("additionalPreconditions") .setDocTitle("Additional preconditions for the annotation to be in effect.") - .addParagraph("Defaults to no additional preconditions.") + .setDocReturn( + "The list of additional preconditions. " + + "Defaults to no additional preconditions.") .defaultEmptyArray("KeepCondition")); } @@ -314,7 +324,9 @@ .addMember( new GroupMember("additionalTargets") .setDocTitle(docTitle) - .addParagraph("Defaults to no additional targets.") + .setDocReturn( + "List of additional target consequences. " + + "Defaults to no additional target consequences.") .defaultEmptyArray("KeepTarget")); } @@ -327,6 +339,7 @@ .defaultType("KeepItemKind") .defaultValue("KeepItemKind.DEFAULT") .setDocTitle("Specify the kind of this item pattern.") + .setDocReturn("The kind for this pattern.") .addParagraph("Possible values are:") .addUnorderedList( KeepItemKind.ONLY_CLASS.name(), @@ -343,15 +356,16 @@ return new Group(OPTIONS_GROUP) .addMember( new GroupMember("allow") - .setDocTitle( - "Define the " - + OPTIONS_GROUP - + " that do not need to be preserved for the target.") + .setDocTitle("Define the " + OPTIONS_GROUP + " that are allowed to be modified.") + .addParagraph("The specified options do not need to be preserved for the target.") + .setDocReturn("Options allowed to be modified for the target.") .defaultEmptyArray("KeepOption")) .addMember( new GroupMember("disallow") .setDocTitle( - "Define the " + OPTIONS_GROUP + " that *must* be preserved for the target.") + "Define the " + OPTIONS_GROUP + " that are not allowed to be modified.") + .addParagraph("The specified options *must* be preserved for the target.") + .setDocReturn("Options not allowed to be modified for the target.") .defaultEmptyArray("KeepOption")) .addDocFooterParagraph( "If nothing is specified for " @@ -368,12 +382,14 @@ .setDocTitle( "Name with which other bindings, conditions or targets " + "can reference the bound item pattern.") + .setDocReturn("Name of the binding.") .requiredStringValue(); } private GroupMember classFromBinding() { return new GroupMember("classFromBinding") .setDocTitle("Define the " + CLASS_GROUP + " pattern by reference to a binding.") + .setDocReturn("The name of the binding that defines the class.") .defaultEmptyString(); } @@ -386,6 +402,7 @@ private GroupMember className() { return new GroupMember("className") .setDocTitle("Define the " + CLASS_NAME_GROUP + " pattern by fully qualified class name.") + .setDocReturn("The qualified class name that defines the class.") .defaultEmptyString(); } @@ -393,6 +410,7 @@ return new GroupMember("classConstant") .setDocTitle( "Define the " + CLASS_NAME_GROUP + " pattern by reference to a Class constant.") + .setDocReturn("The class-constant that defines the class.") .defaultObjectClass(); } @@ -409,6 +427,7 @@ "Define the " + INSTANCE_OF_GROUP + " pattern as classes that are instances of the fully qualified class name.") + .setDocReturn("The qualified class name that defines what instance-of the class must be.") .defaultEmptyString(); } @@ -418,6 +437,7 @@ "Define the " + INSTANCE_OF_GROUP + " pattern as classes that are instances the referenced Class constant.") + .setDocReturn("The class constant that defines what instance-of the class must be.") .defaultObjectClass(); } @@ -433,6 +453,7 @@ "Define the " + INSTANCE_OF_GROUP + " pattern as classes that are instances of the fully qualified class name.") + .setDocReturn("The qualified class name that defines what instance-of the class must be.") .addParagraph(getInstanceOfExclusiveDoc()) .defaultEmptyString(); } @@ -444,6 +465,7 @@ + INSTANCE_OF_GROUP + " pattern as classes that are instances the referenced Class constant.") .addParagraph(getInstanceOfExclusiveDoc()) + .setDocReturn("The class constant that defines what instance-of the class must be.") .defaultObjectClass(); } @@ -454,7 +476,9 @@ + INSTANCE_OF_GROUP + " pattern as classes extending the fully qualified class name.") .addParagraph(getInstanceOfExclusiveDoc()) - .addParagraph("This property is deprecated, use instanceOfClassName instead.") + .setDeprecated( + "This property is deprecated, use " + docLink(instanceOfClassName()) + " instead.") + .setDocReturn("The class name that defines what the class must extend.") .defaultEmptyString(); } @@ -465,7 +489,11 @@ + INSTANCE_OF_GROUP + " pattern as classes extending the referenced Class constant.") .addParagraph(getInstanceOfExclusiveDoc()) - .addParagraph("This property is deprecated, use instanceOfClassConstant instead.") + .setDeprecated( + "This property is deprecated, use " + + docLink(instanceOfClassConstant()) + + " instead.") + .setDocReturn("The class constant that defines what the class must extend.") .defaultObjectClass(); } @@ -490,6 +518,7 @@ "Mutually exclusive with all other class and member pattern properties.", "When a member binding is referenced this item is defined to be that item,", "including its class and member patterns.") + .setDocReturn("The binding name that defines the member.") .defaultEmptyString()); } @@ -501,6 +530,7 @@ .addParagraph( "Mutually exclusive with all field and method properties", "as use restricts the match to both types of members.") + .setDocReturn("The member access-flag constraints that must be met.") .defaultEmptyArray("MemberAccessFlags")); } @@ -531,6 +561,7 @@ .setDocTitle("Define the method-access pattern by matching on access flags.") .addParagraph(getMutuallyExclusiveForMethodProperties()) .addParagraph(getMethodDefaultDoc("any method-access flags")) + .setDocReturn("The method access-flag constraints that must be met.") .defaultEmptyArray("MethodAccessFlags")); } @@ -541,6 +572,7 @@ .setDocTitle("Define the method-name pattern by an exact method name.") .addParagraph(getMutuallyExclusiveForMethodProperties()) .addParagraph(getMethodDefaultDoc("any method name")) + .setDocReturn("The exact method name of the method.") .defaultEmptyString()); } @@ -552,6 +584,7 @@ "Define the method return-type pattern by a fully qualified type or 'void'.") .addParagraph(getMutuallyExclusiveForMethodProperties()) .addParagraph(getMethodDefaultDoc("any return type")) + .setDocReturn("The qualified type name of the method return type.") .defaultEmptyString()); } @@ -563,6 +596,7 @@ "Define the method parameters pattern by a list of fully qualified types.") .addParagraph(getMutuallyExclusiveForMethodProperties()) .addParagraph(getMethodDefaultDoc("any parameters")) + .setDocReturn("The list of qualified type names of the method parameters.") .defaultType("String[]") .defaultValue("{\"<default>\"}")); } @@ -574,6 +608,7 @@ .setDocTitle("Define the field-access pattern by matching on access flags.") .addParagraph(getMutuallyExclusiveForFieldProperties()) .addParagraph(getFieldDefaultDoc("any field-access flags")) + .setDocReturn("The field access-flag constraints that must be met.") .defaultEmptyArray("FieldAccessFlags")); } @@ -584,6 +619,7 @@ .setDocTitle("Define the field-name pattern by an exact field name.") .addParagraph(getMutuallyExclusiveForFieldProperties()) .addParagraph(getFieldDefaultDoc("any field name")) + .setDocReturn("The exact field name of the field.") .defaultEmptyString()); } @@ -594,6 +630,7 @@ .setDocTitle("Define the field-type pattern by a fully qualified type.") .addParagraph(getMutuallyExclusiveForFieldProperties()) .addParagraph(getFieldDefaultDoc("any type")) + .setDocReturn("The qualified type name of the field type.") .defaultEmptyString()); } @@ -674,12 +711,7 @@ println(); withIndent( () -> { - new GroupMember("bindingName") - .setDocTitle( - "Name with which other bindings, conditions or targets can reference the bound" - + " item pattern.") - .requiredValueOfType("String") - .generate(this); + bindingName().generate(this); println(); getKindGroup().generate(this); println(); @@ -939,6 +971,10 @@ return "{@link " + simpleName(clazz) + "}"; } + private String docLink(GroupMember member) { + return "{@link #" + member.name + "}"; + } + private String docLink(KeepItemKind kind) { return "{@link KeepItemKind#" + kind.name() + "}"; } @@ -1236,11 +1272,16 @@ PrintStream printStream = new PrintStream(byteStream); Generator generator = new Generator(printStream); fn.accept(generator); - String formatted = CodeGenerationBase.formatRawOutput(byteStream.toString()); + String formatted = byteStream.toString(); + if (file.toString().endsWith(".java")) { + formatted = CodeGenerationBase.formatRawOutput(formatted); + } Files.write(Paths.get(ToolHelper.getProjectRoot()).resolve(file), formatted.getBytes()); } public static void run() throws IOException { + writeFile(Paths.get("doc/keepanno-guide.md"), KeepAnnoMarkdownGenerator::generateMarkdownDoc); + Path keepAnnoRoot = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno"); Path astPkg = keepAnnoRoot.resolve("ast"); @@ -1260,4 +1301,5 @@ g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI")); } } + }
diff --git a/tools/archive.py b/tools/archive.py index fd250dd..a906a82 100755 --- a/tools/archive.py +++ b/tools/archive.py
@@ -122,6 +122,21 @@ print('INFO: Open files hard limit: %s' % hard) +def UploadDir(src_dir, version_or_path, dst_dir, is_main, options): + destination = GetUploadDestination(version_or_path, dst_dir, is_main) + print(f'Uploading {src_dir} to {destination}') + if options.dry_run: + if options.dry_run_output: + dry_run_destination = os.path.join(options.dry_run_output, version_or_path, dst_dir) + print(f'Dry run, not actually uploading. Copying to {dry_run_destination}') + shutil.copytree(src_dir, dry_run_destination) + else: + print('Dry run, not actually uploading') + else: + utils.upload_directory_to_cloud_storage(src_dir, destination) + print(f'Directory available at: {GetUrl(version_or_path, dst_dir, is_main)}') + + def Main(): (options, args) = ParseOptions() Run(options) @@ -171,7 +186,9 @@ if (not options.skip_gradle_build): gradle.RunGradle([ utils.GRADLE_TASK_CONSOLIDATED_LICENSE, - utils.GRADLE_TASK_KEEP_ANNO_JAR, utils.GRADLE_TASK_R8, + utils.GRADLE_TASK_KEEP_ANNO_JAR, + utils.GRADLE_TASK_KEEP_ANNO_DOC, + utils.GRADLE_TASK_R8, utils.GRADLE_TASK_R8LIB, utils.GRADLE_TASK_R8LIB_NO_DEPS, utils.GRADLE_TASK_THREADING_MODULE_BLOCKING, utils.GRADLE_TASK_THREADING_MODULE_SINGLE_THREADED, @@ -229,6 +246,20 @@ '-PspdxRevision=' + GetGitHash() ]) + # Upload keep-anno javadoc to a fixed "docs" location. + if is_main: + version_or_path = 'docs' + dst_dir = 'keepanno/javadoc' + UploadDir(utils.KEEPANNO_ANNOTATIONS_DOC, version_or_path, dst_dir, is_main, options) + + # Upload directories. + dirs_for_archiving = [ + (utils.KEEPANNO_ANNOTATIONS_DOC, 'keepanno/javadoc'), + ] + for (src_dir, dst_dir) in dirs_for_archiving: + UploadDir(src_dir, version, dst_dir, is_main, options) + + # Upload files. for_archiving = [ utils.R8_JAR, utils.R8LIB_JAR, utils.R8LIB_JAR + '.map', utils.R8LIB_JAR + '_map.zip', utils.R8_FULL_EXCLUDE_DEPS_JAR, @@ -258,8 +289,8 @@ print('Uploading %s to %s' % (tagged_jar, destination)) if options.dry_run: if options.dry_run_output: - dry_run_destination = os.path.join(options.dry_run_output, - file_name) + dry_run_destination = os.path.join( + options.dry_run_output, version, file_name) print('Dry run, not actually uploading. Copying to ' + dry_run_destination) shutil.copyfile(tagged_jar, dry_run_destination)
diff --git a/tools/compiledump.py b/tools/compiledump.py index 2d1a3a8..ce09db8 100755 --- a/tools/compiledump.py +++ b/tools/compiledump.py
@@ -11,6 +11,7 @@ import zipfile import archive +import gradle import jdk import retrace import utils @@ -139,6 +140,18 @@ help='Run compilation in specified mode', choices=['debug', 'release'], default=None) + parser.add_argument( + '--ignore-features', + help="Don't split into features when features are present." + ' Instead include feature code in main app output.' + ' This is always the case when compiler is d8.', + default=False, + action='store_true') + parser.add_argument( + '--no-build', + help="Don't build when using --version main", + default=False, + action='store_true') return parser @@ -348,7 +361,9 @@ def determine_feature_output(feature_jar, temp): - return os.path.join(temp, os.path.basename(feature_jar)[:-4] + ".out.jar") + return os.path.join( + args.output if args.output and os.path.isdir(args.output) else temp, + os.path.basename(feature_jar)[:-4] + ".out.jar") def determine_program_jar(args, dump): @@ -401,6 +416,10 @@ def download_distribution(version, args, temp): nolib = args.nolib if version == 'main': + if not args.no_build: + gradle.RunGradle( + [utils.GRADLE_TASK_R8] if nolib else [utils.GRADLE_TASK_R8LIB] + ) return utils.R8_JAR if nolib else utils.R8LIB_JAR if version == 'source': return '%s:%s' % (utils.BUILD_JAVA_MAIN_DIR, utils.ALL_DEPS_JAR) @@ -574,10 +593,13 @@ else: cmd.extend(['--source', program_jar]) for feature_jar in dump.feature_jars(): - cmd.extend([ - '--feature-jar', feature_jar, - determine_feature_output(feature_jar, temp) - ]) + if not args.ignore_features and compiler != 'd8': + cmd.extend([ + '--feature-jar', feature_jar, + determine_feature_output(feature_jar, temp) + ]) + else: + cmd.append(feature_jar) if dump.library_jar(): cmd.extend(['--lib', dump.library_jar()]) if dump.classpath_jar() and not is_l8_compiler(compiler):
diff --git a/tools/r8_release.py b/tools/r8_release.py index 116a52c..ce83d32 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py
@@ -121,9 +121,7 @@ validate_version_change_diff(version_diff_output, "main", version) - cmd = ['git', 'cl', 'upload', '--no-squash'] - if args.bypass_hooks: - cmd.append('--bypass-hooks') + cmd = ['git', 'cl', 'upload', '--no-squash', '--bypass-hooks'] maybe_check_call(args, cmd) if args.dry_run:
diff --git a/tools/utils.py b/tools/utils.py index 597f8b1..e859748 100644 --- a/tools/utils.py +++ b/tools/utils.py
@@ -43,6 +43,7 @@ GRADLE_TASK_CLEAN_TEST = ':test:cleanTest' GRADLE_TASK_CONSOLIDATED_LICENSE = ':main:consolidatedLicense' GRADLE_TASK_KEEP_ANNO_JAR = ':keepanno:keepAnnoAnnotationsJar' +GRADLE_TASK_KEEP_ANNO_DOC = ':keepanno:keepAnnoAnnotationsDoc' GRADLE_TASK_R8 = ':main:r8WithRelocatedDeps' GRADLE_TASK_R8LIB = ':test:assembleR8LibWithRelocatedDeps' GRADLE_TASK_R8LIB_NO_DEPS = ':test:assembleR8LibNoDeps' @@ -77,6 +78,7 @@ LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join( CUSTOM_CONVERSION_DIR, 'library_desugar_conversions.jar') KEEPANNO_ANNOTATIONS_JAR = os.path.join(LIBS, 'keepanno-annotations.jar') +KEEPANNO_ANNOTATIONS_DOC = os.path.join('d8_r8', 'keepanno', 'build', 'docs', 'javadoc') DESUGAR_CONFIGURATION = os.path.join('src', 'library_desugar', 'desugar_jdk_libs.json') @@ -391,6 +393,14 @@ PrintCmd(cmd) subprocess.check_call(cmd) +def upload_directory_to_cloud_storage(source, destination, parallel=True): + cmd = [get_gsutil()] + if parallel: + cmd += ['-m'] + cmd += ['cp', '-R'] + cmd += [source, destination] + PrintCmd(cmd) + subprocess.check_call(cmd) def delete_file_from_cloud_storage(destination): cmd = [get_gsutil(), 'rm', destination]