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