blob: 2356ac1dc9b1039161c4a7cc2e773292822ef5de [file] [log] [blame]
Ian Zernydcb172e2022-02-22 15:36:45 +01001#!/usr/bin/env python3
Mathias Ravbfcf7282018-05-22 09:50:18 +02002# 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.
Mathias Ravbfcf7282018-05-22 09:50:18 +02005'''
6Run R8 (with the class-file backend) to optimize a command-line program.
7
8Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR
9with the given main-class in the manifest along with a -keep rule for keeping
10just the main entrypoint, and runs R8 in release+classfile mode on the JAR.
Mathias Ravd3499eb2018-05-23 10:55:34 +020011
12If --benchmark-name NAME is given, prints "<NAME>(RunTimeRaw): <elapsed> ms"
13to standard output at the end of the run.
Mathias Ravbfcf7282018-05-22 09:50:18 +020014'''
15
16import argparse
17import os
18import re
19import sys
Mathias Rav92f33402018-05-22 14:26:45 +020020import time
Ian Zernycded6802018-06-15 13:30:32 +020021import zipfile
22
Mathias Ravbfcf7282018-05-22 09:50:18 +020023import toolhelper
24import utils
Mathias Ravbfcf7282018-05-22 09:50:18 +020025
26KEEP = '-keep public class %s { public static void main(...); }\n'
27MANIFEST_PATH = 'META-INF/MANIFEST.MF'
28MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
29MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
Mathias Ravbfcf7282018-05-22 09:50:18 +020030
31parser = argparse.ArgumentParser(description=__doc__.strip(),
32 formatter_class=argparse.RawTextHelpFormatter)
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020033parser.add_argument('-i',
34 '--input-jar',
35 default=utils.R8_JAR,
36 help='Input JAR to use (default: build/libs/r8.jar)')
Mathias Ravbfcf7282018-05-22 09:50:18 +020037parser.add_argument(
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020038 '-o',
39 '--output-jar',
Mathias Ravbfcf7282018-05-22 09:50:18 +020040 help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020041parser.add_argument('-l',
42 '--lib',
43 default=utils.RT_JAR,
44 help='Path to rt.jar to use instead of OpenJDK 1.8')
Mathias Ravbfcf7282018-05-22 09:50:18 +020045parser.add_argument(
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020046 '-m',
47 '--mainclass',
Mathias Ravbfcf7282018-05-22 09:50:18 +020048 help='Create/overwrite MANIFEST.MF with the given Main-Class')
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020049parser.add_argument('-O',
50 '--no-debug',
51 dest='debug',
52 action='store_false',
53 help='Disable assertions when running R8')
54parser.add_argument('--benchmark-name',
55 help='Print benchmarks with the given name')
56
Mathias Ravbfcf7282018-05-22 09:50:18 +020057
58def generate_output_name(input_jar, mainclass):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020059 if not mainclass:
60 input_base, input_ext = os.path.splitext(input_jar)
61 return '%s-min%s' % (input_base, input_ext)
62 base = mainclass[mainclass.rindex('.') +
63 1:] if '.' in mainclass else mainclass
64 return os.path.join(utils.LIBS, '%s-min.jar' % base)
65
Mathias Ravbfcf7282018-05-22 09:50:18 +020066
67def repackage(input_jar, output_jar, mainclass):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020068 print("Repackaging %s to %s with Main-Class: %s..." %
69 (input_jar, output_jar, mainclass))
70 manifest = MANIFEST % mainclass
71 with zipfile.ZipFile(input_jar, 'r') as input_zf:
72 with zipfile.ZipFile(output_jar, 'w') as output_zf:
73 for zipinfo in input_zf.infolist():
74 if zipinfo.filename.upper() == MANIFEST_PATH:
75 assert manifest is not None
76 output_zf.writestr(MANIFEST_PATH, manifest)
77 manifest = None
78 else:
79 output_zf.writestr(zipinfo, input_zf.read(zipinfo))
80 if manifest is not None:
81 output_zf.writestr(MANIFEST_PATH, manifest)
82
Mathias Ravbfcf7282018-05-22 09:50:18 +020083
84def extract_mainclass(input_jar):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020085 with zipfile.ZipFile(input_jar, 'r') as input_zf:
86 try:
87 manifest = input_zf.getinfo(MANIFEST_PATH)
88 except KeyError:
89 raise SystemExit(
90 'No --mainclass specified and no manifest in input JAR.')
91 mo = re.search(MANIFEST_PATTERN, input_zf.read(manifest))
92 if not mo:
93 raise SystemExit(
94 'No --mainclass specified and no Main-Class in input JAR manifest.'
95 )
96 return mo.group(1)
Mathias Ravbfcf7282018-05-22 09:50:18 +020097
Ian Zernycded6802018-06-15 13:30:32 +020098
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020099def minify_tool(mainclass=None,
100 input_jar=utils.R8_JAR,
101 output_jar=None,
102 lib=utils.RT_JAR,
103 debug=True,
104 build=True,
105 benchmark_name=None,
106 track_memory_file=None,
107 additional_args=[],
108 java_args=[]):
109 if output_jar is None:
110 output_jar = generate_output_name(input_jar, mainclass)
111 with utils.TempDir() as path:
112 if mainclass:
113 tmp_input_path = os.path.join(path, 'input.jar')
114 repackage(input_jar, tmp_input_path, mainclass)
115 else:
116 tmp_input_path = input_jar
117 mainclass = extract_mainclass(input_jar)
118 keep_path = os.path.join(path, 'keep.txt')
119 with open(keep_path, 'w') as fp:
120 fp.write(KEEP % mainclass)
121 args = [
122 '--lib', lib, '--classfile', '--output', output_jar, '--pg-conf',
123 keep_path, '--release', tmp_input_path
124 ] + additional_args
125 start_time = time.time()
126 return_code = toolhelper.run('r8',
127 args,
128 debug=debug,
129 build=build,
130 track_memory_file=track_memory_file,
131 extra_args=java_args)
132 if benchmark_name:
133 elapsed_ms = 1000 * (time.time() - start_time)
134 print('%s(RunTimeRaw): %s ms' % (benchmark_name, elapsed_ms))
135 if track_memory_file:
136 print('%s(MemoryUse): %s' %
137 (benchmark_name, utils.grep_memoryuse(track_memory_file)))
138
139 return return_code
140
Mathias Ravbfcf7282018-05-22 09:50:18 +0200141
142if __name__ == '__main__':
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200143 sys.exit(minify_tool(**vars(parser.parse_args())))