blob: 67ead8c1d6316c0ecbd184e62169c74c9e4d6578 [file] [log] [blame]
Ian Zernydcb172e2022-02-22 15:36:45 +01001#!/usr/bin/env python3
Rico Wind0ed24cc2018-03-23 07:16:35 +01002# 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# Script for building sample apks using the sdk tools directly.
7
8import apk_utils
9import fnmatch
10import glob
Ian Zerny3f54e222019-02-12 10:51:17 +010011import jdk
Rico Wind0ed24cc2018-03-23 07:16:35 +010012import optparse
13import os
14import shutil
15import subprocess
16import sys
Rico Windb1e257e2018-06-28 14:17:09 +020017import time
Rico Wind0ed24cc2018-03-23 07:16:35 +010018import utils
Rico Windb1e257e2018-06-28 14:17:09 +020019import uuid
Rico Wind0ed24cc2018-03-23 07:16:35 +010020
Rico Wind9d70f612018-08-31 09:17:43 +020021
Rico Wind0ed24cc2018-03-23 07:16:35 +010022DEFAULT_AAPT = 'aapt' # Assume in path.
Rico Wind33a57cc2020-05-05 09:06:09 +020023DEFAULT_AAPT2 = 'aapt2' # Assume in path.
Rico Wind0ed24cc2018-03-23 07:16:35 +010024DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
Rico Winda5db71d82018-06-27 13:45:05 +020025DEFAULT_DEXSPLITTER = os.path.join(utils.REPO_ROOT, 'tools', 'dexsplitter.py')
Ian Zerny3f54e222019-02-12 10:51:17 +010026DEFAULT_JAVAC = jdk.GetJavacExecutable()
Rico Wind0ed24cc2018-03-23 07:16:35 +010027SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
28DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
Rico Windb1e257e2018-06-28 14:17:09 +020029PACKAGE_PREFIX = 'com.android.tools.r8.sample'
30STANDARD_ACTIVITY = "R8Activity"
Rico Wind097e3eb2018-07-05 15:14:10 +020031BENCHMARK_ITERATIONS = 30
Rico Wind0ed24cc2018-03-23 07:16:35 +010032
33SAMPLE_APKS = [
Rico Winda5db71d82018-06-27 13:45:05 +020034 'simple',
35 'split'
Rico Wind0ed24cc2018-03-23 07:16:35 +010036]
37
38def parse_options():
39 result = optparse.OptionParser()
40 result.add_option('--aapt',
41 help='aapt executable to use',
42 default=DEFAULT_AAPT)
Rico Wind33a57cc2020-05-05 09:06:09 +020043 result.add_option('--aapt2',
44 help='aapt2 executable to use',
45 default=DEFAULT_AAPT2)
Rico Wind0ed24cc2018-03-23 07:16:35 +010046 result.add_option('--api',
47 help='Android api level',
48 default=21,
Rico Windfecaf022020-01-28 12:28:31 +010049 choices=['14', '15', '19', '21', '22', '23', '24', '25',
50 '26'])
Rico Wind0ed24cc2018-03-23 07:16:35 +010051 result.add_option('--keystore',
52 help='Keystore used for signing',
53 default=DEFAULT_KEYSTORE)
Rico Winda5db71d82018-06-27 13:45:05 +020054 result.add_option('--split',
55 help='Split the app using the split.spec file',
56 default=False, action='store_true')
Rico Wind33a57cc2020-05-05 09:06:09 +020057 result.add_option('--generate-proto-apk',
58 help='Use aapt2 to generate the proto version of the apk.',
59 default=False, action='store_true')
Rico Windd0cd1a52018-06-27 15:26:22 +020060 result.add_option('--install',
61 help='Install the app (including featuresplit)',
62 default=False, action='store_true')
Rico Windb1e257e2018-06-28 14:17:09 +020063 result.add_option('--benchmark',
64 help='Benchmark the app on the phone with specialized markers',
65 default=False, action='store_true')
66 result.add_option('--benchmark-output-dir',
67 help='Store benchmark results here.',
68 default=None)
Rico Wind0ed24cc2018-03-23 07:16:35 +010069 result.add_option('--app',
70 help='Which app to build',
71 default='simple',
72 choices=SAMPLE_APKS)
73 return result.parse_args()
74
75def run_aapt(aapt, args):
76 command = [aapt]
77 command.extend(args)
78 utils.PrintCmd(command)
79 subprocess.check_call(command)
80
81def get_build_dir(app):
82 return os.path.join(utils.BUILD, 'sampleApks', app)
83
84def get_gen_path(app):
85 gen_path = os.path.join(get_build_dir(app), 'gen')
86 utils.makedirs_if_needed(gen_path)
87 return gen_path
88
89def get_bin_path(app):
90 bin_path = os.path.join(get_build_dir(app), 'bin')
91 utils.makedirs_if_needed(bin_path)
92 return bin_path
93
Rico Wind0ed24cc2018-03-23 07:16:35 +010094
Rico Windf3958632018-08-07 09:39:19 +020095def get_guava_jar():
96 return os.path.join(utils.REPO_ROOT,
97 'third_party/gradle-plugin/com/google/guava/guava/22.0/guava-22.0.jar')
98
Rico Wind0ed24cc2018-03-23 07:16:35 +010099def get_sample_dir(app):
100 return os.path.join(utils.REPO_ROOT, 'src', 'test', 'sampleApks', app)
101
102def get_src_path(app):
103 return os.path.join(get_sample_dir(app), 'src')
104
Rico Winda5db71d82018-06-27 13:45:05 +0200105def get_dex_path(app):
106 return os.path.join(get_bin_path(app), 'classes.dex')
107
108def get_split_path(app, split):
109 return os.path.join(get_bin_path(app), split, 'classes.dex')
110
Rico Windb1e257e2018-06-28 14:17:09 +0200111def get_package_name(app):
112 return '%s.%s' % (PACKAGE_PREFIX, app)
113
114def get_qualified_activity(app):
115 # The activity specified to adb start is PACKAGE_NAME/.ACTIVITY
116 return '%s/.%s' % (get_package_name(app), STANDARD_ACTIVITY)
117
Rico Wind0ed24cc2018-03-23 07:16:35 +0100118def run_aapt_pack(aapt, api, app):
119 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
120 args = ['package',
121 '-v', '-f',
Rico Wind9d70f612018-08-31 09:17:43 +0200122 '-I', utils.get_android_jar(api),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100123 '-M', 'AndroidManifest.xml',
124 '-A', 'assets',
125 '-S', 'res',
126 '-m',
127 '-J', get_gen_path(app),
Søren Gjesse12c52092018-04-05 14:22:29 +0200128 '-F', os.path.join(get_bin_path(app), 'resources.ap_'),
129 '-G', os.path.join(get_build_dir(app), 'proguard_options')]
Rico Wind0ed24cc2018-03-23 07:16:35 +0100130 run_aapt(aapt, args)
131
Rico Winda5db71d82018-06-27 13:45:05 +0200132def run_aapt_split_pack(aapt, api, app):
133 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
134 args = ['package',
135 '-v', '-f',
Rico Wind9d70f612018-08-31 09:17:43 +0200136 '-I', utils.get_android_jar(api),
Rico Winda5db71d82018-06-27 13:45:05 +0200137 '-M', 'split_manifest/AndroidManifest.xml',
138 '-S', 'res',
139 '-F', os.path.join(get_bin_path(app), 'split_resources.ap_')]
140 run_aapt(aapt, args)
141
Rico Wind0ed24cc2018-03-23 07:16:35 +0100142def compile_with_javac(api, app):
143 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
144 files = glob.glob(SRC_LOCATION.format(app=app))
Rico Wind9d70f612018-08-31 09:17:43 +0200145 classpath = '%s:%s' % (utils.get_android_jar(api), get_guava_jar())
Rico Wind0ed24cc2018-03-23 07:16:35 +0100146 command = [DEFAULT_JAVAC,
Rico Wind9d70f612018-08-31 09:17:43 +0200147 '-classpath', classpath,
Rico Windf3958632018-08-07 09:39:19 +0200148 '-sourcepath', '%s:%s:%s' % (
149 get_src_path(app),
150 get_gen_path(app),
151 get_guava_jar()),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100152 '-d', get_bin_path(app)]
153 command.extend(files)
154 utils.PrintCmd(command)
155 subprocess.check_call(command)
156
157def dex(app, api):
158 files = []
159 for root, dirnames, filenames in os.walk(get_bin_path(app)):
160 for filename in fnmatch.filter(filenames, '*.class'):
161 files.append(os.path.join(root, filename))
Rico Windc2704742021-12-16 13:29:55 +0100162 command = [DEFAULT_D8, '--',
Rico Wind0ed24cc2018-03-23 07:16:35 +0100163 '--output', get_bin_path(app),
Rico Wind9d70f612018-08-31 09:17:43 +0200164 '--classpath', utils.get_android_jar(api),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100165 '--min-api', str(api)]
166 command.extend(files)
Rico Windfecaf022020-01-28 12:28:31 +0100167 if app != 'simple':
168 command.append(get_guava_jar())
Rico Windf3958632018-08-07 09:39:19 +0200169
Rico Wind0ed24cc2018-03-23 07:16:35 +0100170 utils.PrintCmd(command)
171 subprocess.check_call(command)
172
Rico Winda5db71d82018-06-27 13:45:05 +0200173def split(app):
174 split_spec = os.path.join(get_sample_dir(app), 'split.spec')
175 command = [DEFAULT_DEXSPLITTER,
176 '--input', get_dex_path(app),
177 '--output', get_bin_path(app),
178 '--feature-splits', split_spec]
179 utils.PrintCmd(command)
180 subprocess.check_call(command)
181
Rico Wind097e3eb2018-07-05 15:14:10 +0200182def run_adb(args, ignore_exit=False):
Rico Windd0cd1a52018-06-27 15:26:22 +0200183 command = ['adb']
184 command.extend(args)
185 utils.PrintCmd(command)
Rico Winde01a5052018-07-06 07:28:32 +0200186 # On M adb install-multiple exits 1 but succeed in installing.
Rico Wind097e3eb2018-07-05 15:14:10 +0200187 if ignore_exit:
188 subprocess.call(command)
189 else:
190 subprocess.check_call(command)
Rico Windd0cd1a52018-06-27 15:26:22 +0200191
192def adb_install(apks):
193 args = [
194 'install-multiple' if len(apks) > 1 else 'install',
195 '-r',
196 '-d']
197 args.extend(apks)
Rico Wind097e3eb2018-07-05 15:14:10 +0200198 run_adb(args, ignore_exit=True)
Rico Windd0cd1a52018-06-27 15:26:22 +0200199
Rico Winda5db71d82018-06-27 13:45:05 +0200200def create_temp_apk(app, prefix):
Rico Wind0ed24cc2018-03-23 07:16:35 +0100201 temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
Rico Winda5db71d82018-06-27 13:45:05 +0200202 shutil.copyfile(os.path.join(get_bin_path(app), '%sresources.ap_' % prefix),
203 temp_apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100204 return temp_apk_path
205
Rico Winda5db71d82018-06-27 13:45:05 +0200206def aapt_add_dex(aapt, dex, temp_apk_path):
Rico Wind0ed24cc2018-03-23 07:16:35 +0100207 args = ['add',
208 '-k', temp_apk_path,
Rico Winda5db71d82018-06-27 13:45:05 +0200209 dex]
Rico Wind0ed24cc2018-03-23 07:16:35 +0100210 run_aapt(aapt, args)
211
Rico Windb1e257e2018-06-28 14:17:09 +0200212def kill(app):
213 args = ['shell', 'am', 'force-stop', get_package_name(app)]
214 run_adb(args)
215
216def start_logcat():
Rico Wind097e3eb2018-07-05 15:14:10 +0200217 return subprocess.Popen(['adb', 'logcat'], bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Rico Windb1e257e2018-06-28 14:17:09 +0200218
219def start(app):
220 args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
221 run_adb(args)
222
223def clear_logcat():
224 args = ['logcat', '-c']
225 run_adb(args)
226
227def stop_logcat(popen):
228 popen.terminate()
229 lines = []
230 for l in popen.stdout:
231 if 'System.out' in l:
232 lines.append(l)
233 return lines
234
235def store_or_print_benchmarks(lines, output):
Rico Wind097e3eb2018-07-05 15:14:10 +0200236 results = {}
237 overall_total = 0
238 # We assume that the total times are
239 # prefixed with 'NAME Total: '. The logcat lines looks like:
240 # 06-28 12:22:00.991 13698 13698 I System.out: Call Total: 61614
Rico Windb1e257e2018-06-28 14:17:09 +0200241 for l in lines:
Rico Windb1e257e2018-06-28 14:17:09 +0200242 if 'Total: ' in l:
Rico Wind097e3eb2018-07-05 15:14:10 +0200243 split = l.split('Total: ')
244 time = split[1]
245 name = split[0].split()[-1]
246 overall_total += int(time)
247 print '%s: %s' % (name, time)
248 results[name] = time
Rico Windb1e257e2018-06-28 14:17:09 +0200249
Rico Wind097e3eb2018-07-05 15:14:10 +0200250 print 'Total: %s' % overall_total
251 if not output:
252 return overall_total
253 results['total'] = str(overall_total)
Rico Windb1e257e2018-06-28 14:17:09 +0200254 output_dir = os.path.join(output, str(uuid.uuid4()))
255 os.makedirs(output_dir)
Rico Wind097e3eb2018-07-05 15:14:10 +0200256 written_files = []
257 for name, time in results.iteritems():
258 total_file = os.path.join(output_dir, name)
259 written_files.append(total_file)
260 with open(total_file, 'w') as f:
261 f.write(time)
262
263 print 'Result stored in: \n%s' % ('\n'.join(written_files))
264 return overall_total
Rico Windb1e257e2018-06-28 14:17:09 +0200265
266def benchmark(app, output_dir):
267 # Ensure app is not running
268 kill(app)
269 clear_logcat()
270 logcat = start_logcat()
271 start(app)
272 # We could do better here by continiously parsing the logcat for a marker, but
273 # this works nicely with the current setup.
Rico Windf3958632018-08-07 09:39:19 +0200274 time.sleep(12)
Rico Windb1e257e2018-06-28 14:17:09 +0200275 kill(app)
Rico Wind097e3eb2018-07-05 15:14:10 +0200276 return float(store_or_print_benchmarks(stop_logcat(logcat), output_dir))
277
278def ensure_no_logcat():
279 output = subprocess.check_output(['ps', 'aux'])
280 if 'adb logcat' in output:
281 raise Exception('You have adb logcat running, please close it and rerun')
Rico Windb1e257e2018-06-28 14:17:09 +0200282
Rico Wind33a57cc2020-05-05 09:06:09 +0200283def generate_proto_apks(apks, options):
284 proto_apks = []
285 for apk in apks:
286 proto_apk = apk + '.proto'
287 cmd = [options.aapt2, 'convert',
288 '-o', proto_apk,
289 '--output-format', 'proto',
290 apk]
291 utils.PrintCmd(cmd)
292 subprocess.check_call(cmd)
293 proto_apks.append(proto_apk)
294 return proto_apks
295
Rico Wind0ed24cc2018-03-23 07:16:35 +0100296def Main():
297 (options, args) = parse_options()
Rico Windd0cd1a52018-06-27 15:26:22 +0200298 apks = []
Rico Winda5db71d82018-06-27 13:45:05 +0200299 is_split = options.split
Rico Wind0ed24cc2018-03-23 07:16:35 +0100300 run_aapt_pack(options.aapt, options.api, options.app)
Rico Winda5db71d82018-06-27 13:45:05 +0200301 if is_split:
302 run_aapt_split_pack(options.aapt, options.api, options.app)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100303 compile_with_javac(options.api, options.app)
304 dex(options.app, options.api)
Rico Winda5db71d82018-06-27 13:45:05 +0200305 dex_files = { options.app: get_dex_path(options.app)}
306 dex_path = get_dex_path(options.app)
307 if is_split:
308 split(options.app)
309 dex_path = get_split_path(options.app, 'base')
Rico Winda5db71d82018-06-27 13:45:05 +0200310 temp_apk_path = create_temp_apk(options.app, '')
311 aapt_add_dex(options.aapt, dex_path, temp_apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100312 apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
313 apk_utils.sign(temp_apk_path, apk_path, options.keystore)
Rico Windd0cd1a52018-06-27 15:26:22 +0200314 apks.append(apk_path)
Rico Windd0cd1a52018-06-27 15:26:22 +0200315 if is_split:
Rico Winda5db71d82018-06-27 13:45:05 +0200316 split_temp_apk_path = create_temp_apk(options.app, 'split_')
317 aapt_add_dex(options.aapt,
318 get_split_path(options.app, 'split'),
319 temp_apk_path)
320 split_apk_path = os.path.join(get_bin_path(options.app), 'featuresplit.apk')
321 apk_utils.sign(temp_apk_path, split_apk_path, options.keystore)
Rico Windd0cd1a52018-06-27 15:26:22 +0200322 apks.append(split_apk_path)
Rico Wind33a57cc2020-05-05 09:06:09 +0200323 if options.generate_proto_apk:
324 proto_apks = generate_proto_apks(apks, options)
325 print('Generated proto apks available at: %s' % ' '.join(proto_apks))
Rico Windd0cd1a52018-06-27 15:26:22 +0200326 print('Generated apks available at: %s' % ' '.join(apks))
Rico Winde01a5052018-07-06 07:28:32 +0200327 if options.install or options.benchmark:
Rico Windd0cd1a52018-06-27 15:26:22 +0200328 adb_install(apks)
Rico Wind097e3eb2018-07-05 15:14:10 +0200329 grand_total = 0
Rico Windb1e257e2018-06-28 14:17:09 +0200330 if options.benchmark:
Rico Wind097e3eb2018-07-05 15:14:10 +0200331 ensure_no_logcat()
Rico Windb1e257e2018-06-28 14:17:09 +0200332 for _ in range(BENCHMARK_ITERATIONS):
Rico Wind097e3eb2018-07-05 15:14:10 +0200333 grand_total += benchmark(options.app, options.benchmark_output_dir)
334 print 'Combined average: %s' % (grand_total/BENCHMARK_ITERATIONS)
Rico Winda5db71d82018-06-27 13:45:05 +0200335
Rico Wind0ed24cc2018-03-23 07:16:35 +0100336if __name__ == '__main__':
337 sys.exit(Main())