Add a compile dump wrapper for D8 to pass in api modeling flags

Change-Id: Ic59fd9d9ee9f8401913987d531736dacb4206f20
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
new file mode 100644
index 0000000..0e3a0c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
@@ -0,0 +1,201 @@
+// Copyright (c) 2022, 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.utils;
+
+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.OutputMode;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Wrapper to make it easy to call D8 mode when compiling a dump file.
+ *
+ * <p>This wrapper will be added to the classpath so it *must* only refer to the public API. See
+ * {@code tools/compiledump.py}.
+ *
+ * <p>It is tempting to have this class share the D8 parser code, but such refactoring would not be
+ * valid on past version of the D8 API. Thus there is little else to do than reimplement the parts
+ * we want to support for reading dumps.
+ */
+public class CompileDumpD8 {
+
+  private static final List<String> VALID_OPTIONS =
+      Arrays.asList(
+          "--classfile",
+          "--debug",
+          "--release",
+          "--enable-missing-library-api-modeling",
+          "--android-platform-build");
+
+  private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
+      Arrays.asList(
+          "--output",
+          "--lib",
+          "--classpath",
+          "--min-api",
+          "--main-dex-rules",
+          "--main-dex-list",
+          "--main-dex-list-output",
+          "--desugared-lib",
+          "--threads");
+
+  public static void main(String[] args) throws CompilationFailedException {
+    OutputMode outputMode = OutputMode.DexIndexed;
+    Path outputPath = null;
+    Path desugaredLibJson = null;
+    CompilationMode compilationMode = CompilationMode.RELEASE;
+    List<Path> program = new ArrayList<>();
+    List<Path> library = new ArrayList<>();
+    List<Path> classpath = new ArrayList<>();
+    List<Path> mainDexRulesFiles = new ArrayList<>();
+    int minApi = 1;
+    int threads = -1;
+    boolean enableMissingLibraryApiModeling = false;
+    boolean androidPlatformBuild = false;
+    for (int i = 0; i < args.length; i++) {
+      String option = args[i];
+      if (VALID_OPTIONS.contains(option)) {
+        switch (option) {
+          case "--classfile":
+            {
+              outputMode = OutputMode.ClassFile;
+            }
+          case "--debug":
+            {
+              compilationMode = CompilationMode.DEBUG;
+              break;
+            }
+          case "--release":
+            {
+              compilationMode = CompilationMode.RELEASE;
+              break;
+            }
+          case "--enable-missing-library-api-modeling":
+            enableMissingLibraryApiModeling = true;
+            break;
+          case "--android-platform-build":
+            androidPlatformBuild = true;
+            break;
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
+      } else if (VALID_OPTIONS_WITH_SINGLE_OPERAND.contains(option)) {
+        String operand = args[++i];
+        switch (option) {
+          case "--output":
+            {
+              outputPath = Paths.get(operand);
+              break;
+            }
+          case "--lib":
+            {
+              library.add(Paths.get(operand));
+              break;
+            }
+          case "--classpath":
+            {
+              classpath.add(Paths.get(operand));
+              break;
+            }
+          case "--min-api":
+            {
+              minApi = Integer.parseInt(operand);
+              break;
+            }
+          case "--desugared-lib":
+            {
+              desugaredLibJson = Paths.get(operand);
+              break;
+            }
+          case "--threads":
+            {
+              threads = Integer.parseInt(operand);
+              break;
+            }
+          case "--main-dex-rules":
+            {
+              mainDexRulesFiles.add(Paths.get(operand));
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
+      } else {
+        program.add(Paths.get(option));
+      }
+    }
+    D8Command.Builder commandBuilder =
+        D8Command.builder()
+            .addProgramFiles(program)
+            .addLibraryFiles(library)
+            .addClasspathFiles(classpath)
+            .addMainDexRulesFiles(mainDexRulesFiles)
+            .setOutput(outputPath, outputMode)
+            .setMode(compilationMode);
+    getReflectiveBuilderMethod(
+            commandBuilder, "setEnableExperimentalMissingLibraryApiModeling", boolean.class)
+        .accept(new Object[] {enableMissingLibraryApiModeling});
+    getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
+        .accept(new Object[] {androidPlatformBuild});
+    if (desugaredLibJson != null) {
+      commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
+    }
+    commandBuilder.setMinApiLevel(minApi);
+    D8Command command = commandBuilder.build();
+    if (threads != -1) {
+      ExecutorService executor = Executors.newWorkStealingPool(threads);
+      try {
+        D8.run(command, executor);
+      } finally {
+        executor.shutdown();
+      }
+    } else {
+      D8.run(command);
+    }
+  }
+
+  private static Consumer<Object[]> getReflectiveBuilderMethod(
+      D8Command.Builder builder, String setter, Class<?>... parameters) {
+    try {
+      Method declaredMethod = D8Command.Builder.class.getMethod(setter, parameters);
+      return args -> {
+        try {
+          declaredMethod.invoke(builder, args);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      };
+    } catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      // The option is not available so we just return an empty consumer
+      return args -> {};
+    }
+  }
+
+  // We cannot use StringResource since this class is added to the class path and has access only
+  // to the public APIs.
+  private static String readAllBytesJava7(Path filePath) {
+    String content = "";
+
+    try {
+      content = new String(Files.readAllBytes(filePath));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    return content;
+  }
+}
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 5962cc0..c6a4a19 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -357,19 +357,33 @@
     return True
   return False
 
-def prepare_wrapper(dist, temp, jdkhome):
-  wrapper_file = os.path.join(
+def prepare_r8_wrapper(dist, temp, jdkhome):
+  compile_with_javac(
+    dist,
+    temp,
+    jdkhome,
+    os.path.join(
       utils.REPO_ROOT,
-      'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java')
+      'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java'))
+
+def prepare_d8_wrapper(dist, temp, jdkhome):
+  compile_with_javac(
+    dist,
+    temp,
+    jdkhome,
+    os.path.join(
+      utils.REPO_ROOT,
+      'src/main/java/com/android/tools/r8/utils/CompileDumpD8.java'))
+
+def compile_with_javac(dist, temp, jdkhome, path):
   cmd = [
     jdk.GetJavacExecutable(jdkhome),
-    wrapper_file,
+    path,
     '-d', temp,
     '-cp', dist,
   ]
   utils.PrintCmd(cmd)
   subprocess.check_output(cmd)
-  return temp
 
 def is_hash(version):
   return len(version) == 40
@@ -396,7 +410,8 @@
     jar = args.r8_jar if args.r8_jar else download_distribution(version, args.nolib, temp)
     if ':' not in jar and not os.path.exists(jar):
       error("Distribution does not exist: " + jar)
-    wrapper_dir = prepare_wrapper(jar, temp, jdkhome)
+    prepare_r8_wrapper(jar, temp, jdkhome)
+    prepare_d8_wrapper(jar, temp, jdkhome)
     cmd = [jdk.GetJavaExecutable(jdkhome)]
     if args.debug_agent:
       if not args.nolib:
@@ -415,9 +430,9 @@
     if hasattr(args, 'properties'):
       cmd.extend(args.properties);
     cmd.extend(determine_properties(build_properties))
-    cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
+    cmd.extend(['-cp', '%s:%s' % (temp, jar)])
     if compiler == 'd8':
-      cmd.append('com.android.tools.r8.D8')
+      cmd.append('com.android.tools.r8.utils.CompileDumpD8')
     if compiler == 'l8':
       cmd.append('com.android.tools.r8.L8')
     if compiler.startswith('r8'):