Merge "Remove unneeded Utf8Consumer."
diff --git a/build.gradle b/build.gradle
index 418474a..c5fe273 100644
--- a/build.gradle
+++ b/build.gradle
@@ -709,6 +709,18 @@
destinationDir file('tests')
}
+task buildD8ApiUsageSample(type: Jar) {
+ from sourceSets.apiUsageSample.output
+ baseName 'd8_api_usage_sample'
+ destinationDir file('tests')
+}
+
+task buildR8ApiUsageSample(type: Jar) {
+ from sourceSets.apiUsageSample.output
+ baseName 'r8_api_usage_sample'
+ destinationDir file('tests')
+}
+
task buildDebugInfoExamplesDex {
def examplesDir = file("src/test/java")
def hostJar = "debuginfo_examples.jar"
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index cae894d..0424e2a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -27,6 +27,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -112,10 +113,8 @@
return self();
}
- /**
- * Add proguard configuration file resources for automatic main dex list calculation.
- */
- public Builder addMainDexRulesFiles(List<Path> paths) {
+ /** Add proguard configuration file resources for automatic main dex list calculation. */
+ public Builder addMainDexRulesFiles(Collection<Path> paths) {
guard(() -> {
for (Path path : paths) {
mainDexRules.add(new ProguardConfigurationSourceFile(path));
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index 249e7a1..9311078 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -322,7 +322,7 @@
if (options.inputArchives.size() != 1) {
throw new RuntimeException("'--multidex=given_shard' requires exactly one --input.");
}
- singleFixedFileIndex = parseFileIndexFromShardFilename(options.inputArchives.get(0));
+ singleFixedFileIndex = parseFileIndexFromShardFilename(options.inputArchives.get(0)) - 1;
break;
case MINIMAL:
case BEST_EFFORT:
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 d4a2ef9..35fcceb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -130,6 +130,7 @@
public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
+ public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
public final DexString intFieldUpdaterDescriptor =
createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -181,9 +182,12 @@
public final DexType varHandleType = createType(varHandleDescriptor);
public final DexType methodHandleType = createType(methodHandleDescriptor);
+ public final DexType methodTypeType = createType(methodTypeDescriptor);
- public final StringBuildingMethods stringBuilderMethods = new StringBuildingMethods(stringBuilderType);
- public final StringBuildingMethods stringBufferMethods = new StringBuildingMethods(stringBufferType);
+ public final StringBuildingMethods stringBuilderMethods =
+ new StringBuildingMethods(stringBuilderType);
+ public final StringBuildingMethods stringBufferMethods =
+ new StringBuildingMethods(stringBufferType);
public final ObjectsMethods objectsMethods = new ObjectsMethods();
public final ObjectMethods objectMethods = new ObjectMethods();
public final LongMethods longMethods = new LongMethods();
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 1cd1b20..2d45a308 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import java.util.function.Function;
public class ConstMethodHandle extends ConstInstruction {
@@ -76,4 +79,10 @@
public ConstMethodHandle asConstMethodHandle() {
return this;
}
+
+ @Override
+ public TypeLatticeElement evaluate(
+ AppInfoWithSubtyping appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
+ return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.methodHandleType, false);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index cbcf143..aff7b9b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import java.util.function.Function;
public class ConstMethodType extends ConstInstruction {
@@ -76,4 +79,10 @@
public ConstMethodType asConstMethodType() {
return this;
}
+
+ @Override
+ public TypeLatticeElement evaluate(
+ AppInfoWithSubtyping appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
+ return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.methodTypeType, false);
+ }
}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
new file mode 100644
index 0000000..363b80c
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -0,0 +1,449 @@
+// 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.ArchiveProgramResourceProvider;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class D8ApiUsageSample {
+
+ private static final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "D8ApiUsageSample";
+ }
+ };
+
+ private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
+
+ /**
+ * Example invocation:
+ *
+ * <pre>
+ * java -jar d8-api-uses.jar \
+ * --output path/to/output/dir \
+ * --min-api minApiLevel \
+ * --lib path/to/library.jar \
+ * --classpath path/to/classpath.jar \
+ * path/to/input{1,2,3}.{jar,class}
+ * </pre>
+ */
+ public static void main(String[] args) {
+ // Parse arguments with the commandline parser to make use of its API.
+ D8Command.Builder cmd = D8Command.parse(args, origin);
+ CompilationMode mode = cmd.getMode();
+ Path temp = cmd.getOutputPath();
+ int minApiLevel = cmd.getMinApiLevel();
+ // The Builder API does not provide access to the concrete paths
+ // (everything is put into providers) so manually parse them here.
+ List<Path> libraries = new ArrayList<>(1);
+ List<Path> classpath = new ArrayList<>(args.length);
+ List<Path> mainDexList = new ArrayList<>(1);
+ List<Path> inputs = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--lib")) {
+ libraries.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--classpath")) {
+ classpath.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--main-dex-list")) {
+ mainDexList.add(Paths.get(args[++i]));
+ } else if (isArchive(args[i]) || isClassFile(args[i])) {
+ inputs.add(Paths.get(args[i]));
+ }
+ }
+ if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+ throw new RuntimeException("Must supply a temp/output directory");
+ }
+ if (inputs.isEmpty()) {
+ throw new RuntimeException("Must supply program inputs");
+ }
+ if (classpath.isEmpty()) {
+ throw new RuntimeException("Must supply classpath inputs");
+ }
+ if (libraries.isEmpty()) {
+ throw new RuntimeException("Must supply library inputs");
+ }
+ if (mainDexList.isEmpty()) {
+ throw new RuntimeException("Must supply main-dex-list inputs");
+ }
+
+ useProgramFileBuilder(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
+ useProgramFileBuilder(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
+ useProgramDataBuilder(minApiLevel, libraries, classpath, inputs);
+ useProgramProvider(minApiLevel, libraries, classpath, inputs);
+ useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
+ useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
+ useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
+ incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
+ }
+
+ // Check API support for compiling Java class-files from the file system.
+ private static void useProgramFileBuilder(
+ CompilationMode mode,
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMode(mode)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from byte content.
+ private static void useProgramDataBuilder(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath);
+ for (ClassFileContent classfile : readClassFiles(inputs)) {
+ builder.addClassProgramData(classfile.data, classfile.origin);
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from a program provider abstraction.
+ private static void useProgramProvider(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath);
+ for (Path input : inputs) {
+ if (isArchive(input)) {
+ builder.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(
+ input, ArchiveProgramResourceProvider::includeClassFileEntries));
+ } else {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+ }
+ });
+ }
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useLibraryAndClasspathProvider(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ try {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addProgramFiles(inputs);
+ for (Path library : libraries) {
+ builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+ }
+ for (Path path : classpath) {
+ builder.addClasspathResourceProvider(new ArchiveClassFileProvider(path));
+ }
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useMainDexListFiles(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs,
+ Collection<Path> mainDexList) {
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .addMainDexListFiles(mainDexList)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useMainDexClasses(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs,
+ Collection<Path> mainDexList) {
+ try {
+ List<String> mainDexClasses = new ArrayList<>(1);
+ for (Path path : mainDexList) {
+ for (String line : Files.readAllLines(path)) {
+ String entry = line.trim();
+ if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
+ continue;
+ }
+ mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
+ }
+ }
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .addMainDexClasses(mainDexClasses)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void incrementalCompileAndMerge(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ // Compile and merge via index intermediates.
+ mergeIntermediates(
+ minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
+ // Compile and merge via per-classfile intermediates.
+ mergeIntermediates(
+ minApiLevel, compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
+ }
+
+ private static Collection<byte[]> compileToIndexedIntermediates(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setIntermediate(true)
+ .setProgramConsumer(consumer)
+ .addClasspathFiles(classpath)
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ return consumer.bytes;
+ }
+
+ private static Collection<byte[]> compileToPerClassFileIntermediates(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> classpath,
+ Collection<Path> inputs) {
+ PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
+ try {
+ D8.run(
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(consumer)
+ .addLibraryFiles(libraries)
+ .addClasspathFiles(classpath)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ return consumer.bytes;
+ }
+
+ private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
+ D8Command.Builder builder =
+ D8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer());
+ for (byte[] intermediate : intermediates) {
+ builder.addDexProgramData(intermediate, Origin.unknown());
+ }
+ try {
+ D8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected merging error", e);
+ }
+ }
+
+ // Helpers for tests.
+ // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+ // rely on it.
+
+ private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
+ List<ClassFileContent> classfiles = new ArrayList<>();
+ for (Path file : files) {
+ if (isArchive(file)) {
+ Origin zipOrigin = new PathOrigin(file);
+ ZipInputStream zip = new ZipInputStream(Files.newInputStream(file));
+ ZipEntry entry;
+ while (null != (entry = zip.getNextEntry())) {
+ if (isClassFile(Paths.get(entry.getName()))) {
+ Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+ classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+ }
+ }
+ } else if (isClassFile(file)) {
+ classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+ }
+ }
+ return classfiles;
+ }
+
+ private static byte[] readBytes(InputStream stream) throws IOException {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[0xffff];
+ for (int length; (length = stream.read(buffer)) != -1; ) {
+ bytes.write(buffer, 0, length);
+ }
+ return bytes.toByteArray();
+ }
+ }
+
+ private static boolean isClassFile(Path file) {
+ return isClassFile(file.toString());
+ }
+
+ private static boolean isClassFile(String file) {
+ file = file.toLowerCase();
+ return file.endsWith(".class");
+ }
+
+ private static boolean isArchive(Path file) {
+ return isArchive(file.toString());
+ }
+
+ private static boolean isArchive(String file) {
+ file = file.toLowerCase();
+ return file.endsWith(".zip") || file.endsWith(".jar");
+ }
+
+ private static class ClassFileContent {
+ final Origin origin;
+ final byte[] data;
+
+ public ClassFileContent(Origin origin, byte[] data) {
+ this.origin = origin;
+ this.data = data;
+ }
+ }
+
+ private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
+
+ List<byte[]> bytes = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ bytes.add(data);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
+
+ private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
+
+ List<byte[]> bytes = new ArrayList<>();
+
+ @Override
+ public synchronized void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ bytes.add(data);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ }
+
+ private static class EnsureOutputConsumer implements DexIndexedConsumer {
+ boolean hasOutput = false;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!hasOutput) {
+ handler.error(new StringDiagnostic("Expected to produce output but had none"));
+ }
+ }
+ }
+}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
index d9f11b0..9b08352 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
@@ -49,7 +49,7 @@
Origin origin = diagnostic.getOrigin();
Position positionInOrigin = diagnostic.getPosition();
String position;
- if (origin instanceof PathOrigin) {
+ if (origin.parent() instanceof PathOrigin) {
if (positionInOrigin instanceof TextRange) {
TextRange textRange = (TextRange) positionInOrigin;
position = ((PathOrigin) origin.parent()).getPath().toFile() + ": "
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
new file mode 100644
index 0000000..d069d05
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -0,0 +1,420 @@
+// Copyright (c) 2018, 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.ArchiveProgramResourceProvider;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class R8ApiUsageSample {
+
+ private static final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "R8ApiUsageSample";
+ }
+ };
+
+ private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
+
+ /**
+ * Example invocation:
+ *
+ * <pre>
+ * java -jar r8-api-uses.jar \
+ * --output path/to/output/dir \
+ * --min-api minApiLevel \
+ * --lib path/to/library.jar \
+ * path/to/input{1,2,3}.{jar,class}
+ * </pre>
+ */
+ public static void main(String[] args) {
+ // Parse arguments with the commandline parser to make use of its API.
+ R8Command.Builder cmd = R8Command.parse(args, origin);
+ CompilationMode mode = cmd.getMode();
+ Path temp = cmd.getOutputPath();
+ int minApiLevel = cmd.getMinApiLevel();
+ // The Builder API does not provide access to the concrete paths
+ // (everything is put into providers) so manually parse them here.
+ List<Path> libraries = new ArrayList<>(1);
+ List<Path> mainDexList = new ArrayList<>(1);
+ List<Path> mainDexRules = new ArrayList<>(1);
+ List<Path> pgConf = new ArrayList<>(1);
+ List<Path> inputs = new ArrayList<>(args.length);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--lib")) {
+ libraries.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--main-dex-list")) {
+ mainDexList.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--main-dex-rules")) {
+ mainDexRules.add(Paths.get(args[++i]));
+ } else if (args[i].equals("--pg-conf")) {
+ pgConf.add(Paths.get(args[++i]));
+ } else if (isArchive(args[i]) || isClassFile(args[i])) {
+ inputs.add(Paths.get(args[i]));
+ }
+ }
+ if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+ throw new RuntimeException("Must supply a temp/output directory");
+ }
+ if (inputs.isEmpty()) {
+ throw new RuntimeException("Must supply program inputs");
+ }
+ if (libraries.isEmpty()) {
+ throw new RuntimeException("Must supply library inputs");
+ }
+ if (mainDexList.isEmpty()) {
+ throw new RuntimeException("Must supply main-dex-list inputs");
+ }
+ if (mainDexRules.isEmpty()) {
+ throw new RuntimeException("Must supply main-dex-rules inputs");
+ }
+ if (pgConf.isEmpty()) {
+ throw new RuntimeException("Must supply pg-conf inputs");
+ }
+
+ useProgramFileBuilder(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
+ useProgramFileBuilder(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
+ useProgramDataBuilder(minApiLevel, libraries, inputs);
+ useProgramProvider(minApiLevel, libraries, inputs);
+ useLibraryProvider(minApiLevel, libraries, inputs);
+ useMainDexListFiles(minApiLevel, libraries, inputs, mainDexList);
+ useMainDexClasses(minApiLevel, libraries, inputs, mainDexList);
+ useMainDexRulesFiles(minApiLevel, libraries, inputs, mainDexRules);
+ useMainDexRules(minApiLevel, libraries, inputs, mainDexRules);
+ useProguardConfigFiles(minApiLevel, libraries, inputs, mainDexList, pgConf);
+ useProguardConfigLines(minApiLevel, libraries, inputs, mainDexList, pgConf);
+ }
+
+ // Check API support for compiling Java class-files from the file system.
+ private static void useProgramFileBuilder(
+ CompilationMode mode, int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMode(mode)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from byte content.
+ private static void useProgramDataBuilder(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries);
+ for (ClassFileContent classfile : readClassFiles(inputs)) {
+ builder.addClassProgramData(classfile.data, classfile.origin);
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Check API support for compiling Java class-files from a program provider abstraction.
+ private static void useProgramProvider(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries);
+ for (Path input : inputs) {
+ if (isArchive(input)) {
+ builder.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(
+ input, ArchiveProgramResourceProvider::includeClassFileEntries));
+ } else {
+ builder.addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+ }
+ });
+ }
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useLibraryProvider(
+ int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addProgramFiles(inputs);
+ for (Path library : libraries) {
+ builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useMainDexListFiles(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs,
+ Collection<Path> mainDexList) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addMainDexListFiles(mainDexList)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useMainDexClasses(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs,
+ Collection<Path> mainDexList) {
+ try {
+ List<String> mainDexClasses = new ArrayList<>(1);
+ for (Path path : mainDexList) {
+ for (String line : Files.readAllLines(path)) {
+ String entry = line.trim();
+ if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
+ continue;
+ }
+ mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
+ }
+ }
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addMainDexClasses(mainDexClasses)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useMainDexRulesFiles(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs,
+ Collection<Path> mainDexRules) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addMainDexRulesFiles(mainDexRules)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useMainDexRules(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs,
+ Collection<Path> mainDexRulesFiles) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs);
+ for (Path mainDexRulesFile : mainDexRulesFiles) {
+ builder.addMainDexRules(
+ Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ private static void useProguardConfigFiles(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs,
+ Collection<Path> mainDexList,
+ List<Path> pgConf) {
+ try {
+ R8.run(
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addMainDexListFiles(mainDexList)
+ .addProguardConfigurationFiles(pgConf)
+ .build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ }
+ }
+
+ private static void useProguardConfigLines(
+ int minApiLevel,
+ Collection<Path> libraries,
+ Collection<Path> inputs,
+ Collection<Path> mainDexList,
+ List<Path> pgConf) {
+ try {
+ R8Command.Builder builder =
+ R8Command.builder(handler)
+ .setMinApiLevel(minApiLevel)
+ .setProgramConsumer(new EnsureOutputConsumer())
+ .addLibraryFiles(libraries)
+ .addProgramFiles(inputs)
+ .addMainDexListFiles(mainDexList);
+ for (Path file : pgConf) {
+ builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
+ }
+ R8.run(builder.build());
+ } catch (CompilationFailedException e) {
+ throw new RuntimeException("Unexpected compilation exceptions", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Unexpected IO exception", e);
+ }
+ }
+
+ // Helpers for tests.
+ // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+ // rely on it.
+
+ private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
+ List<ClassFileContent> classfiles = new ArrayList<>();
+ for (Path file : files) {
+ if (isArchive(file)) {
+ Origin zipOrigin = new PathOrigin(file);
+ ZipInputStream zip = new ZipInputStream(Files.newInputStream(file));
+ ZipEntry entry;
+ while (null != (entry = zip.getNextEntry())) {
+ if (isClassFile(Paths.get(entry.getName()))) {
+ Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+ classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+ }
+ }
+ } else if (isClassFile(file)) {
+ classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+ }
+ }
+ return classfiles;
+ }
+
+ private static byte[] readBytes(InputStream stream) throws IOException {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[0xffff];
+ for (int length; (length = stream.read(buffer)) != -1; ) {
+ bytes.write(buffer, 0, length);
+ }
+ return bytes.toByteArray();
+ }
+ }
+
+ private static boolean isClassFile(Path file) {
+ return isClassFile(file.toString());
+ }
+
+ private static boolean isClassFile(String file) {
+ file = file.toLowerCase();
+ return file.endsWith(".class");
+ }
+
+ private static boolean isArchive(Path file) {
+ return isArchive(file.toString());
+ }
+
+ private static boolean isArchive(String file) {
+ file = file.toLowerCase();
+ return file.endsWith(".zip") || file.endsWith(".jar");
+ }
+
+ private static class ClassFileContent {
+ final Origin origin;
+ final byte[] data;
+
+ public ClassFileContent(Origin origin, byte[] data) {
+ this.origin = origin;
+ this.data = data;
+ }
+ }
+
+ private static class EnsureOutputConsumer implements DexIndexedConsumer {
+ boolean hasOutput = false;
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ hasOutput = true;
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (!hasOutput) {
+ handler.error(new StringDiagnostic("Expected to produce output but had none"));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
deleted file mode 100644
index 5a3b0f8..0000000
--- a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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/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..86f0319
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -0,0 +1,121 @@
+// 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.android.tools.r8.utils.FileUtils;
+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 java.util.stream.Collectors;
+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 testCompatibilityDeprecatedApi() 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);
+ Assert.assertTrue(result.stdout, result.stdout.isEmpty());
+ Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+ }
+
+ @Test
+ public void testCompatibilityNewApi() throws IOException {
+ Path jar = Paths.get("tests", "d8_api_usage_sample.jar");
+ String main = "com.android.tools.apiusagesample.D8ApiUsageSample";
+ int minApiLevel = AndroidApiLevel.K.getLevel();
+
+ 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 inputDir =
+ Paths.get(
+ ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR, "classes", "desugaringwithmissingclasstest1");
+ List<Path> input =
+ ImmutableList.of(
+ inputDir.resolve("ImplementMethodsWithDefault.class"), inputDir.resolve("Main.class"));
+
+ Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
+ FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
+
+ List<String> command =
+ ImmutableList.<String>builder()
+ .addAll(
+ ImmutableList.of(
+ ToolHelper.getJavaExecutable(),
+ "-cp",
+ jar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+ main,
+ // Compiler arguments.
+ "--output",
+ temp.newFolder().getAbsolutePath(),
+ "--min-api",
+ Integer.toString(minApiLevel),
+ "--main-dex-list",
+ mainDexList.toString(),
+ "--lib",
+ ToolHelper.getAndroidJar(minApiLevel),
+ "--classpath",
+ lib1.toString(),
+ "--classpath",
+ lib2.toString()))
+ .addAll(input.stream().map(Path::toString).collect(Collectors.toList()))
+ .build();
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ ProcessResult result = ToolHelper.runProcess(builder);
+ Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+ Assert.assertTrue(result.stdout, result.stdout.isEmpty());
+ Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
new file mode 100644
index 0000000..898e691
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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 com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class R8ApiBinaryCompatibilityTests {
+
+ static final Path JAR = Paths.get("tests", "r8_api_usage_sample.jar");
+ static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
+ static final int MIN_API = AndroidApiLevel.K.getLevel();
+
+ @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testCompatibility() throws IOException {
+ List<Path> inputs =
+ ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
+
+ Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
+ FileUtils.writeTextFile(
+ pgConf, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+
+ Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
+ FileUtils.writeTextFile(
+ mainDexRules, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+
+ Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
+ FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
+
+ List<String> command =
+ ImmutableList.<String>builder()
+ .addAll(
+ ImmutableList.of(
+ ToolHelper.getJavaExecutable(),
+ "-cp",
+ JAR.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+ MAIN,
+ // Compiler arguments.
+ "--output",
+ temp.newFolder().toString(),
+ "--min-api",
+ Integer.toString(MIN_API),
+ "--pg-conf",
+ pgConf.toString(),
+ "--main-dex-rules",
+ mainDexRules.toString(),
+ "--main-dex-list",
+ mainDexList.toString(),
+ "--lib",
+ ToolHelper.getAndroidJar(MIN_API)))
+ .addAll(inputs.stream().map(Path::toString).collect(Collectors.toList()))
+ .build();
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ ProcessResult result = ToolHelper.runProcess(builder);
+ Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+ Assert.assertTrue(result.stdout, result.stdout.isEmpty());
+ Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4412dec..3af239f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -280,7 +280,7 @@
* Generate a Proguard configuration for keeping the "public static void main(String[])" method
* of the specified class.
*/
- public String keepMainProguardConfiguration(Class clazz) {
+ public static String keepMainProguardConfiguration(Class clazz) {
return keepMainProguardConfiguration(clazz.getCanonicalName());
}
@@ -288,7 +288,7 @@
* Generate a Proguard configuration for keeping the "public static void main(String[])" method
* of the specified class.
*/
- public String keepMainProguardConfiguration(String clazz) {
+ public static String keepMainProguardConfiguration(String clazz) {
return "-keep public class " + clazz + " {\n"
+ " public static void main(java.lang.String[]);\n"
+ "}\n"
@@ -300,7 +300,7 @@
* of the specified class and specify if -allowaccessmodification and -dontobfuscate are added
* as well.
*/
- public String keepMainProguardConfiguration(
+ public static String keepMainProguardConfiguration(
Class clazz, boolean allowaccessmodification, boolean obfuscate) {
return keepMainProguardConfiguration(clazz)
+ (allowaccessmodification ? "-allowaccessmodification\n" : "")
diff --git a/tests/d8_api_usage_sample.jar b/tests/d8_api_usage_sample.jar
new file mode 100644
index 0000000..3f78a93
--- /dev/null
+++ b/tests/d8_api_usage_sample.jar
Binary files differ
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
new file mode 100644
index 0000000..e0456f6
--- /dev/null
+++ b/tests/r8_api_usage_sample.jar
Binary files differ