| #!/usr/bin/env python3 |
| # 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. |
| |
| If --benchmark-name NAME is given, prints "<NAME>(RunTimeRaw): <elapsed> ms" |
| to standard output at the end of the run. |
| ''' |
| |
| import argparse |
| import os |
| import re |
| import sys |
| import time |
| import zipfile |
| |
| import toolhelper |
| import utils |
| |
| 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+)' |
| |
| 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=utils.RT_JAR, |
| 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( |
| '--benchmark-name', |
| help='Print benchmarks with the given name') |
| |
| 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=utils.RT_JAR, debug=True, build=True, benchmark_name=None, |
| track_memory_file=None, additional_args=[], java_args=[]): |
| 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] + additional_args |
| start_time = time.time() |
| return_code = toolhelper.run('r8', args, debug=debug, build=build, |
| track_memory_file=track_memory_file, |
| extra_args=java_args) |
| if benchmark_name: |
| elapsed_ms = 1000 * (time.time() - start_time) |
| print('%s(RunTimeRaw): %s ms' % (benchmark_name, elapsed_ms)) |
| if track_memory_file: |
| print('%s(MemoryUse): %s' % |
| (benchmark_name, utils.grep_memoryuse(track_memory_file))) |
| |
| return return_code |
| |
| if __name__ == '__main__': |
| sys.exit(minify_tool(**vars(parser.parse_args()))) |