blob: 0d577d4f7974876d156ac6480ece9900a8ece9f0 [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
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020021DEFAULT_AAPT = 'aapt' # Assume in path.
22DEFAULT_AAPT2 = 'aapt2' # Assume in path.
Rico Wind0ed24cc2018-03-23 07:16:35 +010023DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
Rico Winda5db71d82018-06-27 13:45:05 +020024DEFAULT_DEXSPLITTER = os.path.join(utils.REPO_ROOT, 'tools', 'dexsplitter.py')
Ian Zerny3f54e222019-02-12 10:51:17 +010025DEFAULT_JAVAC = jdk.GetJavacExecutable()
Rico Wind0ed24cc2018-03-23 07:16:35 +010026SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
27DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
Rico Windb1e257e2018-06-28 14:17:09 +020028PACKAGE_PREFIX = 'com.android.tools.r8.sample'
29STANDARD_ACTIVITY = "R8Activity"
Rico Wind097e3eb2018-07-05 15:14:10 +020030BENCHMARK_ITERATIONS = 30
Rico Wind0ed24cc2018-03-23 07:16:35 +010031
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020032SAMPLE_APKS = ['simple', 'split']
33
Rico Wind0ed24cc2018-03-23 07:16:35 +010034
35def parse_options():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020036 result = optparse.OptionParser()
37 result.add_option('--aapt',
38 help='aapt executable to use',
39 default=DEFAULT_AAPT)
40 result.add_option('--aapt2',
41 help='aapt2 executable to use',
42 default=DEFAULT_AAPT2)
43 result.add_option(
44 '--api',
45 help='Android api level',
46 default=21,
47 choices=['14', '15', '19', '21', '22', '23', '24', '25', '26'])
48 result.add_option('--keystore',
49 help='Keystore used for signing',
50 default=DEFAULT_KEYSTORE)
51 result.add_option('--split',
52 help='Split the app using the split.spec file',
53 default=False,
54 action='store_true')
55 result.add_option(
56 '--generate-proto-apk',
57 help='Use aapt2 to generate the proto version of the apk.',
58 default=False,
59 action='store_true')
60 result.add_option('--install',
61 help='Install the app (including featuresplit)',
62 default=False,
63 action='store_true')
64 result.add_option(
65 '--benchmark',
66 help='Benchmark the app on the phone with specialized markers',
67 default=False,
68 action='store_true')
69 result.add_option('--benchmark-output-dir',
70 help='Store benchmark results here.',
71 default=None)
72 result.add_option('--app',
73 help='Which app to build',
74 default='simple',
75 choices=SAMPLE_APKS)
76 return result.parse_args()
77
Rico Wind0ed24cc2018-03-23 07:16:35 +010078
79def run_aapt(aapt, args):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020080 command = [aapt]
81 command.extend(args)
Rico Wind0ed24cc2018-03-23 07:16:35 +010082 utils.PrintCmd(command)
83 subprocess.check_call(command)
84
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020085
86def get_build_dir(app):
87 return os.path.join(utils.BUILD, 'sampleApks', app)
88
89
90def get_gen_path(app):
91 gen_path = os.path.join(get_build_dir(app), 'gen')
92 utils.makedirs_if_needed(gen_path)
93 return gen_path
94
95
96def get_bin_path(app):
97 bin_path = os.path.join(get_build_dir(app), 'bin')
98 utils.makedirs_if_needed(bin_path)
99 return bin_path
100
101
102def get_guava_jar():
103 return os.path.join(
104 utils.REPO_ROOT,
105 'third_party/gradle-plugin/com/google/guava/guava/22.0/guava-22.0.jar')
106
107
108def get_sample_dir(app):
109 return os.path.join(utils.REPO_ROOT, 'src', 'test', 'sampleApks', app)
110
111
112def get_src_path(app):
113 return os.path.join(get_sample_dir(app), 'src')
114
115
116def get_dex_path(app):
117 return os.path.join(get_bin_path(app), 'classes.dex')
118
119
120def get_split_path(app, split):
121 return os.path.join(get_bin_path(app), split, 'classes.dex')
122
123
124def get_package_name(app):
125 return '%s.%s' % (PACKAGE_PREFIX, app)
126
127
128def get_qualified_activity(app):
129 # The activity specified to adb start is PACKAGE_NAME/.ACTIVITY
130 return '%s/.%s' % (get_package_name(app), STANDARD_ACTIVITY)
131
132
133def run_aapt_pack(aapt, api, app):
134 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
135 args = [
136 'package', '-v', '-f', '-I',
137 utils.get_android_jar(api), '-M', 'AndroidManifest.xml', '-A',
138 'assets', '-S', 'res', '-m', '-J',
139 get_gen_path(app), '-F',
140 os.path.join(get_bin_path(app), 'resources.ap_'), '-G',
141 os.path.join(get_build_dir(app), 'proguard_options')
142 ]
143 run_aapt(aapt, args)
144
145
146def run_aapt_split_pack(aapt, api, app):
147 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
148 args = [
149 'package', '-v', '-f', '-I',
150 utils.get_android_jar(api), '-M',
151 'split_manifest/AndroidManifest.xml', '-S', 'res', '-F',
152 os.path.join(get_bin_path(app), 'split_resources.ap_')
153 ]
154 run_aapt(aapt, args)
155
156
157def compile_with_javac(api, app):
158 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
159 files = glob.glob(SRC_LOCATION.format(app=app))
160 classpath = '%s:%s' % (utils.get_android_jar(api), get_guava_jar())
161 command = [
162 DEFAULT_JAVAC, '-classpath', classpath, '-sourcepath',
163 '%s:%s:%s' %
164 (get_src_path(app), get_gen_path(app), get_guava_jar()), '-d',
165 get_bin_path(app)
166 ]
167 command.extend(files)
168 utils.PrintCmd(command)
169 subprocess.check_call(command)
170
171
Rico Wind0ed24cc2018-03-23 07:16:35 +0100172def dex(app, api):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200173 files = []
174 for root, dirnames, filenames in os.walk(get_bin_path(app)):
175 for filename in fnmatch.filter(filenames, '*.class'):
176 files.append(os.path.join(root, filename))
177 command = [
178 DEFAULT_D8, '--', '--output',
179 get_bin_path(app), '--classpath',
180 utils.get_android_jar(api), '--min-api',
181 str(api)
182 ]
183 command.extend(files)
184 if app != 'simple':
185 command.append(get_guava_jar())
Rico Windf3958632018-08-07 09:39:19 +0200186
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200187 utils.PrintCmd(command)
Rico Wind097e3eb2018-07-05 15:14:10 +0200188 subprocess.check_call(command)
Rico Windd0cd1a52018-06-27 15:26:22 +0200189
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200190
191def split(app):
192 split_spec = os.path.join(get_sample_dir(app), 'split.spec')
193 command = [
194 DEFAULT_DEXSPLITTER, '--input',
195 get_dex_path(app), '--output',
196 get_bin_path(app), '--feature-splits', split_spec
197 ]
198 utils.PrintCmd(command)
199 subprocess.check_call(command)
200
201
202def run_adb(args, ignore_exit=False):
203 command = ['adb']
204 command.extend(args)
205 utils.PrintCmd(command)
206 # On M adb install-multiple exits 1 but succeed in installing.
207 if ignore_exit:
208 subprocess.call(command)
209 else:
210 subprocess.check_call(command)
211
212
Rico Windd0cd1a52018-06-27 15:26:22 +0200213def adb_install(apks):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200214 args = ['install-multiple' if len(apks) > 1 else 'install', '-r', '-d']
215 args.extend(apks)
216 run_adb(args, ignore_exit=True)
217
Rico Windd0cd1a52018-06-27 15:26:22 +0200218
Rico Winda5db71d82018-06-27 13:45:05 +0200219def create_temp_apk(app, prefix):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200220 temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
221 shutil.copyfile(os.path.join(get_bin_path(app), '%sresources.ap_' % prefix),
222 temp_apk_path)
223 return temp_apk_path
224
Rico Wind0ed24cc2018-03-23 07:16:35 +0100225
Rico Winda5db71d82018-06-27 13:45:05 +0200226def aapt_add_dex(aapt, dex, temp_apk_path):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200227 args = ['add', '-k', temp_apk_path, dex]
228 run_aapt(aapt, args)
229
Rico Wind0ed24cc2018-03-23 07:16:35 +0100230
Rico Windb1e257e2018-06-28 14:17:09 +0200231def kill(app):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200232 args = ['shell', 'am', 'force-stop', get_package_name(app)]
233 run_adb(args)
234
Rico Windb1e257e2018-06-28 14:17:09 +0200235
236def start_logcat():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200237 return subprocess.Popen(['adb', 'logcat'],
238 bufsize=1024 * 1024,
239 stdout=subprocess.PIPE,
240 stderr=subprocess.PIPE)
241
Rico Windb1e257e2018-06-28 14:17:09 +0200242
243def start(app):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200244 args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
245 run_adb(args)
246
Rico Windb1e257e2018-06-28 14:17:09 +0200247
248def clear_logcat():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200249 args = ['logcat', '-c']
250 run_adb(args)
251
Rico Windb1e257e2018-06-28 14:17:09 +0200252
253def stop_logcat(popen):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200254 popen.terminate()
255 lines = []
256 for l in popen.stdout:
257 if 'System.out' in l:
258 lines.append(l)
259 return lines
260
Rico Windb1e257e2018-06-28 14:17:09 +0200261
262def store_or_print_benchmarks(lines, output):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200263 results = {}
264 overall_total = 0
265 # We assume that the total times are
266 # prefixed with 'NAME Total: '. The logcat lines looks like:
267 # 06-28 12:22:00.991 13698 13698 I System.out: Call Total: 61614
268 for l in lines:
269 if 'Total: ' in l:
270 split = l.split('Total: ')
271 time = split[1]
272 name = split[0].split()[-1]
273 overall_total += int(time)
274 print('%s: %s' % (name, time))
275 results[name] = time
Rico Windb1e257e2018-06-28 14:17:09 +0200276
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200277 print('Total: %s' % overall_total)
278 if not output:
279 return overall_total
280 results['total'] = str(overall_total)
281 output_dir = os.path.join(output, str(uuid.uuid4()))
282 os.makedirs(output_dir)
283 written_files = []
284 for name, time in results.iteritems():
285 total_file = os.path.join(output_dir, name)
286 written_files.append(total_file)
287 with open(total_file, 'w') as f:
288 f.write(time)
289
290 print('Result stored in: \n%s' % ('\n'.join(written_files)))
Rico Wind097e3eb2018-07-05 15:14:10 +0200291 return overall_total
Rico Wind097e3eb2018-07-05 15:14:10 +0200292
Rico Windb1e257e2018-06-28 14:17:09 +0200293
294def benchmark(app, output_dir):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200295 # Ensure app is not running
296 kill(app)
297 clear_logcat()
298 logcat = start_logcat()
299 start(app)
300 # We could do better here by continiously parsing the logcat for a marker, but
301 # this works nicely with the current setup.
302 time.sleep(12)
303 kill(app)
304 return float(store_or_print_benchmarks(stop_logcat(logcat), output_dir))
305
Rico Wind097e3eb2018-07-05 15:14:10 +0200306
307def ensure_no_logcat():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200308 output = subprocess.check_output(['ps', 'aux'])
309 if 'adb logcat' in output:
310 raise Exception(
311 'You have adb logcat running, please close it and rerun')
312
Rico Windb1e257e2018-06-28 14:17:09 +0200313
Rico Wind33a57cc2020-05-05 09:06:09 +0200314def generate_proto_apks(apks, options):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200315 proto_apks = []
316 for apk in apks:
317 proto_apk = apk + '.proto'
318 cmd = [
319 options.aapt2, 'convert', '-o', proto_apk, '--output-format',
320 'proto', apk
321 ]
322 utils.PrintCmd(cmd)
323 subprocess.check_call(cmd)
324 proto_apks.append(proto_apk)
325 return proto_apks
326
Rico Wind33a57cc2020-05-05 09:06:09 +0200327
Rico Wind0ed24cc2018-03-23 07:16:35 +0100328def Main():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200329 (options, args) = parse_options()
330 apks = []
331 is_split = options.split
332 run_aapt_pack(options.aapt, options.api, options.app)
333 if is_split:
334 run_aapt_split_pack(options.aapt, options.api, options.app)
335 compile_with_javac(options.api, options.app)
336 dex(options.app, options.api)
337 dex_files = {options.app: get_dex_path(options.app)}
338 dex_path = get_dex_path(options.app)
339 if is_split:
340 split(options.app)
341 dex_path = get_split_path(options.app, 'base')
342 temp_apk_path = create_temp_apk(options.app, '')
343 aapt_add_dex(options.aapt, dex_path, temp_apk_path)
344 apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
345 apk_utils.sign(temp_apk_path, apk_path, options.keystore)
346 apks.append(apk_path)
347 if is_split:
348 split_temp_apk_path = create_temp_apk(options.app, 'split_')
349 aapt_add_dex(options.aapt, get_split_path(options.app, 'split'),
350 temp_apk_path)
351 split_apk_path = os.path.join(get_bin_path(options.app),
352 'featuresplit.apk')
353 apk_utils.sign(temp_apk_path, split_apk_path, options.keystore)
354 apks.append(split_apk_path)
355 if options.generate_proto_apk:
356 proto_apks = generate_proto_apks(apks, options)
357 print('Generated proto apks available at: %s' % ' '.join(proto_apks))
358 print('Generated apks available at: %s' % ' '.join(apks))
359 if options.install or options.benchmark:
360 adb_install(apks)
361 grand_total = 0
362 if options.benchmark:
363 ensure_no_logcat()
364 for _ in range(BENCHMARK_ITERATIONS):
365 grand_total += benchmark(options.app, options.benchmark_output_dir)
366 print('Combined average: %s' % (grand_total / BENCHMARK_ITERATIONS))
367
Rico Winda5db71d82018-06-27 13:45:05 +0200368
Rico Wind0ed24cc2018-03-23 07:16:35 +0100369if __name__ == '__main__':
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200370 sys.exit(Main())