Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | ''' |
| 7 | Run R8 (with the class-file backend) to optimize a command-line program. |
| 8 | |
| 9 | Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR |
| 10 | with the given main-class in the manifest along with a -keep rule for keeping |
| 11 | just the main entrypoint, and runs R8 in release+classfile mode on the JAR. |
Mathias Rav | d3499eb | 2018-05-23 10:55:34 +0200 | [diff] [blame] | 12 | |
| 13 | If --benchmark-name NAME is given, prints "<NAME>(RunTimeRaw): <elapsed> ms" |
| 14 | to standard output at the end of the run. |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 15 | ''' |
| 16 | |
| 17 | import argparse |
| 18 | import os |
| 19 | import re |
| 20 | import sys |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 21 | import time |
Ian Zerny | cded680 | 2018-06-15 13:30:32 +0200 | [diff] [blame] | 22 | import zipfile |
| 23 | |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 24 | import toolhelper |
| 25 | import utils |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 26 | |
| 27 | KEEP = '-keep public class %s { public static void main(...); }\n' |
| 28 | MANIFEST_PATH = 'META-INF/MANIFEST.MF' |
| 29 | MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n' |
| 30 | MANIFEST_PATTERN = r'Main-Class:\s*(\S+)' |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 31 | |
| 32 | parser = argparse.ArgumentParser(description=__doc__.strip(), |
| 33 | formatter_class=argparse.RawTextHelpFormatter) |
| 34 | parser.add_argument( |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 35 | '-i', '--input-jar', default=utils.R8_JAR, |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 36 | help='Input JAR to use (default: build/libs/r8.jar)') |
| 37 | parser.add_argument( |
| 38 | '-o', '--output-jar', |
| 39 | help='Path to output JAR (default: build/libs/<MainClass>-min.jar)') |
| 40 | parser.add_argument( |
Mathias Rav | 3fb4a3a | 2018-05-29 15:41:36 +0200 | [diff] [blame] | 41 | '-l', '--lib', default=utils.RT_JAR, |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 42 | help='Path to rt.jar to use instead of OpenJDK 1.8') |
| 43 | parser.add_argument( |
| 44 | '-m', '--mainclass', |
| 45 | help='Create/overwrite MANIFEST.MF with the given Main-Class') |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 46 | parser.add_argument( |
| 47 | '-O', '--no-debug', dest='debug', action='store_false', |
| 48 | help='Disable assertions when running R8') |
| 49 | parser.add_argument( |
Mathias Rav | d3499eb | 2018-05-23 10:55:34 +0200 | [diff] [blame] | 50 | '--benchmark-name', |
| 51 | help='Print benchmarks with the given name') |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 52 | |
| 53 | def generate_output_name(input_jar, mainclass): |
| 54 | if not mainclass: |
| 55 | input_base, input_ext = os.path.splitext(input_jar) |
| 56 | return '%s-min%s' % (input_base, input_ext) |
| 57 | base = mainclass[mainclass.rindex('.')+1:] if '.' in mainclass else mainclass |
| 58 | return os.path.join(utils.LIBS, '%s-min.jar' % base) |
| 59 | |
| 60 | def repackage(input_jar, output_jar, mainclass): |
| 61 | print("Repackaging %s to %s with Main-Class: %s..." % |
| 62 | (input_jar, output_jar, mainclass)) |
| 63 | manifest = MANIFEST % mainclass |
| 64 | with zipfile.ZipFile(input_jar, 'r') as input_zf: |
| 65 | with zipfile.ZipFile(output_jar, 'w') as output_zf: |
| 66 | for zipinfo in input_zf.infolist(): |
| 67 | if zipinfo.filename.upper() == MANIFEST_PATH: |
| 68 | assert manifest is not None |
| 69 | output_zf.writestr(MANIFEST_PATH, manifest) |
| 70 | manifest = None |
| 71 | else: |
| 72 | output_zf.writestr(zipinfo, input_zf.read(zipinfo)) |
| 73 | if manifest is not None: |
| 74 | output_zf.writestr(MANIFEST_PATH, manifest) |
| 75 | |
| 76 | def extract_mainclass(input_jar): |
| 77 | with zipfile.ZipFile(input_jar, 'r') as input_zf: |
| 78 | try: |
| 79 | manifest = input_zf.getinfo(MANIFEST_PATH) |
| 80 | except KeyError: |
| 81 | raise SystemExit('No --mainclass specified and no manifest in input JAR.') |
| 82 | mo = re.search(MANIFEST_PATTERN, input_zf.read(manifest)) |
| 83 | if not mo: |
| 84 | raise SystemExit( |
| 85 | 'No --mainclass specified and no Main-Class in input JAR manifest.') |
| 86 | return mo.group(1) |
| 87 | |
Mathias Rav | 3fb4a3a | 2018-05-29 15:41:36 +0200 | [diff] [blame] | 88 | def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None, |
Ian Zerny | cded680 | 2018-06-15 13:30:32 +0200 | [diff] [blame] | 89 | lib=utils.RT_JAR, debug=True, build=True, benchmark_name=None, |
| 90 | track_memory_file=None): |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 91 | if output_jar is None: |
| 92 | output_jar = generate_output_name(input_jar, mainclass) |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 93 | with utils.TempDir() as path: |
| 94 | if mainclass: |
| 95 | tmp_input_path = os.path.join(path, 'input.jar') |
| 96 | repackage(input_jar, tmp_input_path, mainclass) |
| 97 | else: |
| 98 | tmp_input_path = input_jar |
| 99 | mainclass = extract_mainclass(input_jar) |
| 100 | keep_path = os.path.join(path, 'keep.txt') |
| 101 | with open(keep_path, 'w') as fp: |
| 102 | fp.write(KEEP % mainclass) |
| 103 | args = ('--lib', lib, |
| 104 | '--classfile', |
| 105 | '--output', output_jar, |
| 106 | '--pg-conf', keep_path, |
| 107 | '--release', |
| 108 | tmp_input_path) |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 109 | start_time = time.time() |
Ian Zerny | cded680 | 2018-06-15 13:30:32 +0200 | [diff] [blame] | 110 | return_code = toolhelper.run('r8', args, debug=debug, build=build, |
| 111 | track_memory_file=track_memory_file) |
Mathias Rav | d3499eb | 2018-05-23 10:55:34 +0200 | [diff] [blame] | 112 | if benchmark_name: |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 113 | elapsed_ms = 1000 * (time.time() - start_time) |
Mathias Rav | d3499eb | 2018-05-23 10:55:34 +0200 | [diff] [blame] | 114 | print('%s(RunTimeRaw): %s ms' % (benchmark_name, elapsed_ms)) |
Ian Zerny | cded680 | 2018-06-15 13:30:32 +0200 | [diff] [blame] | 115 | if track_memory_file: |
| 116 | print('%s(MemoryUse): %s' % |
| 117 | (benchmark_name, utils.grep_memoryuse(track_memory_file))) |
| 118 | |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 119 | return return_code |
Mathias Rav | bfcf728 | 2018-05-22 09:50:18 +0200 | [diff] [blame] | 120 | |
| 121 | if __name__ == '__main__': |
Mathias Rav | 92f3340 | 2018-05-22 14:26:45 +0200 | [diff] [blame] | 122 | sys.exit(minify_tool(**vars(parser.parse_args()))) |