blob: 29f745936884380135b52e9366cb4dc131fa78a2 [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
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.
23DEFAULT_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
32SAMPLE_APKS = [
Rico Winda5db71d82018-06-27 13:45:05 +020033 'simple',
34 'split'
Rico Wind0ed24cc2018-03-23 07:16:35 +010035]
36
37def parse_options():
38 result = optparse.OptionParser()
39 result.add_option('--aapt',
40 help='aapt executable to use',
41 default=DEFAULT_AAPT)
42 result.add_option('--api',
43 help='Android api level',
44 default=21,
45 choices=[14, 15, 19, 21, 22, 23, 24, 25, 26])
46 result.add_option('--keystore',
47 help='Keystore used for signing',
48 default=DEFAULT_KEYSTORE)
Rico Winda5db71d82018-06-27 13:45:05 +020049 result.add_option('--split',
50 help='Split the app using the split.spec file',
51 default=False, action='store_true')
Rico Windd0cd1a52018-06-27 15:26:22 +020052 result.add_option('--install',
53 help='Install the app (including featuresplit)',
54 default=False, action='store_true')
Rico Windb1e257e2018-06-28 14:17:09 +020055 result.add_option('--benchmark',
56 help='Benchmark the app on the phone with specialized markers',
57 default=False, action='store_true')
58 result.add_option('--benchmark-output-dir',
59 help='Store benchmark results here.',
60 default=None)
Rico Wind0ed24cc2018-03-23 07:16:35 +010061 result.add_option('--app',
62 help='Which app to build',
63 default='simple',
64 choices=SAMPLE_APKS)
65 return result.parse_args()
66
67def run_aapt(aapt, args):
68 command = [aapt]
69 command.extend(args)
70 utils.PrintCmd(command)
71 subprocess.check_call(command)
72
73def get_build_dir(app):
74 return os.path.join(utils.BUILD, 'sampleApks', app)
75
76def get_gen_path(app):
77 gen_path = os.path.join(get_build_dir(app), 'gen')
78 utils.makedirs_if_needed(gen_path)
79 return gen_path
80
81def get_bin_path(app):
82 bin_path = os.path.join(get_build_dir(app), 'bin')
83 utils.makedirs_if_needed(bin_path)
84 return bin_path
85
Rico Wind0ed24cc2018-03-23 07:16:35 +010086
Rico Windf3958632018-08-07 09:39:19 +020087def get_guava_jar():
88 return os.path.join(utils.REPO_ROOT,
89 'third_party/gradle-plugin/com/google/guava/guava/22.0/guava-22.0.jar')
90
Rico Wind0ed24cc2018-03-23 07:16:35 +010091def get_sample_dir(app):
92 return os.path.join(utils.REPO_ROOT, 'src', 'test', 'sampleApks', app)
93
94def get_src_path(app):
95 return os.path.join(get_sample_dir(app), 'src')
96
Rico Winda5db71d82018-06-27 13:45:05 +020097def get_dex_path(app):
98 return os.path.join(get_bin_path(app), 'classes.dex')
99
100def get_split_path(app, split):
101 return os.path.join(get_bin_path(app), split, 'classes.dex')
102
Rico Windb1e257e2018-06-28 14:17:09 +0200103def get_package_name(app):
104 return '%s.%s' % (PACKAGE_PREFIX, app)
105
106def get_qualified_activity(app):
107 # The activity specified to adb start is PACKAGE_NAME/.ACTIVITY
108 return '%s/.%s' % (get_package_name(app), STANDARD_ACTIVITY)
109
Rico Wind0ed24cc2018-03-23 07:16:35 +0100110def run_aapt_pack(aapt, api, app):
111 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
112 args = ['package',
113 '-v', '-f',
Rico Wind9d70f612018-08-31 09:17:43 +0200114 '-I', utils.get_android_jar(api),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100115 '-M', 'AndroidManifest.xml',
116 '-A', 'assets',
117 '-S', 'res',
118 '-m',
119 '-J', get_gen_path(app),
Søren Gjesse12c52092018-04-05 14:22:29 +0200120 '-F', os.path.join(get_bin_path(app), 'resources.ap_'),
121 '-G', os.path.join(get_build_dir(app), 'proguard_options')]
Rico Wind0ed24cc2018-03-23 07:16:35 +0100122 run_aapt(aapt, args)
123
Rico Winda5db71d82018-06-27 13:45:05 +0200124def run_aapt_split_pack(aapt, api, app):
125 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
126 args = ['package',
127 '-v', '-f',
Rico Wind9d70f612018-08-31 09:17:43 +0200128 '-I', utils.get_android_jar(api),
Rico Winda5db71d82018-06-27 13:45:05 +0200129 '-M', 'split_manifest/AndroidManifest.xml',
130 '-S', 'res',
131 '-F', os.path.join(get_bin_path(app), 'split_resources.ap_')]
132 run_aapt(aapt, args)
133
Rico Wind0ed24cc2018-03-23 07:16:35 +0100134def compile_with_javac(api, app):
135 with utils.ChangedWorkingDirectory(get_sample_dir(app)):
136 files = glob.glob(SRC_LOCATION.format(app=app))
Rico Wind9d70f612018-08-31 09:17:43 +0200137 classpath = '%s:%s' % (utils.get_android_jar(api), get_guava_jar())
Rico Wind0ed24cc2018-03-23 07:16:35 +0100138 command = [DEFAULT_JAVAC,
Rico Wind9d70f612018-08-31 09:17:43 +0200139 '-classpath', classpath,
Rico Windf3958632018-08-07 09:39:19 +0200140 '-sourcepath', '%s:%s:%s' % (
141 get_src_path(app),
142 get_gen_path(app),
143 get_guava_jar()),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100144 '-d', get_bin_path(app)]
145 command.extend(files)
146 utils.PrintCmd(command)
147 subprocess.check_call(command)
148
149def dex(app, api):
150 files = []
151 for root, dirnames, filenames in os.walk(get_bin_path(app)):
152 for filename in fnmatch.filter(filenames, '*.class'):
153 files.append(os.path.join(root, filename))
154 command = [DEFAULT_D8,
155 '--output', get_bin_path(app),
Rico Wind9d70f612018-08-31 09:17:43 +0200156 '--classpath', utils.get_android_jar(api),
Rico Wind0ed24cc2018-03-23 07:16:35 +0100157 '--min-api', str(api)]
158 command.extend(files)
Rico Windf3958632018-08-07 09:39:19 +0200159 command.append(get_guava_jar())
160
Rico Wind0ed24cc2018-03-23 07:16:35 +0100161 utils.PrintCmd(command)
162 subprocess.check_call(command)
163
Rico Winda5db71d82018-06-27 13:45:05 +0200164def split(app):
165 split_spec = os.path.join(get_sample_dir(app), 'split.spec')
166 command = [DEFAULT_DEXSPLITTER,
167 '--input', get_dex_path(app),
168 '--output', get_bin_path(app),
169 '--feature-splits', split_spec]
170 utils.PrintCmd(command)
171 subprocess.check_call(command)
172
Rico Wind097e3eb2018-07-05 15:14:10 +0200173def run_adb(args, ignore_exit=False):
Rico Windd0cd1a52018-06-27 15:26:22 +0200174 command = ['adb']
175 command.extend(args)
176 utils.PrintCmd(command)
Rico Winde01a5052018-07-06 07:28:32 +0200177 # On M adb install-multiple exits 1 but succeed in installing.
Rico Wind097e3eb2018-07-05 15:14:10 +0200178 if ignore_exit:
179 subprocess.call(command)
180 else:
181 subprocess.check_call(command)
Rico Windd0cd1a52018-06-27 15:26:22 +0200182
183def adb_install(apks):
184 args = [
185 'install-multiple' if len(apks) > 1 else 'install',
186 '-r',
187 '-d']
188 args.extend(apks)
Rico Wind097e3eb2018-07-05 15:14:10 +0200189 run_adb(args, ignore_exit=True)
Rico Windd0cd1a52018-06-27 15:26:22 +0200190
Rico Winda5db71d82018-06-27 13:45:05 +0200191def create_temp_apk(app, prefix):
Rico Wind0ed24cc2018-03-23 07:16:35 +0100192 temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
Rico Winda5db71d82018-06-27 13:45:05 +0200193 shutil.copyfile(os.path.join(get_bin_path(app), '%sresources.ap_' % prefix),
194 temp_apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100195 return temp_apk_path
196
Rico Winda5db71d82018-06-27 13:45:05 +0200197def aapt_add_dex(aapt, dex, temp_apk_path):
Rico Wind0ed24cc2018-03-23 07:16:35 +0100198 args = ['add',
199 '-k', temp_apk_path,
Rico Winda5db71d82018-06-27 13:45:05 +0200200 dex]
Rico Wind0ed24cc2018-03-23 07:16:35 +0100201 run_aapt(aapt, args)
202
Rico Windb1e257e2018-06-28 14:17:09 +0200203def kill(app):
204 args = ['shell', 'am', 'force-stop', get_package_name(app)]
205 run_adb(args)
206
207def start_logcat():
Rico Wind097e3eb2018-07-05 15:14:10 +0200208 return subprocess.Popen(['adb', 'logcat'], bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Rico Windb1e257e2018-06-28 14:17:09 +0200209
210def start(app):
211 args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
212 run_adb(args)
213
214def clear_logcat():
215 args = ['logcat', '-c']
216 run_adb(args)
217
218def stop_logcat(popen):
219 popen.terminate()
220 lines = []
221 for l in popen.stdout:
222 if 'System.out' in l:
223 lines.append(l)
224 return lines
225
226def store_or_print_benchmarks(lines, output):
Rico Wind097e3eb2018-07-05 15:14:10 +0200227 results = {}
228 overall_total = 0
229 # We assume that the total times are
230 # prefixed with 'NAME Total: '. The logcat lines looks like:
231 # 06-28 12:22:00.991 13698 13698 I System.out: Call Total: 61614
Rico Windb1e257e2018-06-28 14:17:09 +0200232 for l in lines:
Rico Windb1e257e2018-06-28 14:17:09 +0200233 if 'Total: ' in l:
Rico Wind097e3eb2018-07-05 15:14:10 +0200234 split = l.split('Total: ')
235 time = split[1]
236 name = split[0].split()[-1]
237 overall_total += int(time)
238 print '%s: %s' % (name, time)
239 results[name] = time
Rico Windb1e257e2018-06-28 14:17:09 +0200240
Rico Wind097e3eb2018-07-05 15:14:10 +0200241 print 'Total: %s' % overall_total
242 if not output:
243 return overall_total
244 results['total'] = str(overall_total)
Rico Windb1e257e2018-06-28 14:17:09 +0200245 output_dir = os.path.join(output, str(uuid.uuid4()))
246 os.makedirs(output_dir)
Rico Wind097e3eb2018-07-05 15:14:10 +0200247 written_files = []
248 for name, time in results.iteritems():
249 total_file = os.path.join(output_dir, name)
250 written_files.append(total_file)
251 with open(total_file, 'w') as f:
252 f.write(time)
253
254 print 'Result stored in: \n%s' % ('\n'.join(written_files))
255 return overall_total
Rico Windb1e257e2018-06-28 14:17:09 +0200256
257def benchmark(app, output_dir):
258 # Ensure app is not running
259 kill(app)
260 clear_logcat()
261 logcat = start_logcat()
262 start(app)
263 # We could do better here by continiously parsing the logcat for a marker, but
264 # this works nicely with the current setup.
Rico Windf3958632018-08-07 09:39:19 +0200265 time.sleep(12)
Rico Windb1e257e2018-06-28 14:17:09 +0200266 kill(app)
Rico Wind097e3eb2018-07-05 15:14:10 +0200267 return float(store_or_print_benchmarks(stop_logcat(logcat), output_dir))
268
269def ensure_no_logcat():
270 output = subprocess.check_output(['ps', 'aux'])
271 if 'adb logcat' in output:
272 raise Exception('You have adb logcat running, please close it and rerun')
Rico Windb1e257e2018-06-28 14:17:09 +0200273
Rico Wind0ed24cc2018-03-23 07:16:35 +0100274def Main():
275 (options, args) = parse_options()
Rico Windd0cd1a52018-06-27 15:26:22 +0200276 apks = []
Rico Winda5db71d82018-06-27 13:45:05 +0200277 is_split = options.split
Rico Wind0ed24cc2018-03-23 07:16:35 +0100278 run_aapt_pack(options.aapt, options.api, options.app)
Rico Winda5db71d82018-06-27 13:45:05 +0200279 if is_split:
280 run_aapt_split_pack(options.aapt, options.api, options.app)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100281 compile_with_javac(options.api, options.app)
282 dex(options.app, options.api)
Rico Winda5db71d82018-06-27 13:45:05 +0200283 dex_files = { options.app: get_dex_path(options.app)}
284 dex_path = get_dex_path(options.app)
285 if is_split:
286 split(options.app)
287 dex_path = get_split_path(options.app, 'base')
288
289 temp_apk_path = create_temp_apk(options.app, '')
290 aapt_add_dex(options.aapt, dex_path, temp_apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100291 apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
292 apk_utils.sign(temp_apk_path, apk_path, options.keystore)
Rico Windd0cd1a52018-06-27 15:26:22 +0200293 apks.append(apk_path)
Rico Wind0ed24cc2018-03-23 07:16:35 +0100294
Rico Windd0cd1a52018-06-27 15:26:22 +0200295 if is_split:
Rico Winda5db71d82018-06-27 13:45:05 +0200296 split_temp_apk_path = create_temp_apk(options.app, 'split_')
297 aapt_add_dex(options.aapt,
298 get_split_path(options.app, 'split'),
299 temp_apk_path)
300 split_apk_path = os.path.join(get_bin_path(options.app), 'featuresplit.apk')
301 apk_utils.sign(temp_apk_path, split_apk_path, options.keystore)
Rico Windd0cd1a52018-06-27 15:26:22 +0200302 apks.append(split_apk_path)
Rico Winda5db71d82018-06-27 13:45:05 +0200303
Rico Windd0cd1a52018-06-27 15:26:22 +0200304 print('Generated apks available at: %s' % ' '.join(apks))
Rico Winde01a5052018-07-06 07:28:32 +0200305 if options.install or options.benchmark:
Rico Windd0cd1a52018-06-27 15:26:22 +0200306 adb_install(apks)
Rico Wind097e3eb2018-07-05 15:14:10 +0200307 grand_total = 0
Rico Windb1e257e2018-06-28 14:17:09 +0200308 if options.benchmark:
Rico Wind097e3eb2018-07-05 15:14:10 +0200309 ensure_no_logcat()
Rico Windb1e257e2018-06-28 14:17:09 +0200310 for _ in range(BENCHMARK_ITERATIONS):
Rico Wind097e3eb2018-07-05 15:14:10 +0200311 grand_total += benchmark(options.app, options.benchmark_output_dir)
312 print 'Combined average: %s' % (grand_total/BENCHMARK_ITERATIONS)
Rico Winda5db71d82018-06-27 13:45:05 +0200313
Rico Wind0ed24cc2018-03-23 07:16:35 +0100314if __name__ == '__main__':
315 sys.exit(Main())