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'):