blob: 8b086e59a368a0d5af9a7f991694d629d9cc7dbc [file] [log] [blame]
Mathias Ravbfcf7282018-05-22 09:50:18 +02001#!/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'''
7Run R8 (with the class-file backend) to optimize a command-line program.
8
9Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR
10with the given main-class in the manifest along with a -keep rule for keeping
11just the main entrypoint, and runs R8 in release+classfile mode on the JAR.
Mathias Ravd3499eb2018-05-23 10:55:34 +020012
13If --benchmark-name NAME is given, prints "<NAME>(RunTimeRaw): <elapsed> ms"
14to standard output at the end of the run.
Mathias Ravbfcf7282018-05-22 09:50:18 +020015'''
16
17import argparse
18import os
19import re
20import sys
Mathias Rav92f33402018-05-22 14:26:45 +020021import time
Ian Zernycded6802018-06-15 13:30:32 +020022import zipfile
23
Mathias Ravbfcf7282018-05-22 09:50:18 +020024import toolhelper
25import utils
Mathias Ravbfcf7282018-05-22 09:50:18 +020026
27KEEP = '-keep public class %s { public static void main(...); }\n'
28MANIFEST_PATH = 'META-INF/MANIFEST.MF'
29MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
30MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
Mathias Ravbfcf7282018-05-22 09:50:18 +020031
32parser = argparse.ArgumentParser(description=__doc__.strip(),
33 formatter_class=argparse.RawTextHelpFormatter)
34parser.add_argument(
Mathias Rav92f33402018-05-22 14:26:45 +020035 '-i', '--input-jar', default=utils.R8_JAR,
Mathias Ravbfcf7282018-05-22 09:50:18 +020036 help='Input JAR to use (default: build/libs/r8.jar)')
37parser.add_argument(
38 '-o', '--output-jar',
39 help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
40parser.add_argument(
Mathias Rav3fb4a3a2018-05-29 15:41:36 +020041 '-l', '--lib', default=utils.RT_JAR,
Mathias Ravbfcf7282018-05-22 09:50:18 +020042 help='Path to rt.jar to use instead of OpenJDK 1.8')
43parser.add_argument(
44 '-m', '--mainclass',
45 help='Create/overwrite MANIFEST.MF with the given Main-Class')
Mathias Rav92f33402018-05-22 14:26:45 +020046parser.add_argument(
47 '-O', '--no-debug', dest='debug', action='store_false',
48 help='Disable assertions when running R8')
49parser.add_argument(
Mathias Ravd3499eb2018-05-23 10:55:34 +020050 '--benchmark-name',
51 help='Print benchmarks with the given name')
Mathias Ravbfcf7282018-05-22 09:50:18 +020052
53def 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
60def 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
76def 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 Rav3fb4a3a2018-05-29 15:41:36 +020088def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None,
Ian Zernycded6802018-06-15 13:30:32 +020089 lib=utils.RT_JAR, debug=True, build=True, benchmark_name=None,
90 track_memory_file=None):
Mathias Rav92f33402018-05-22 14:26:45 +020091 if output_jar is None:
92 output_jar = generate_output_name(input_jar, mainclass)
Mathias Ravbfcf7282018-05-22 09:50:18 +020093 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 Rav92f33402018-05-22 14:26:45 +0200109 start_time = time.time()
Ian Zernycded6802018-06-15 13:30:32 +0200110 return_code = toolhelper.run('r8', args, debug=debug, build=build,
111 track_memory_file=track_memory_file)
Mathias Ravd3499eb2018-05-23 10:55:34 +0200112 if benchmark_name:
Mathias Rav92f33402018-05-22 14:26:45 +0200113 elapsed_ms = 1000 * (time.time() - start_time)
Mathias Ravd3499eb2018-05-23 10:55:34 +0200114 print('%s(RunTimeRaw): %s ms' % (benchmark_name, elapsed_ms))
Ian Zernycded6802018-06-15 13:30:32 +0200115 if track_memory_file:
116 print('%s(MemoryUse): %s' %
117 (benchmark_name, utils.grep_memoryuse(track_memory_file)))
118
Mathias Rav92f33402018-05-22 14:26:45 +0200119 return return_code
Mathias Ravbfcf7282018-05-22 09:50:18 +0200120
121if __name__ == '__main__':
Mathias Rav92f33402018-05-22 14:26:45 +0200122 sys.exit(minify_tool(**vars(parser.parse_args())))