Add command line utility for compile dump files.
Change-Id: Iace6b27561a01e5e55386a346bc0284ff9471fb6
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
new file mode 100644
index 0000000..271bd70
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2020, 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.CompatProguardCommandBuilder;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Wrapper to make it easy to call R8 in compat 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 R8 parser code, but such refactoring would not be
+ * valid on past version of the R8 API. Thus there is little else to do than reimplement the parts
+ * we want to support for reading dumps.
+ */
+public class CompileDumpCompatR8 {
+
+ private static final List<String> VALID_OPTIONS =
+ Arrays.asList("--classfile", "--compat", "--debug", "--release");
+
+ private static final List<String> VALID_OPTIONS_WITH_OPERAND =
+ Arrays.asList(
+ "--output",
+ "--lib",
+ "--classpath",
+ "--min-api",
+ "--main-dex-rules",
+ "--main-dex-list",
+ "--main-dex-list-output",
+ "--pg-conf",
+ "--pg-map-output",
+ "--desugared-lib");
+
+ public static void main(String[] args) throws CompilationFailedException {
+ boolean isCompatMode = false;
+ OutputMode outputMode = OutputMode.DexIndexed;
+ Path outputPath = null;
+ CompilationMode compilationMode = CompilationMode.RELEASE;
+ List<Path> program = new ArrayList<>();
+ List<Path> library = new ArrayList<>();
+ List<Path> classpath = new ArrayList<>();
+ List<Path> config = new ArrayList<>();
+ int minApi = 1;
+ for (int i = 0; i < args.length; i++) {
+ String option = args[i];
+ if (VALID_OPTIONS.contains(option)) {
+ switch (option) {
+ case "--classfile":
+ {
+ outputMode = OutputMode.ClassFile;
+ break;
+ }
+ case "--compat":
+ {
+ isCompatMode = true;
+ break;
+ }
+ case "--debug":
+ {
+ compilationMode = CompilationMode.DEBUG;
+ break;
+ }
+ case "--release":
+ {
+ compilationMode = CompilationMode.RELEASE;
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unimplemented option: " + option);
+ }
+ } else if (VALID_OPTIONS_WITH_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 "--pg-conf":
+ {
+ config.add(Paths.get(operand));
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unimplemented option: " + option);
+ }
+ } else {
+ program.add(Paths.get(option));
+ }
+ }
+ R8.run(
+ new CompatProguardCommandBuilder(isCompatMode)
+ .addProgramFiles(program)
+ .addLibraryFiles(library)
+ .addClasspathFiles(classpath)
+ .addProguardConfigurationFiles(config)
+ .setOutput(outputPath, outputMode)
+ .setMode(compilationMode)
+ .setMinApiLevel(minApi)
+ .build());
+ }
+}
diff --git a/tools/compiledump.py b/tools/compiledump.py
new file mode 100755
index 0000000..6f78eef
--- /dev/null
+++ b/tools/compiledump.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+# Copyright (c) 2020, 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.
+
+import argparse
+import os
+import subprocess
+import sys
+import zipfile
+
+import archive
+import jdk
+import retrace
+import utils
+
+
+def make_parser():
+ parser = argparse.ArgumentParser(description = 'Compile a dump artifact.')
+ parser.add_argument(
+ '-d',
+ '--dump',
+ help='Dump file to compile',
+ default=None)
+ parser.add_argument(
+ '-c',
+ '--compiler',
+ help='Compiler to use (default read from version file)',
+ default=None)
+ parser.add_argument(
+ '-v',
+ '--version',
+ help='Compiler version to use (default read from dump version file).'
+ 'Valid arguments are:'
+ ' "master" to run from your own tree,'
+ ' "X.Y.Z" to run a specific version, or'
+ ' <hash> to run that hash from master.',
+ default=None)
+ parser.add_argument(
+ '--nolib',
+ help='Use the non-lib distribution (default uses the lib distribution)',
+ default=False,
+ action='store_true')
+ parser.add_argument(
+ '--ea',
+ help='Enable Java assertions when running the compiler (default disabled)',
+ default=False,
+ action='store_true')
+ parser.add_argument(
+ '--debug-agent',
+ help='Enable Java debug agent and suspend compilation (default disabled)',
+ default=False,
+ action='store_true')
+ return parser
+
+def error(msg):
+ print msg
+ sys.exit(1)
+
+class Dump(object):
+
+ def __init__(self, directory):
+ self.directory = directory
+
+ def if_exists(self, name):
+ f = os.path.join(self.directory, name)
+ if os.path.exists(f):
+ return f
+ return None
+
+ def program_jar(self):
+ return self.if_exists('program.jar')
+
+ def library_jar(self):
+ return self.if_exists('library.jar')
+
+ def classpath_jar(self):
+ return self.if_exists('classpath.jar')
+
+ def config_file(self):
+ return self.if_exists('proguard.config')
+
+ def version_file(self):
+ return self.if_exists('r8-version')
+
+ def version(self):
+ f = self.version_file()
+ if f:
+ return open(f).read().split(' ')[0]
+ return None
+
+def read_dump(args, temp):
+ if args.dump is None:
+ error("A dump file must be specified")
+ dump_file = zipfile.ZipFile(args.dump, 'r')
+ with utils.ChangedWorkingDirectory(temp):
+ dump_file.extractall()
+ return Dump(temp)
+
+def determine_version(args, dump):
+ if args.version is None:
+ return dump.version()
+ return args.version
+
+def determine_compiler(args, dump):
+ compilers = ('d8', 'r8', 'r8full')
+ if args.compiler not in compilers:
+ error("Unable to determine a compiler to use. Valid options: %s" % compilers.join(', '))
+ return args.compiler
+
+def determine_output(args, temp):
+ return os.path.join(temp, 'out.jar')
+
+def download_distribution(args, version, temp):
+ if version == 'master':
+ return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
+ name = 'r8.jar' if args.nolib else 'r8lib.jar'
+ source = archive.GetUploadDestination(version, name, is_hash(version))
+ dest = os.path.join(temp, 'r8.jar')
+ utils.download_file_from_cloud_storage(source, dest)
+ return dest
+
+def prepare_wrapper(dist, temp):
+ wrapper_file = os.path.join(
+ utils.REPO_ROOT,
+ 'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java')
+ subprocess.check_output([
+ jdk.GetJavacExecutable(),
+ wrapper_file,
+ '-d', temp,
+ '-cp', dist,
+ ])
+ return temp
+
+def is_hash(version):
+ return len(version) == 40
+
+def run(args, otherargs):
+ with utils.TempDir() as temp:
+ dump = read_dump(args, temp)
+ version = determine_version(args, dump)
+ compiler = determine_compiler(args, dump)
+ out = determine_output(args, temp)
+ jar = download_distribution(args, version, temp)
+ wrapper_dir = prepare_wrapper(jar, temp)
+ cmd = [jdk.GetJavaExecutable()]
+ if args.debug_agent:
+ if not args.nolib:
+ print "WARNING: Running debugging agent on r8lib is questionable..."
+ cmd.append(
+ '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
+ if args.ea:
+ cmd.append('-ea')
+ cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
+ if compiler == 'd8':
+ cmd.append('com.android.tools.r8.D8')
+ if compiler.startswith('r8'):
+ cmd.append('com.android.tools.r8.utils.CompileDumpCompatR8')
+ if compiler == 'r8':
+ cmd.append('--compat')
+ cmd.append(dump.program_jar())
+ cmd.extend(['--output', out])
+ if dump.library_jar():
+ cmd.extend(['--lib', dump.library_jar()])
+ if dump.classpath_jar():
+ cmd.extend(['--classpath', dump.classpath_jar()])
+ if compiler != 'd8' and dump.config_file():
+ cmd.extend(['--pg-conf', dump.config_file()])
+ cmd.extend(otherargs)
+ utils.PrintCmd(cmd)
+ try:
+ print subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ return 0
+ except subprocess.CalledProcessError, e:
+ print e.output
+ if not args.nolib:
+ stacktrace = os.path.join(temp, 'stacktrace')
+ open(stacktrace, 'w+').write(e.output)
+ local_map = utils.R8LIB_MAP if version == 'master' else None
+ hash_or_version = None if version == 'master' else version
+ print "=" * 80
+ print " RETRACED OUTPUT"
+ print "=" * 80
+ retrace.run(local_map, hash_or_version, stacktrace, is_hash(version))
+ return 1
+
+if __name__ == '__main__':
+ (args, otherargs) = make_parser().parse_known_args(sys.argv[1:])
+ sys.exit(run(args, otherargs))
diff --git a/tools/retrace.py b/tools/retrace.py
index e8fdf89..771f23d 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -53,16 +53,18 @@
def main():
args = parse_arguments()
- r8lib_map_path = args.map
if args.tag:
hash_or_version = find_version_or_hash_from_tag(args.tag)
else:
hash_or_version = args.commit_hash or args.version
+ return run(args.map, hash_or_version, args.stacktrace, args.commit_hash is not None)
+
+def run(r8lib_map_path, hash_or_version, stacktrace, is_hash):
if hash_or_version:
download_path = archive.GetUploadDestination(
hash_or_version,
'r8lib.jar.map',
- args.commit_hash is not None)
+ is_hash)
if utils.file_exists_on_cloud_storage(download_path):
r8lib_map_path = tempfile.NamedTemporaryFile().name
utils.download_file_from_cloud_storage(download_path, r8lib_map_path)
@@ -78,8 +80,8 @@
r8lib_map_path
]
- if args.stacktrace:
- retrace_args.append(args.stacktrace)
+ if stacktrace:
+ retrace_args.append(stacktrace)
return subprocess.call(retrace_args)
diff --git a/tools/utils.py b/tools/utils.py
index 6690ba2..16a0696 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -46,6 +46,7 @@
D8_JAR = os.path.join(LIBS, 'd8.jar')
R8_JAR = os.path.join(LIBS, 'r8.jar')
R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')
+R8LIB_MAP = os.path.join(LIBS, 'r8lib.jar.map')
R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')