Add a test checking D8 API binary compatibility
When changing source in
src/test/apiUsageSample/com/android/tools/apiusagesample, a manual
compilation of the corresponding jar is necessary:
./tools/gradle.py buildApiUsageSample
This is needed to allow the test to detect incompatible changes that
would be solved by a re-compilation of the client classes.
Change-Id: If4c15bf4472bb2d2846925730471cadad22391f3
diff --git a/build.gradle b/build.gradle
index 61360dc..c0a183b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -77,6 +77,12 @@
]
}
}
+ apiUsageSample {
+ java {
+ srcDirs = ['src/test/apiUsageSample']
+ }
+ output.resourcesDir = 'build/classes/apiUsageSample'
+ }
debugTestResources {
java {
srcDirs = ['src/test/debugTestResources']
@@ -191,6 +197,7 @@
supportLibs 'com.android.support:support-v4:25.4.0'
supportLibs 'junit:junit:4.12'
supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+ apiUsageSampleCompile sourceSets.main.output
debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.1.4-3'
apt 'com.google.auto.value:auto-value:1.5'
}
@@ -630,6 +637,12 @@
dependsOn createJctfTests
}
+task buildApiUsageSample(type: Jar) {
+ from sourceSets.apiUsageSample.output
+ baseName 'api_usage_sample'
+ destinationDir file('tests')
+}
+
task buildDebugInfoExamplesDex {
def examplesDir = file("src/test/java")
def hostJar = "debuginfo_examples.jar"
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
new file mode 100644
index 0000000..859876d
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2017, 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.apiusagesample;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.utils.DirectoryClassFileProvider;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CachingArchiveClassFileProvider extends ArchiveClassFileProvider {
+
+ private ConcurrentHashMap<String, Resource> resources = new ConcurrentHashMap<>();
+
+ private CachingArchiveClassFileProvider(Path archive) throws IOException {
+ super(archive);
+ }
+
+ @Override
+ public Resource getResource(String descriptor) {
+ return resources.computeIfAbsent(descriptor, desc -> super.getResource(desc));
+ }
+
+ public static ClassFileResourceProvider getProvider(Path entry)
+ throws IOException {
+ if (Files.isRegularFile(entry)) {
+ return new CachingArchiveClassFileProvider(entry);
+ } else if (Files.isDirectory(entry)) {
+ return DirectoryClassFileProvider.fromDirectory(entry);
+ } else {
+ throw new FileNotFoundException(entry.toString());
+ }
+ }
+}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
new file mode 100644
index 0000000..ff61567
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2017, 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.apiusagesample;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class D8Compiler {
+ private int minSdkVersion;
+ private Path bootclasspath;
+ private List<Path> classpath;
+ private static ExecutorService pool = Executors.newFixedThreadPool(4);
+
+ private D8Compiler(int minSdkVersion, Path bootclasspath, List<Path> classpath) {
+ this.minSdkVersion = minSdkVersion;
+ this.bootclasspath = bootclasspath;
+ this.classpath = classpath;
+ }
+
+ /**
+ * java ...Compiler output input minSdkVersion mainDexClasses bootclasspath [classpathEntries]+
+ */
+ public static void main(String[] args) throws Throwable {
+ try {
+ int argIndex = 0;
+ Path outputDir = Paths.get(args[argIndex++]);
+ Path input = Paths.get(args[argIndex++]);
+ int minSdkVersion = Integer.parseInt(args[argIndex++]);
+ Path mainDexClasses = Paths.get(args[argIndex++]);
+ Path bootclasspath = Paths.get(args[argIndex++]);
+
+ List<Path> classpath = new ArrayList<>(args.length - argIndex);
+ while (argIndex < args.length) {
+ classpath.add(Paths.get(args[argIndex++]));
+ }
+
+ D8Compiler compiler = new D8Compiler(minSdkVersion, bootclasspath, classpath);
+
+ List<Path> toMerge = new ArrayList<>(3);
+
+ int intermediateIndex = 0;
+ for (Path entry : classpath) {
+ Path output = outputDir.resolve(entry.getFileName() + "." + (intermediateIndex++));
+ Files.createDirectory(output);
+ toMerge.add(output);
+ compiler.compile(output, entry);
+ }
+
+ Path output = outputDir.resolve("main." + (intermediateIndex++));
+ Files.createDirectory(output);
+ toMerge.add(output);
+ compiler.compile(output, input);
+
+ compiler.merge(outputDir, mainDexClasses, toMerge);
+ } finally {
+ // Terminate pool threads to prevent the VM to wait on then before exiting.
+ pool.shutdown();
+ }
+ }
+
+ private void compile(Path output, Path input) throws Throwable {
+ D8Command.Builder builder =
+ D8Command.builder()
+ // Compile in debug and merge in release to assert access to both modes
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(minSdkVersion)
+ .setIntermediate(true)
+ .setEnableDesugaring(true)
+ .setOutputPath(output);
+
+ builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(bootclasspath));
+
+ for (Path entry : classpath) {
+ builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(entry));
+ }
+
+ if (Files.isRegularFile(input)) {
+ builder.setOutputMode(OutputMode.Indexed);
+ builder.addProgramFiles(input);
+ } else {
+ builder.setOutputMode(OutputMode.FilePerInputClass);
+ Files.walkFileTree(input, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+ throws IOException {
+ builder.addClassProgramData(Files.readAllBytes(path));
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ D8.run(builder.build(), pool);
+ }
+
+ private void merge(Path outputDir, Path mainDexClasses,
+ List<Path> toMerge) throws IOException, CompilationException {
+ D8Command.Builder merger = D8Command.builder();
+ merger.setEnableDesugaring(false);
+
+ for (Path mergeInput : toMerge) {
+ Files.walkFileTree(mergeInput, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+ throws IOException {
+ merger.addDexProgramData(Files.readAllBytes(path));
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+ if (mainDexClasses != null) {
+ merger.addMainDexListFiles(mainDexClasses);
+ }
+ merger.setMinApiLevel(minSdkVersion)
+ .setMode(CompilationMode.RELEASE)
+ .setOutputPath(outputDir)
+ .setEnableDesugaring(false)
+ .setIntermediate(false);
+ D8.run(merger.build());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
new file mode 100644
index 0000000..5a3b0f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2017, 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class D8APiBinaryCompatibilityTests {
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testCompatibility() throws IOException {
+ Path compilerJar = Paths.get("tests", "api_usage_sample.jar");
+ String compiler = "com.android.tools.apiusagesample.D8Compiler";
+
+ String output = temp.newFolder().getAbsolutePath();
+ int minSdkVersion = AndroidApiLevel.K.getLevel();
+ String androidJar = ToolHelper.getAndroidJar(minSdkVersion);
+ Path lib1 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+ "desugaringwithmissingclasslib1" + JAR_EXTENSION);
+ Path lib2 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+ "desugaringwithmissingclasslib2" + JAR_EXTENSION);
+ Path input = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+ "classes", "desugaringwithmissingclasstest1");
+ File mainDexClasses = temp.newFile();
+ Files.asCharSink(mainDexClasses, StandardCharsets.UTF_8)
+ .write("desugaringwithmissingclasstest1/Main.class");
+
+ List<String> command = ImmutableList.of(
+ ToolHelper.getJavaExecutable(),
+ "-cp",
+ compilerJar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+ compiler,
+ // Compiler arguments.
+ output,
+ input.toString(),
+ Integer.toString(minSdkVersion),
+ mainDexClasses.getAbsolutePath(),
+ androidJar,
+ lib1.toString(),
+ lib2.toString());
+ ProcessBuilder builder = new ProcessBuilder(command);
+ ProcessResult result = ToolHelper.runProcess(builder);
+
+ Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+ }
+}
diff --git a/tests/api_usage_sample.jar b/tests/api_usage_sample.jar
new file mode 100644
index 0000000..b8b9d66
--- /dev/null
+++ b/tests/api_usage_sample.jar
Binary files differ