Refactor minify-tool.py and add run_bootstrap_benchmark.py

Rename to minify_tool.py so it can be imported in
run_bootstrap_benchmark.py.

Rename minify_tool.main() -> minify_tool.minify_tool(), and take
function parameters instead of using parser.parse_args().

Add non-None default values to the argument parser to make it possible
to keyword-argument unpacking `**vars(parser.parse_args())` for the main
invocation.

Add `debug` and `build` parameters, default True, that are set to False
in run_bootstrap_benchmark.py.

Change-Id: I61d70ae37b9ac433e13b107cc5c9d29225a7740c
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
new file mode 100755
index 0000000..08b2ada
--- /dev/null
+++ b/tools/minify_tool.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# 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.
+
+'''
+Run R8 (with the class-file backend) to optimize a command-line program.
+
+Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR
+with the given main-class in the manifest along with a -keep rule for keeping
+just the main entrypoint, and runs R8 in release+classfile mode on the JAR.
+'''
+
+import argparse
+import os
+import re
+import sys
+import time
+import toolhelper
+import utils
+import zipfile
+
+KEEP = '-keep public class %s { public static void main(...); }\n'
+MANIFEST_PATH = 'META-INF/MANIFEST.MF'
+MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
+MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
+RT = os.path.join(utils.REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+                                 formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument(
+    '-i', '--input-jar', default=utils.R8_JAR,
+    help='Input JAR to use (default: build/libs/r8.jar)')
+parser.add_argument(
+    '-o', '--output-jar',
+    help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
+parser.add_argument(
+    '-l', '--lib', default=RT,
+    help='Path to rt.jar to use instead of OpenJDK 1.8')
+parser.add_argument(
+    '-m', '--mainclass',
+    help='Create/overwrite MANIFEST.MF with the given Main-Class')
+parser.add_argument(
+    '-O', '--no-debug', dest='debug', action='store_false',
+    help='Disable assertions when running R8')
+parser.add_argument(
+    '--print-runtimeraw', metavar='BENCHMARKNAME',
+    help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+def generate_output_name(input_jar, mainclass):
+  if not mainclass:
+    input_base, input_ext = os.path.splitext(input_jar)
+    return '%s-min%s' % (input_base, input_ext)
+  base = mainclass[mainclass.rindex('.')+1:] if '.' in mainclass else mainclass
+  return os.path.join(utils.LIBS, '%s-min.jar' % base)
+
+def repackage(input_jar, output_jar, mainclass):
+  print("Repackaging %s to %s with Main-Class: %s..." %
+        (input_jar, output_jar, mainclass))
+  manifest = MANIFEST % mainclass
+  with zipfile.ZipFile(input_jar, 'r') as input_zf:
+    with zipfile.ZipFile(output_jar, 'w') as output_zf:
+      for zipinfo in input_zf.infolist():
+        if zipinfo.filename.upper() == MANIFEST_PATH:
+          assert manifest is not None
+          output_zf.writestr(MANIFEST_PATH, manifest)
+          manifest = None
+        else:
+          output_zf.writestr(zipinfo, input_zf.read(zipinfo))
+      if manifest is not None:
+        output_zf.writestr(MANIFEST_PATH, manifest)
+
+def extract_mainclass(input_jar):
+  with zipfile.ZipFile(input_jar, 'r') as input_zf:
+    try:
+      manifest = input_zf.getinfo(MANIFEST_PATH)
+    except KeyError:
+      raise SystemExit('No --mainclass specified and no manifest in input JAR.')
+    mo = re.search(MANIFEST_PATTERN, input_zf.read(manifest))
+    if not mo:
+      raise SystemExit(
+          'No --mainclass specified and no Main-Class in input JAR manifest.')
+    return mo.group(1)
+
+def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None, lib=RT,
+                debug=True, build=True, print_runtimeraw=None):
+  if output_jar is None:
+    output_jar = generate_output_name(input_jar, mainclass)
+  with utils.TempDir() as path:
+    if mainclass:
+      tmp_input_path = os.path.join(path, 'input.jar')
+      repackage(input_jar, tmp_input_path, mainclass)
+    else:
+      tmp_input_path = input_jar
+      mainclass = extract_mainclass(input_jar)
+    keep_path = os.path.join(path, 'keep.txt')
+    with open(keep_path, 'w') as fp:
+      fp.write(KEEP % mainclass)
+    args = ('--lib', lib,
+            '--classfile',
+            '--output', output_jar,
+            '--pg-conf', keep_path,
+            '--release',
+            tmp_input_path)
+    start_time = time.time()
+    return_code = toolhelper.run('r8', args, debug=debug, build=build)
+    if print_runtimeraw:
+      elapsed_ms = 1000 * (time.time() - start_time)
+      print('%s-Total(RunTimeRaw): %s ms' % (print_runtimeraw, elapsed_ms))
+    return return_code
+
+if __name__ == '__main__':
+  sys.exit(minify_tool(**vars(parser.parse_args())))