blob: 9053e23c99eb0b3399d9840a81506d3fddb71334 [file] [log] [blame]
Rico Wind0ed24cc2018-03-23 07:16:35 +01001#!/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# Script for building sample apks using the sdk tools directly.
7
8import apk_utils
9import fnmatch
10import glob
11import optparse
12import os
13import shutil
14import subprocess
15import sys
Rico Windb1e257e2018-06-28 14:17:09 +020016import time
Rico Wind0ed24cc2018-03-23 07:16:35 +010017import utils
Rico Windb1e257e2018-06-28 14:17:09 +020018import uuid
Rico Wind0ed24cc2018-03-23 07:16:35 +010019
Rico Wind9d70f612018-08-31 09:17:43 +020020
Rico Wind0ed24cc2018-03-23 07:16:35 +010021DEFAULT_AAPT = 'aapt' # Assume in path.
22DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
Rico Winda5db71d82018-06-27 13:45:05 +020023DEFAULT_DEXSPLITTER = os.path.join(utils.REPO_ROOT, 'tools', 'dexsplitter.py')
Rico Wind0ed24cc2018-03-23 07:16:35 +010024DEFAULT_JAVAC = 'javac'
25SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
26DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
Rico Windb1e257e2018-06-28 14:17:09 +020027PACKAGE_PREFIX = 'com.android.tools.r8.sample'
28STANDARD_ACTIVITY = "R8Activity"
Rico Wind097e3eb2018-07-05 15:14:10 +020029BENCHMARK_ITERATIONS = 30
Rico Wind0ed24cc2018-03-23 07:16:35 +010030
31SAMPLE_APKS = [
Rico Winda5db71d82018-06-27 13:45:05 +020032 'simple',
33 'split'
Rico Wind0ed24cc2018-03-23 07:16:35 +010034]
35
36def parse_options():
37 result = optparse.OptionParser()
38 result.add_option('--aapt',
39 help='aapt executable to use',
40 default=DEFAULT_AAPT)
41 result.add_option('--api',
42 help='Android api level',
43 default=21,
44 choices=[14, 15, 19, 21, 22, 23, 24, 25, 26])
45 result.add_option('--keystore',
46 help='Keystore used for signing',
47 default=DEFAULT_KEYSTORE)
Rico Winda5db71d82018-06-27 13:45:05 +020048 result.add_option('--split',
49 help='Split the app using the split.spec file',
50 default=False, action='store_true')
Rico Windd0cd1a52018-06-27 15:26:22 +020051 result.add_option('--install',
52 help='Install the app (including featuresplit)',
53 default=False, action='store_true')
Rico Windb1e257e2018-06-28 14:17:09 +020054 result.add_option('--benchmark',
55 help='Benchmark the app on the phone with specialized markers',
56 default=False, action='store_true')
57 result.add_option('--benchmark-output-dir',
58 help='Store benchmark results here.',
59 default=None)
Rico Wind0ed24cc2018-03-23 07:16:35 +010060 result.add_option('--app',
61 help='Which app to build',
62 default='simple',
63 choices=SAMPLE_APKS)
64 return result.parse_args()
65
66def run_aapt(aapt, args):
67 command = [aapt]
68 command.extend(args)
69 utils.PrintCmd(command)
70 subprocess.check_call(command)
71
72def get_build_dir(app):
73 return os.path.join(utils.BUILD, 'sampleApks', app)
74
75def get_gen_path(app):
76 gen_path = os.path.join(get_build_dir(app), 'gen')
77 utils.makedirs_if_needed(gen_path)
78 return gen_path
79
80def get_bin_path(app):
81 bin_path = os.path.join(get_build_dir(app), 'bin')
82 utils.makedirs_if_needed(bin_path)
83 return bin_path
84
Rico Wind0ed24cc2018-03-23 07:16:35 +010085
Rico Windf3958632018-08-07 09:39:19 +020086def get_guava_jar():
87 return os.path.join(utils.REPO_ROOT,
88 'third_party/gradle-plugin/com/google/guava/guava/22.0/guava-22.0.jar')
89
Rico Wind0ed24cc2018-03-23 07:16:35 +010090def get_sample_dir(app):
91 return os.path.join(utils.REPO_ROOT, 'src', 'test', 'sampleApks', app)
92
93def get_src_path(app):
94 return os.path.join(get_sample_dir(app), 'src')
95
Rico Winda5db71d82018-06-27 13:45:05 +020096def get_dex_path(app):
97 return os.path.join(get_bin_path(app), 'classes.dex')
98
99def get_split_path(app, split):
100 return os.path.join(get_bin_path(app), split, 'classes.dex')
101
Rico Windb1e257e2018-06-28 14:17:09 +0200102def get_package_name(app):
103 return '%s.%s' % (PACKAGE_PREFIX, app)
104
105def get_qualified_activity(app):
106 # The activity specified to adb start is PACKAGE_NAME/.ACTIVITY
107 return '%s/.%s' % (get_package_name(app), STANDARD_ACTIVITY)
108
Rico Wind0ed24cc2018-03-23 07:16:35 +0100109def run_aapt_pack(aapt, api, app):
110 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
111 args = ['package',
112 '-v', '-f',
Rico Wind9d70f612018-08-31 09:17:43 +0200113 '-I', utils.get_android_jar(api),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100114 '-M', 'AndroidManifest.xml',
115 '-A', 'assets',
116 '-S', 'res',
117 '-m',
118 '-J', get_gen_path(app),
Søren Gjesse12c52092018-04-05 14:22:29 +0200119 '-F', os.path.join(get_bin_path(app), 'resources.ap_'),
120 '-G', os.path.join(get_build_dir(app), 'proguard_options')]
Rico Wind0ed24cc2018-03-23 07:16:35 +0100121 run_aapt(aapt, args)
122
Rico Winda5db71d82018-06-27 13:45:05 +0200123def run_aapt_split_pack(aapt, api, app):
124 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
125 args = ['package',
126 '-v', '-f',
Rico Wind9d70f612018-08-31 09:17:43 +0200127 '-I', utils.get_android_jar(api),
Rico Winda5db71d82018-06-27 13:45:05 +0200128 '-M', 'split_manifest/AndroidManifest.xml',
129 '-S', 'res',
130 '-F', os.path.join(get_bin_path(app), 'split_resources.ap_')]
131 run_aapt(aapt, args)
132
Rico Wind0ed24cc2018-03-23 07:16:35 +0100133def compile_with_javac(api, app):
134 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
135 files = glob.glob(SRC_LOCATION.format(app=app))
Rico Wind9d70f612018-08-31 09:17:43 +0200136 classpath = '%s:%s' % (utils.get_android_jar(api), get_guava_jar())
Rico Wind0ed24cc2018-03-23 07:16:35 +0100137 command = [DEFAULT_JAVAC,
Rico Wind9d70f612018-08-31 09:17:43 +0200138 '-classpath', classpath,
Rico Windf3958632018-08-07 09:39:19 +0200139 '-sourcepath', '%s:%s:%s' % (
140 get_src_path(app),
141 get_gen_path(app),
142 get_guava_jar()),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100143 '-d', get_bin_path(app)]
144 command.extend(files)
145 utils.PrintCmd(command)
146 subprocess.check_call(command)
147
148def dex(app, api):
149 files = []
150 for root, dirnames, filenames in os.walk(get_bin_path(app)):
151 for filename in fnmatch.filter(filenames, '*.class'):
152 files.append(os.path.join(root, filename))
153 command = [DEFAULT_D8,
154 '--output', get_bin_path(app),
Rico Wind9d70f612018-08-31 09:17:43 +0200155 '--classpath', utils.get_android_jar(api),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100156 '--min-api', str(api)]
157 command.extend(files)
Rico Windf3958632018-08-07 09:39:19 +0200158 command.append(get_guava_jar())
159
Rico Wind0ed24cc2018-03-23 07:16:35 +0100160 utils.PrintCmd(command)
161 subprocess.check_call(command)
162
Rico Winda5db71d82018-06-27 13:45:05 +0200163def split(app):
164 split_spec = os.path.join(get_sample_dir(app), 'split.spec')
165 command = [DEFAULT_DEXSPLITTER,
166 '--input', get_dex_path(app),
167 '--output', get_bin_path(app),
168 '--feature-splits', split_spec]
169 utils.PrintCmd(command)
170 subprocess.check_call(command)
171
Rico Wind097e3eb2018-07-05 15:14:10 +0200172def run_adb(args, ignore_exit=False):
Rico Windd0cd1a52018-06-27 15:26:22 +0200173 command = ['adb']
174 command.extend(args)
175 utils.PrintCmd(command)
Rico Winde01a5052018-07-06 07:28:32 +0200176 # On M adb install-multiple exits 1 but succeed in installing.
Rico Wind097e3eb2018-07-05 15:14:10 +0200177 if ignore_exit:
178 subprocess.call(command)
179 else:
180 subprocess.check_call(command)
Rico Windd0cd1a52018-06-27 15:26:22 +0200181
182def adb_install(apks):
183 args = [
184 'install-multiple' if len(apks) > 1 else 'install',
185 '-r',
186 '-d']
187 args.extend(apks)
Rico Wind097e3eb2018-07-05 15:14:10 +0200188 run_adb(args, ignore_exit=True)
Rico Windd0cd1a52018-06-27 15:26:22 +0200189
Rico Winda5db71d82018-06-27 13:45:05 +0200190def create_temp_apk(app, prefix):
Rico Wind0ed24cc2018-03-23 07:16:35 +0100191 temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
Rico Winda5db71d82018-06-27 13:45:05 +0200192 shutil.copyfile(os.path.join(get_bin_path(app), '%sresources.ap_' % prefix),
193 temp_apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100194 return temp_apk_path
195
Rico Winda5db71d82018-06-27 13:45:05 +0200196def aapt_add_dex(aapt, dex, temp_apk_path):
Rico Wind0ed24cc2018-03-23 07:16:35 +0100197 args = ['add',
198 '-k', temp_apk_path,
Rico Winda5db71d82018-06-27 13:45:05 +0200199 dex]
Rico Wind0ed24cc2018-03-23 07:16:35 +0100200 run_aapt(aapt, args)
201
Rico Windb1e257e2018-06-28 14:17:09 +0200202def kill(app):
203 args = ['shell', 'am', 'force-stop', get_package_name(app)]
204 run_adb(args)
205
206def start_logcat():
Rico Wind097e3eb2018-07-05 15:14:10 +0200207 return subprocess.Popen(['adb', 'logcat'], bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Rico Windb1e257e2018-06-28 14:17:09 +0200208
209def start(app):
210 args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
211 run_adb(args)
212
213def clear_logcat():
214 args = ['logcat', '-c']
215 run_adb(args)
216
217def stop_logcat(popen):
218 popen.terminate()
219 lines = []
220 for l in popen.stdout:
221 if 'System.out' in l:
222 lines.append(l)
223 return lines
224
225def store_or_print_benchmarks(lines, output):
Rico Wind097e3eb2018-07-05 15:14:10 +0200226 results = {}
227 overall_total = 0
228 # We assume that the total times are
229 # prefixed with 'NAME Total: '. The logcat lines looks like:
230 # 06-28 12:22:00.991 13698 13698 I System.out: Call Total: 61614
Rico Windb1e257e2018-06-28 14:17:09 +0200231 for l in lines:
Rico Windb1e257e2018-06-28 14:17:09 +0200232 if 'Total: ' in l:
Rico Wind097e3eb2018-07-05 15:14:10 +0200233 split = l.split('Total: ')
234 time = split[1]
235 name = split[0].split()[-1]
236 overall_total += int(time)
237 print '%s: %s' % (name, time)
238 results[name] = time
Rico Windb1e257e2018-06-28 14:17:09 +0200239
Rico Wind097e3eb2018-07-05 15:14:10 +0200240 print 'Total: %s' % overall_total
241 if not output:
242 return overall_total
243 results['total'] = str(overall_total)
Rico Windb1e257e2018-06-28 14:17:09 +0200244 output_dir = os.path.join(output, str(uuid.uuid4()))
245 os.makedirs(output_dir)
Rico Wind097e3eb2018-07-05 15:14:10 +0200246 written_files = []
247 for name, time in results.iteritems():
248 total_file = os.path.join(output_dir, name)
249 written_files.append(total_file)
250 with open(total_file, 'w') as f:
251 f.write(time)
252
253 print 'Result stored in: \n%s' % ('\n'.join(written_files))
254 return overall_total
Rico Windb1e257e2018-06-28 14:17:09 +0200255
256def benchmark(app, output_dir):
257 # Ensure app is not running
258 kill(app)
259 clear_logcat()
260 logcat = start_logcat()
261 start(app)
262 # We could do better here by continiously parsing the logcat for a marker, but
263 # this works nicely with the current setup.
Rico Windf3958632018-08-07 09:39:19 +0200264 time.sleep(12)
Rico Windb1e257e2018-06-28 14:17:09 +0200265 kill(app)
Rico Wind097e3eb2018-07-05 15:14:10 +0200266 return float(store_or_print_benchmarks(stop_logcat(logcat), output_dir))
267
268def ensure_no_logcat():
269 output = subprocess.check_output(['ps', 'aux'])
270 if 'adb logcat' in output:
271 raise Exception('You have adb logcat running, please close it and rerun')
Rico Windb1e257e2018-06-28 14:17:09 +0200272
Rico Wind0ed24cc2018-03-23 07:16:35 +0100273def Main():
274 (options, args) = parse_options()
Rico Windd0cd1a52018-06-27 15:26:22 +0200275 apks = []
Rico Winda5db71d82018-06-27 13:45:05 +0200276 is_split = options.split
Rico Wind0ed24cc2018-03-23 07:16:35 +0100277 run_aapt_pack(options.aapt, options.api, options.app)
Rico Winda5db71d82018-06-27 13:45:05 +0200278 if is_split:
279 run_aapt_split_pack(options.aapt, options.api, options.app)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100280 compile_with_javac(options.api, options.app)
281 dex(options.app, options.api)
Rico Winda5db71d82018-06-27 13:45:05 +0200282 dex_files = { options.app: get_dex_path(options.app)}
283 dex_path = get_dex_path(options.app)
284 if is_split:
285 split(options.app)
286 dex_path = get_split_path(options.app, 'base')
287
288 temp_apk_path = create_temp_apk(options.app, '')
289 aapt_add_dex(options.aapt, dex_path, temp_apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100290 apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
291 apk_utils.sign(temp_apk_path, apk_path, options.keystore)
Rico Windd0cd1a52018-06-27 15:26:22 +0200292 apks.append(apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100293
Rico Windd0cd1a52018-06-27 15:26:22 +0200294 if is_split:
Rico Winda5db71d82018-06-27 13:45:05 +0200295 split_temp_apk_path = create_temp_apk(options.app, 'split_')
296 aapt_add_dex(options.aapt,
297 get_split_path(options.app, 'split'),
298 temp_apk_path)
299 split_apk_path = os.path.join(get_bin_path(options.app), 'featuresplit.apk')
300 apk_utils.sign(temp_apk_path, split_apk_path, options.keystore)
Rico Windd0cd1a52018-06-27 15:26:22 +0200301 apks.append(split_apk_path)
Rico Winda5db71d82018-06-27 13:45:05 +0200302
Rico Windd0cd1a52018-06-27 15:26:22 +0200303 print('Generated apks available at: %s' % ' '.join(apks))
Rico Winde01a5052018-07-06 07:28:32 +0200304 if options.install or options.benchmark:
Rico Windd0cd1a52018-06-27 15:26:22 +0200305 adb_install(apks)
Rico Wind097e3eb2018-07-05 15:14:10 +0200306 grand_total = 0
Rico Windb1e257e2018-06-28 14:17:09 +0200307 if options.benchmark:
Rico Wind097e3eb2018-07-05 15:14:10 +0200308 ensure_no_logcat()
Rico Windb1e257e2018-06-28 14:17:09 +0200309 for _ in range(BENCHMARK_ITERATIONS):
Rico Wind097e3eb2018-07-05 15:14:10 +0200310 grand_total += benchmark(options.app, options.benchmark_output_dir)
311 print 'Combined average: %s' % (grand_total/BENCHMARK_ITERATIONS)
Rico Winda5db71d82018-06-27 13:45:05 +0200312
Rico Wind0ed24cc2018-03-23 07:16:35 +0100313if __name__ == '__main__':
314 sys.exit(Main())