Add a test checking D8 API binary compatibility

When changing source in
src/test/apiUsageSample/com/android/tools/apiusagesample, a manual
compilation of the corresponding jar is necessary:
./tools/gradle.py buildApiUsageSample
This is needed to allow the test to detect incompatible changes that
would be solved by a re-compilation of the client classes.

Change-Id: If4c15bf4472bb2d2846925730471cadad22391f3
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
new file mode 100644
index 0000000..ff61567
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.apiusagesample;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class D8Compiler {
+  private int minSdkVersion;
+  private Path bootclasspath;
+  private List<Path> classpath;
+  private static ExecutorService pool = Executors.newFixedThreadPool(4);
+
+  private D8Compiler(int minSdkVersion, Path bootclasspath, List<Path> classpath) {
+    this.minSdkVersion = minSdkVersion;
+    this.bootclasspath = bootclasspath;
+    this.classpath = classpath;
+  }
+
+  /**
+   * java ...Compiler output input minSdkVersion mainDexClasses bootclasspath [classpathEntries]+
+   */
+  public static void main(String[] args) throws Throwable {
+    try {
+      int argIndex = 0;
+      Path outputDir = Paths.get(args[argIndex++]);
+      Path input = Paths.get(args[argIndex++]);
+      int minSdkVersion = Integer.parseInt(args[argIndex++]);
+      Path mainDexClasses = Paths.get(args[argIndex++]);
+      Path bootclasspath = Paths.get(args[argIndex++]);
+
+      List<Path> classpath = new ArrayList<>(args.length - argIndex);
+      while (argIndex < args.length) {
+        classpath.add(Paths.get(args[argIndex++]));
+      }
+
+      D8Compiler compiler = new D8Compiler(minSdkVersion, bootclasspath, classpath);
+
+      List<Path> toMerge = new ArrayList<>(3);
+
+      int intermediateIndex = 0;
+      for (Path entry : classpath) {
+        Path output = outputDir.resolve(entry.getFileName() + "." + (intermediateIndex++));
+        Files.createDirectory(output);
+        toMerge.add(output);
+        compiler.compile(output, entry);
+      }
+
+      Path output = outputDir.resolve("main." + (intermediateIndex++));
+      Files.createDirectory(output);
+      toMerge.add(output);
+      compiler.compile(output, input);
+
+      compiler.merge(outputDir, mainDexClasses, toMerge);
+    } finally {
+      // Terminate pool threads to prevent the VM to wait on then before exiting.
+      pool.shutdown();
+    }
+  }
+
+  private void compile(Path output, Path input) throws Throwable {
+    D8Command.Builder builder =
+        D8Command.builder()
+            // Compile in debug and merge in release to assert access to both modes
+            .setMode(CompilationMode.DEBUG)
+            .setMinApiLevel(minSdkVersion)
+            .setIntermediate(true)
+            .setEnableDesugaring(true)
+            .setOutputPath(output);
+
+    builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(bootclasspath));
+
+    for (Path entry : classpath) {
+      builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(entry));
+    }
+
+    if (Files.isRegularFile(input)) {
+      builder.setOutputMode(OutputMode.Indexed);
+      builder.addProgramFiles(input);
+    } else {
+      builder.setOutputMode(OutputMode.FilePerInputClass);
+      Files.walkFileTree(input, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+            throws IOException {
+          builder.addClassProgramData(Files.readAllBytes(path));
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    }
+
+    D8.run(builder.build(), pool);
+  }
+
+  private void merge(Path outputDir, Path mainDexClasses,
+      List<Path> toMerge) throws IOException, CompilationException {
+    D8Command.Builder merger = D8Command.builder();
+    merger.setEnableDesugaring(false);
+
+    for (Path mergeInput : toMerge) {
+      Files.walkFileTree(mergeInput, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+            throws IOException {
+          merger.addDexProgramData(Files.readAllBytes(path));
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    }
+    if (mainDexClasses != null) {
+      merger.addMainDexListFiles(mainDexClasses);
+    }
+    merger.setMinApiLevel(minSdkVersion)
+        .setMode(CompilationMode.RELEASE)
+        .setOutputPath(outputDir)
+        .setEnableDesugaring(false)
+        .setIntermediate(false);
+    D8.run(merger.build());
+  }
+}