Generate jar and move running of tests to main
Bug: 190368382
Bug: 188388130
Bug: 138781768
Change-Id: Ib0ccda93bef8affff86af02aa8fd94fa8298a1a3
diff --git a/.gitignore b/.gitignore
index c9c43534..24eead1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,8 @@
third_party/android_jar/lib.tar.gz
third_party/android_jar/api-versions.tar.gz
third_party/android_jar/api-versions
+third_party/android_jar/api-database.tar.gz
+third_party/android_jar/api-database
third_party/android_sdk
third_party/android_sdk.tar.gz
third_party/bazel
diff --git a/build.gradle b/build.gradle
index 9032bc4..273e192 100644
--- a/build.gradle
+++ b/build.gradle
@@ -316,6 +316,7 @@
"android_jar/lib-v30",
"android_jar/lib-v31",
"android_jar/api-versions",
+ "android_jar/api-database",
"api-outlining/simple-app-dump",
"core-lambda-stubs",
"dart-sdk",
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
index 4a9c41a..f33f5bd 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.androidapi;
-import com.android.tools.r8.KeepForSubclassing;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
@@ -17,7 +16,6 @@
import java.util.function.BiFunction;
/** This is a base class for all generated classes from api-versions.xml. */
-@KeepForSubclassing
public abstract class AndroidApiClass {
private final ClassReference classReference;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index a85a657..1e4082b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -3,14 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import com.android.tools.r8.Keep;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.structural.Ordered;
import java.util.Arrays;
import java.util.List;
/** Android API level description */
-@Keep
public enum AndroidApiLevel implements Ordered<AndroidApiLevel> {
B(1),
B_1_1(2),
@@ -45,6 +43,8 @@
S(31),
UNKNOWN(10000);
+ // When updating LATEST and a new version goes stable, add a new api-versions.xml to third_party
+ // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest.
public static final AndroidApiLevel LATEST = S;
public static final int magicApiLevelUsedByAndroidPlatformBuild = 10000;
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index e3b1753..df59f20 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -3,11 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import com.android.tools.r8.Keep;
-
/** Two value continuation value to indicate the continuation of a loop/traversal. */
/* This class is used for building up api class member traversals. */
-@Keep
public enum TraversalContinuation {
CONTINUE,
BREAK;
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
index 5e75e09..bc25cd4 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -21,22 +21,19 @@
import static org.objectweb.asm.Opcodes.POP;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.androidapi.AndroidApiClass;
import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
import com.android.tools.r8.transformers.MethodTransformer;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.TraversalContinuation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.objectweb.asm.Label;
@@ -48,6 +45,13 @@
return descriptor(AndroidApiDatabaseBuilderTemplate.class).replace("Template", "");
}
+ public static ClassReference ANDROID_API_LEVEL =
+ Reference.classFromBinaryName("com/android/tools/r8/utils/AndroidApiLevel");
+ public static ClassReference ANDROID_API_CLASS =
+ Reference.classFromBinaryName("com/android/tools/r8/androidapi/AndroidApiClass");
+ public static ClassReference TRAVERSAL_CONTINUATION =
+ Reference.classFromBinaryName("com/android/tools/r8/utils/TraversalContinuation");
+
/**
* Generate the classes needed for looking up api level of references in the android.jar.
*
@@ -92,9 +96,10 @@
.addMethodTransformer(getApiLevelTransformer(apiClass))
.addMethodTransformer(getVisitFieldsTransformer(apiClass))
.addMethodTransformer(getVisitMethodsTransformer(apiClass))
- .removeMethods(MethodPredicate.onName("placeHolder"))
- .removeMethods(MethodPredicate.onName("placeHolderForGetApiLevel"))
.removeMethods(MethodPredicate.onName("placeHolderForInit"))
+ .removeMethods(MethodPredicate.onName("placeHolderForGetApiLevel"))
+ .removeMethods(MethodPredicate.onName("placeHolderForVisitFields"))
+ .removeMethods(MethodPredicate.onName("placeHolderForVisitMethods"))
.transform());
}
@@ -114,7 +119,8 @@
.setClassDescriptor(generatedMainDescriptor())
.addMethodTransformer(getVisitApiClassesTransformer(apiClasses))
.addMethodTransformer(getBuildPackageTransformer(packages))
- .removeMethods(MethodPredicate.onName("placeHolder"))
+ .removeMethods(MethodPredicate.onName("placeHolderForVisitApiClasses"))
+ .removeMethods(MethodPredicate.onName("placeHolderForBuildClass"))
.transform());
}
@@ -150,18 +156,16 @@
// return placeHolderForGetApiLevel();
// into
// return AndroidApiLevel.getAndroidApiLevel(<apiLevel>);
- private static MethodTransformer getApiLevelTransformer(ParsedApiClass apiClass)
- throws NoSuchMethodException {
- Method getAndroidApiLevel = AndroidApiLevel.class.getMethod("getAndroidApiLevel", int.class);
+ private static MethodTransformer getApiLevelTransformer(ParsedApiClass apiClass) {
return replaceCode(
"placeHolderForGetApiLevel",
transformer -> {
transformer.visitLdcInsn(apiClass.getApiLevel().getLevel());
transformer.visitMethodInsn(
INVOKESTATIC,
- binaryName(AndroidApiLevel.class),
- getAndroidApiLevel.getName(),
- methodDescriptor(getAndroidApiLevel),
+ ANDROID_API_LEVEL.getBinaryName(),
+ "getAndroidApiLevel",
+ "(I)" + ANDROID_API_LEVEL.getDescriptor(),
false);
});
}
@@ -180,9 +184,7 @@
// }
// ...
// return TraversalContinuation.CONTINUE;
- private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass)
- throws NoSuchMethodException {
- Method shouldBreak = TraversalContinuation.class.getMethod("shouldBreak");
+ private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass) {
return replaceCode(
"placeHolderForVisitFields",
transformer -> {
@@ -197,22 +199,21 @@
transformer.visitVarInsn(ALOAD, 1);
transformer.visitMethodInsn(
INVOKEVIRTUAL,
- binaryName(AndroidApiClass.class),
+ ANDROID_API_CLASS.getBinaryName(),
"visitField",
- methodDescriptor(
- TraversalContinuation.class,
- String.class,
- String.class,
- int.class,
- BiFunction.class),
+ "(Ljava/lang/String;"
+ + "Ljava/lang/String;"
+ + "I"
+ + "Ljava/util/function/BiFunction;)"
+ + TRAVERSAL_CONTINUATION.getDescriptor(),
false);
// Note that instead of storing the result here, we dup it on the stack.
transformer.visitInsn(DUP);
transformer.visitMethodInsn(
INVOKEVIRTUAL,
- binaryName(TraversalContinuation.class),
- shouldBreak.getName(),
- methodDescriptor(shouldBreak),
+ TRAVERSAL_CONTINUATION.getBinaryName(),
+ "shouldBreak",
+ "()Z",
false);
Label label = new Label();
transformer.visitJumpInsn(IFEQ, label);
@@ -223,7 +224,7 @@
0,
new Object[] {},
1,
- new Object[] {binaryName(TraversalContinuation.class)});
+ new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
// The pop here is needed to remove the dupped value in the case we do not
// return.
transformer.visitInsn(POP);
@@ -248,9 +249,7 @@
// }
// ...
// return TraversalContinuation.CONTINUE;
- private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass)
- throws NoSuchMethodException {
- Method shouldBreak = TraversalContinuation.class.getMethod("shouldBreak");
+ private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass) {
return replaceCode(
"placeHolderForVisitMethods",
transformer -> {
@@ -278,23 +277,21 @@
transformer.visitVarInsn(ALOAD, 1);
transformer.visitMethodInsn(
INVOKEVIRTUAL,
- binaryName(AndroidApiClass.class),
+ ANDROID_API_CLASS.getBinaryName(),
"visitMethod",
- methodDescriptor(
- TraversalContinuation.class,
- String.class,
- String[].class,
- String.class,
- int.class,
- BiFunction.class),
+ "(Ljava/lang/String;"
+ + "[Ljava/lang/String;Ljava/lang/String;"
+ + "I"
+ + "Ljava/util/function/BiFunction;)"
+ + TRAVERSAL_CONTINUATION.getDescriptor(),
false);
// Note that instead of storing the result here, we dup it on the stack.
transformer.visitInsn(DUP);
transformer.visitMethodInsn(
INVOKEVIRTUAL,
- binaryName(TraversalContinuation.class),
- shouldBreak.getName(),
- methodDescriptor(shouldBreak),
+ TRAVERSAL_CONTINUATION.getBinaryName(),
+ "shouldBreak",
+ "()Z",
false);
Label label = new Label();
transformer.visitJumpInsn(IFEQ, label);
@@ -305,7 +302,7 @@
0,
new Object[] {},
1,
- new Object[] {binaryName(TraversalContinuation.class)});
+ new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
// The pop here is needed to remove the dupped value in the case we do not
// return.
transformer.visitInsn(POP);
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
index c47353e..c69eff3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -5,15 +5,20 @@
package com.android.tools.r8.apimodel;
import static com.android.tools.r8.apimodel.AndroidApiDatabaseBuilderGenerator.generatedMainDescriptor;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JvmTestBuilder;
import com.android.tools.r8.JvmTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestState;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanBox;
@@ -23,12 +28,16 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.BiConsumer;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
@@ -41,11 +50,13 @@
protected final TestParameters parameters;
private static final Path API_VERSIONS_XML =
Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
+ private static final Path API_DATABASE_JAR =
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-database", "api-database.jar");
private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withSystemRuntime().build();
+ return getTestParameters().withNoneRuntime().build();
}
public AndroidApiDatabaseBuilderGeneratorTest(TestParameters parameters) {
@@ -74,15 +85,6 @@
return builder.build();
}
- public static void main(String[] args) throws Exception {
- generateJar();
- }
-
- @Test
- public void testDatabaseGenerationUpToDate() {
- assumeTrue("b/190368382", false);
- }
-
@Test
public void testCanParseApiVersionsXml() throws Exception {
// This tests makes a rudimentary check on the number of classes, fields and methods in
@@ -108,64 +110,115 @@
assertEquals(38661, numberOfMethods.get());
}
- @Test()
- public void testGeneratedOutputForVisitClasses() throws Exception {
- runTest(
- TestGeneratedMainVisitClasses.class,
- (parsedApiClasses, runResult) -> {
- String expectedOutput =
- StringUtils.lines(
- ListUtils.map(
- parsedApiClasses, apiClass -> apiClass.getClassReference().getDescriptor()));
- runResult.assertSuccessWithOutput(expectedOutput);
- });
+ @Test
+ public void testDatabaseGenerationUpToDate() throws Exception {
+ BootstrapCurrentEqualityTest.filesAreEqual(generateJar(), API_DATABASE_JAR);
}
- @Test()
- public void testBuildClassesContinue() throws Exception {
- runTest(
- TestBuildClassesContinue.class,
- (parsedApiClasses, runResult) -> {
- runResult.assertSuccessWithOutputLines(getExpected(parsedApiClasses, false));
- });
- }
-
- @Test()
- public void testBuildClassesBreak() throws Exception {
- runTest(
- TestBuildClassesBreak.class,
- (parsedApiClasses, runResult) -> {
- runResult.assertSuccessWithOutputLines(getExpected(parsedApiClasses, true));
- });
- }
-
- private void runTest(
- Class<?> testClass, BiConsumer<List<ParsedApiClass>, JvmTestRunResult> resultConsumer)
- throws Exception {
+ /**
+ * Main entry point for building a database over references in framework to the api level they
+ * were introduced. Running main will generate a new jar and run tests on it to ensure it is
+ * compatible with R8 sources and works as expected.
+ *
+ * <p>If the generated jar passes tests it will be moved to third_party/android_jar/api-database/
+ * and override the current file in there.
+ */
+ public static void main(String[] args) throws Exception {
List<ParsedApiClass> parsedApiClasses =
AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
- testForJvm()
- .addProgramClassFileData(
- transformer(testClass)
- .replaceClassDescriptorInMethodInstructions(
- descriptor(AndroidApiDatabaseBuilderTemplate.class), generatedMainDescriptor())
- .transform())
- .addLibraryFiles(generateJar(parsedApiClasses))
- // TODO(b/190368382): This will change when databasebuilder is included in deps.
- .addLibraryFiles(ToolHelper.R8_WITHOUT_DEPS_JAR, ToolHelper.DEPS)
- .addDefaultRuntimeLibrary(parameters)
- .run(parameters.getRuntime(), testClass)
- .apply(
- result -> {
- if (result.getExitCode() != 0) {
- System.out.println(result.getStdErr());
- }
- })
- .assertSuccess()
- .apply(result -> resultConsumer.accept(parsedApiClasses, result));
+ Path generatedJar = generateJar(parsedApiClasses);
+ validateJar(generatedJar, parsedApiClasses);
+ Files.move(generatedJar, API_DATABASE_JAR, REPLACE_EXISTING);
}
- private List<String> getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
+ private static void validateJar(Path generated, List<ParsedApiClass> apiClasses) {
+ List<BiFunction<Path, List<ParsedApiClass>, Boolean>> tests =
+ ImmutableList.of(
+ AndroidApiDatabaseBuilderGeneratorTest::testGeneratedOutputForVisitClasses,
+ AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesContinue,
+ AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesBreak,
+ AndroidApiDatabaseBuilderGeneratorTest::testNoPlaceHolder);
+ tests.forEach(
+ test -> {
+ if (!test.apply(generated, apiClasses)) {
+ throw new RuntimeException("Generated jar did not pass tests");
+ }
+ });
+ }
+
+ private static boolean testGeneratedOutputForVisitClasses(
+ Path generated, List<ParsedApiClass> parsedApiClasses) {
+ String expectedOutput =
+ StringUtils.lines(
+ ListUtils.map(
+ parsedApiClasses, apiClass -> apiClass.getClassReference().getDescriptor()));
+ return runTest(generated, TestGeneratedMainVisitClasses.class)
+ .getStdOut()
+ .equals(expectedOutput);
+ }
+
+ private static boolean testBuildClassesContinue(
+ Path generated, List<ParsedApiClass> parsedApiClasses) {
+ return runTest(generated, TestBuildClassesContinue.class)
+ .getStdOut()
+ .equals(getExpected(parsedApiClasses, false));
+ }
+
+ private static boolean testBuildClassesBreak(
+ Path generated, List<ParsedApiClass> parsedApiClasses) {
+ return runTest(generated, TestBuildClassesBreak.class)
+ .getStdOut()
+ .equals(getExpected(parsedApiClasses, true));
+ }
+
+ private static boolean testNoPlaceHolder(Path generated, List<ParsedApiClass> parsedApiClasses) {
+ try {
+ CodeInspector inspector = new CodeInspector(generated);
+ inspector
+ .allClasses()
+ .forEach(
+ clazz -> {
+ clazz.forAllMethods(
+ methods -> {
+ if (methods.getFinalName().startsWith("placeHolder")) {
+ throw new RuntimeException("Found placeHolder method in generated jar");
+ }
+ });
+ });
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ return true;
+ }
+
+ private static JvmTestRunResult runTest(Path generated, Class<?> testClass) {
+ try {
+ TemporaryFolder temporaryFolder = new TemporaryFolder();
+ temporaryFolder.create();
+ return JvmTestBuilder.create(new TestState(temporaryFolder))
+ .addProgramClassFileData(
+ transformer(testClass)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(AndroidApiDatabaseBuilderTemplate.class),
+ generatedMainDescriptor())
+ .transform())
+ .addLibraryFiles(generated)
+ // TODO(b/190368382): This will change when databasebuilder is included in deps.
+ .addLibraryFiles(ToolHelper.R8_WITHOUT_DEPS_JAR, ToolHelper.DEPS)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .run(TestRuntime.getSystemRuntime(), testClass)
+ .apply(
+ result -> {
+ if (result.getExitCode() != 0) {
+ throw new RuntimeException(result.getStdErr());
+ }
+ });
+ } catch (IOException | ExecutionException | CompilationFailedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static String getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
List<String> expected = new ArrayList<>();
parsedApiClasses.forEach(
apiClass -> {
@@ -200,7 +253,7 @@
});
});
});
- return expected;
+ return StringUtils.lines(expected);
}
public static class TestGeneratedMainVisitClasses {
diff --git a/third_party/android_jar/api-database.tar.gz.sha1 b/third_party/android_jar/api-database.tar.gz.sha1
new file mode 100644
index 0000000..84abf49
--- /dev/null
+++ b/third_party/android_jar/api-database.tar.gz.sha1
@@ -0,0 +1 @@
+e4da4b29079ac393e0012e7676dcca0799841e29
\ No newline at end of file