blob: f0e53e6d4f80998f9f30ea1f02fe01a18407cfcf [file] [log] [blame]
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +01001#!/usr/bin/env python
Christoffer Quist Adamsenf2e8db72020-11-07 14:09:49 +01002# Copyright (c) 2020, 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.
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +01005
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +01006import adb
7import apk_masseur
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +01008import compiledump
9import gradle
10import jdk
11import optparse
12import os
13import shutil
14import sys
15import time
16import utils
17import zipfile
18
19from datetime import datetime
20
21SHRINKERS = ['r8', 'r8-full', 'r8-nolib', 'r8-nolib-full']
22
23class AttrDict(dict):
24 def __getattr__(self, name):
25 return self.get(name, None)
26
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +010027# To generate the files for a new app, navigate to the app source folder and
28# run:
29# ./gradlew clean :app:assembleRelease -Dcom.android.tools.r8.dumpinputtodirectory=<path>
30# and store the dump and the apk.
31# If the app has instrumented tests, adding `testBuildType "release"` and
32# running:
33# ./gradlew assembleAndroidTest -Dcom.android.tools.r8.dumpinputtodirectory=<path>
34# will also generate dumps and apk for tests.
35
36class App(object):
37 def __init__(self, fields):
38 defaults = {
39 'id': None,
40 'name': None,
41 'dump_app': None,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +010042 'apk_app': None,
Morten Krogh-Jespersen571cfe72020-11-09 23:44:03 +010043 'dump_test': None,
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +010044 'apk_test': None,
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +010045 'skip': False,
46 'url': None, # url is not used but nice to have for updating apps
47 'revision': None,
48 'folder': None,
49 'skip_recompilation': False,
50 }
51 # This below does not work in python3
52 defaults.update(fields.items())
53 self.__dict__ = defaults
54
55
56APPS = [
57 App({
Morten Krogh-Jespersen0f85fa52020-11-04 13:40:43 +010058 'id': 'com.numix.calculator',
59 'name': 'Calculator',
60 'dump_app': 'dump_app.zip',
61 'apk_app': 'app-release.apk',
62 # Compiling tests fail: Library class android.content.res.XmlResourceParser
63 # implements program class org.xmlpull.v1.XmlPullParser. Nothing to really
64 # do about that.
65 'id_test': 'com.numix.calculator.test',
66 'dump_test': 'dump_test.zip',
67 'apk_test': 'app-release-androidTest.apk',
68 'url': 'https://github.com/numixproject/android-suite/tree/master/Calculator',
69 'revision': 'f58e1b53f7278c9b675d5855842c6d8a44cccb1f',
70 'folder': 'android-suite-calculator',
71 }),
72 App({
Morten Krogh-Jespersen3f0d72f2020-11-04 15:49:17 +010073 'id': 'dev.dworks.apps.anexplorer.pro',
74 'name': 'AnExplorer',
75 'dump_app': 'dump_app.zip',
76 'apk_app': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
77 'url': 'https://github.com/christofferqa/AnExplorer',
78 'revision': '365927477b8eab4052a1882d5e358057ae3dee4d',
79 'folder': 'anexplorer',
80 }),
81 App({
Morten Krogh-Jespersen09e2fda2020-11-04 16:43:25 +010082 'id': 'de.danoeh.antennapod',
83 'name': 'AntennaPod',
84 'dump_app': 'dump_app.zip',
85 'apk_app': 'app-free-release.apk',
86 # TODO(b/172452102): Tests and monkey do not work
87 'id_test': 'de.danoeh.antennapod.test',
88 'dump_test': 'dump_test.zip',
89 'apk_test': 'app-free-release-androidTest.apk',
90 'url': 'https://github.com/christofferqa/AntennaPod.git',
91 'revision': '77e94f4783a16abe9cc5b78dc2d2b2b1867d8c06',
92 'folder': 'antennapod',
93 # TODO(b/172450929): Fix recompilation
94 'skip_recompilation': True
95 }),
96 App({
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +010097 'id': 'com.example.applymapping',
98 'name': 'applymapping',
99 'dump_app': 'dump_app.zip',
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100100 'apk_app': 'app-release.apk',
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100101 'id_test': 'com.example.applymapping.test',
102 'dump_test': 'dump_test.zip',
103 'apk_test': 'app-release-androidTest.apk',
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100104 'url': 'https://github.com/mkj-gram/applymapping',
105 'revision': 'e3ae14b8c16fa4718e5dea8f7ad00937701b3c48',
106 'folder': 'applymapping',
Morten Krogh-Jespersen90912fd2020-11-05 09:21:16 +0100107 }),
108 App({
Morten Krogh-Jespersena98a4222020-11-05 10:50:10 +0100109 'id': 'com.chanapps.four.activity',
110 'name': 'chanu',
111 'dump_app': 'dump_app.zip',
112 'apk_app': 'app-release.apk',
113 'url': 'https://github.com/mkj-gram/chanu.git',
114 'revision': '6e53458f167b6d78398da60c20fd0da01a232617',
115 'folder': 'chanu',
116 # TODO(b/172535996): Fix recompilation
117 'skip_recompilation': True
118 }),
Morten Krogh-Jespersend06ff012020-11-05 10:50:21 +0100119 # TODO(b/172539375): Monkey runner fails on recompilation.
120 App({
121 'id': 'com.google.firebase.example.fireeats',
122 'name': 'FriendlyEats',
123 'dump_app': 'dump_app.zip',
124 'apk_app': 'app-release-unsigned.apk',
125 'url': 'https://github.com/firebase/friendlyeats-android',
126 'revision': '7c6dd016fc31ea5ecb948d5166b8479efc3775cc',
127 'folder': 'friendlyeats',
128 }),
Morten Krogh-Jespersena98a4222020-11-05 10:50:10 +0100129 App({
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100130 'id': 'com.google.samples.apps.sunflower',
131 'name': 'Sunflower',
132 'dump_app': 'dump_app.zip',
133 'apk_app': 'app-debug.apk',
134 # TODO(b/172549283): Compiling tests fails
Morten Krogh-Jespersend0389c02020-11-09 23:42:51 +0100135 'id_test': 'com.google.samples.apps.sunflower.test',
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100136 'dump_test': 'dump_test.zip',
137 'apk_test': 'app-debug-androidTest.apk',
138 'url': 'https://github.com/android/sunflower',
139 'revision': '0c4c88fdad2a74791199dffd1a6559559b1dbd4a',
140 'folder': 'sunflower',
141 # TODO(b/172548728): Fix recompilation
142 'skip_recompilation': True
143 }),
Morten Krogh-Jespersenac9de3f2020-11-05 17:07:26 +0100144 # TODO(b/172565385): Monkey runner fails on recompilation
145 App({
146 'id': 'com.google.samples.apps.iosched',
147 'name': 'iosched',
148 'dump_app': 'dump_app.zip',
149 'apk_app': 'mobile-release.apk',
150 'url': 'https://github.com/christofferqa/iosched.git',
151 'revision': '581cbbe2253711775dbccb753cdb53e7e506cb02',
152 'folder': 'iosched',
153 }),
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100154 App({
Morten Krogh-Jespersend0389c02020-11-09 23:42:51 +0100155 'id': 'fr.neamar.kiss',
156 'name': 'KISS',
157 'dump_app': 'dump_app.zip',
158 'apk_app': 'app-release.apk',
159 # TODO(b/172569220): Running tests fails due to missing keep rules
160 'id_test': 'fr.neamar.kiss.test',
161 'dump_test': 'dump_test.zip',
162 'apk_test': 'app-release-androidTest.apk',
163 'url': 'https://github.com/Neamar/KISS',
164 'revision': '8ccffaadaf0d0b8fc4418ed2b4281a0935d3d971',
165 'folder': 'kiss',
166 }),
Morten Krogh-Jespersenaaacd722020-11-09 23:43:24 +0100167 # TODO(b/172577344): Monkey runner not working.
168 App({
169 'id': 'io.github.hidroh.materialistic',
170 'name': 'materialistic',
171 'dump_app': 'dump_app.zip',
172 'apk_app': 'app-release.apk',
173 'url': 'https://github.com/christofferqa/materialistic.git',
174 'revision': '2b2b2ee25ce9e672d5aab1dc90a354af1522b1d9',
175 'folder': 'materialistic',
176 }),
Morten Krogh-Jespersend0389c02020-11-09 23:42:51 +0100177 App({
Morten Krogh-Jespersen571cfe72020-11-09 23:44:03 +0100178 'id': 'com.avjindersinghsekhon.minimaltodo',
179 'name': 'MinimalTodo',
180 'dump_app': 'dump_app.zip',
181 'apk_app': 'app-release.apk',
182 'url': 'https://github.com/christofferqa/Minimal-Todo',
183 'revision': '9d8c73746762cd376b718858ec1e8783ca07ba7c',
184 'folder': 'minimal-todo',
185 }),
186 App({
Morten Krogh-Jespersen3e3781f2020-11-09 23:44:33 +0100187 'id': 'net.nurik.roman.muzei',
188 'name': 'muzei',
189 'dump_app': 'dump_app.zip',
190 'apk_app': 'muzei-release.apk',
191 'url': 'https://github.com/romannurik/muzei',
192 'revision': '9eac6e98aebeaf0ae40bdcd85f16dd2886551138',
193 'folder': 'muzei',
194 }),
195 App({
Morten Krogh-Jespersen90912fd2020-11-05 09:21:16 +0100196 'id': 'org.wikipedia',
197 'name': 'Wikipedia',
198 'dump_app': 'dump_app.zip',
199 'apk_app': 'app-prod-release.apk',
200 'url': 'https://github.com/wikimedia/apps-android-wikipedia',
201 'revision': '0fa7cad843c66313be8e25790ef084cf1a1fa67e',
202 'folder': 'wikipedia',
203 }),
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100204]
205
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100206def remove_print_lines(file):
207 with open(file) as f:
208 lines = f.readlines()
209 with open(file, 'w') as f:
210 for line in lines:
211 if '-printconfiguration' not in line:
212 f.write(line)
213
214
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100215def download_app(app_sha):
216 utils.DownloadFromGoogleCloudStorage(app_sha)
217
218
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100219def is_logging_enabled_for(app, options):
220 if options.no_logging:
221 return False
222 if options.app_logging_filter and app.name not in options.app_logging_filter:
223 return False
224 return True
225
226
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100227def is_minified_r8(shrinker):
228 return '-nolib' not in shrinker
229
230
231def is_full_r8(shrinker):
232 return '-full' not in shrinker
233
234
235def compute_size_of_dex_files_in_package(path):
236 dex_size = 0
237 z = zipfile.ZipFile(path, 'r')
238 for filename in z.namelist():
239 if filename.endswith('.dex'):
240 dex_size += z.getinfo(filename).file_size
241 return dex_size
242
243
244def dump_for_app(app_dir, app):
245 return os.path.join(app_dir, app.dump_app)
246
247
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100248def dump_test_for_app(app_dir, app):
249 return os.path.join(app_dir, app.dump_test)
250
251
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100252def get_results_for_app(app, options, temp_dir):
253 app_folder = app.folder if app.folder else app.name + "_" + app.revision
254 app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
255
256 if not os.path.exists(app_dir) and not options.golem:
257 # Download the app from google storage.
258 download_app(app_dir + ".tar.gz.sha1")
259
260 # Ensure that the dumps are in place
261 assert os.path.isfile(dump_for_app(app_dir, app)), "Could not find dump " \
262 "for app " + app.name
263
264 result = {}
265 result['status'] = 'success'
266 result_per_shrinker = build_app_with_shrinkers(
267 app, options, temp_dir, app_dir)
268 for shrinker, shrinker_result in result_per_shrinker.iteritems():
269 result[shrinker] = shrinker_result
270 return result
271
272
273def build_app_with_shrinkers(app, options, temp_dir, app_dir):
274 result_per_shrinker = {}
275 for shrinker in options.shrinker:
276 results = []
277 build_app_and_run_with_shrinker(
278 app, options, temp_dir, app_dir, shrinker, results)
279 result_per_shrinker[shrinker] = results
280 if len(options.apps) > 1:
281 print('')
282 log_results_for_app(app, result_per_shrinker, options)
283 print('')
284
285 return result_per_shrinker
286
287
288def is_last_build(index, compilation_steps):
289 return index == compilation_steps - 1
290
291
292def build_app_and_run_with_shrinker(app, options, temp_dir, app_dir, shrinker,
293 results):
294 print('[{}] Building {} with {}'.format(
295 datetime.now().strftime("%H:%M:%S"),
296 app.name,
297 shrinker))
298 print('To compile locally: '
299 'tools/run_on_as_app.py --shrinker {} --r8-compilation-steps {} '
300 '--app {}'.format(
301 shrinker,
302 options.r8_compilation_steps,
303 app.name))
304 print('HINT: use --shrinker r8-nolib --no-build if you have a local R8.jar')
305 recomp_jar = None
306 status = 'success'
307 compilation_steps = 1 if app.skip_recompilation else options.r8_compilation_steps;
308 for compilation_step in range(0, compilation_steps):
309 if status != 'success':
310 break
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100311 print('Compiling {} of {}'.format(compilation_step + 1, compilation_steps))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100312 result = {}
313 try:
314 start = time.time()
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100315 (app_jar, mapping, new_recomp_jar) = \
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100316 build_app_with_shrinker(
317 app, options, temp_dir, app_dir, shrinker, compilation_step,
318 compilation_steps, recomp_jar)
319 end = time.time()
320 dex_size = compute_size_of_dex_files_in_package(app_jar)
321 result['build_status'] = 'success'
322 result['recompilation_status'] = 'success'
323 result['output_jar'] = app_jar
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100324 result['output_mapping'] = mapping
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100325 result['dex_size'] = dex_size
326 result['duration'] = int((end - start) * 1000) # Wall time
327 if (new_recomp_jar is None
328 and not is_last_build(compilation_step, compilation_steps)):
329 result['recompilation_status'] = 'failed'
330 warn('Failed to build {} with {}'.format(app.name, shrinker))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100331 recomp_jar = new_recomp_jar
332 except Exception as e:
333 warn('Failed to build {} with {}'.format(app.name, shrinker))
334 if e:
335 print('Error: ' + str(e))
336 result['build_status'] = 'failed'
337 status = 'failed'
338
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100339 original_app_apk = os.path.join(app_dir, app.apk_app)
340 app_apk_destination = os.path.join(
341 temp_dir,"{}_{}.apk".format(app.id, compilation_step))
342
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100343 if result.get('build_status') == 'success' and options.monkey:
344 # Make a copy of the given APK, move the newly generated dex files into the
345 # copied APK, and then sign the APK.
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100346 apk_masseur.masseur(
347 original_app_apk, dex=app_jar, resources='META-INF/services/*',
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100348 out=app_apk_destination,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100349 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
350 keystore=options.keystore)
351
352 result['monkey_status'] = 'success' if adb.run_monkey(
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100353 app.id, options.emulator_id, app_apk_destination, options.monkey_events,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100354 options.quiet, is_logging_enabled_for(app, options)) else 'failed'
355
Morten Krogh-Jespersen571cfe72020-11-09 23:44:03 +0100356 if (result.get('build_status') == 'success'
357 and options.run_tests and app.dump_test):
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100358 if not os.path.isfile(app_apk_destination):
359 apk_masseur.masseur(
360 original_app_apk, dex=app_jar, resources='META-INF/services/*',
361 out=app_apk_destination,
362 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
363 keystore=options.keystore)
364
365 # Compile the tests with the mapping file.
366 test_jar = build_test_with_shrinker(
367 app, options, temp_dir, app_dir,shrinker, compilation_step,
368 result['output_mapping'])
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100369 if not test_jar:
370 result['instrumentation_test_status'] = 'compilation_failed'
371 else:
372 original_test_apk = os.path.join(app_dir, app.apk_test)
373 test_apk_destination = os.path.join(
374 temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
375 apk_masseur.masseur(
376 original_test_apk, dex=test_jar, resources='META-INF/services/*',
377 out=test_apk_destination,
378 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
379 keystore=options.keystore)
380 result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
381 app.id, app.id_test, options.emulator_id, app_apk_destination,
382 test_apk_destination, options.quiet,
383 is_logging_enabled_for(app, options)) else 'failed'
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100384
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100385 results.append(result)
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100386 if result.get('recompilation_status') != 'success':
387 break
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100388
389
390def build_app_with_shrinker(app, options, temp_dir, app_dir, shrinker,
391 compilation_step_index, compilation_steps,
392 prev_recomp_jar):
393 r8jar = os.path.join(
394 temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
395
396 args = AttrDict({
397 'dump': dump_for_app(app_dir, app),
398 'r8_jar': r8jar,
399 'ea': False if options.disable_assertions else True,
400 'version': 'master',
401 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
402 'debug_agent': options.debug_agent,
403 'program_jar': prev_recomp_jar,
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100404 'nolib': not is_minified_r8(shrinker),
405 'config_file_consumer': remove_print_lines,
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100406 })
407
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100408 compile_result = compiledump.run1(temp_dir, args, [])
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100409
410 out_jar = os.path.join(temp_dir, "out.jar")
411 out_mapping = os.path.join(temp_dir, "out.jar.map")
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100412 app_jar = os.path.join(
413 temp_dir, '{}_{}_{}_dex_out.jar'.format(
414 app.name, shrinker, compilation_step_index))
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100415 app_mapping = os.path.join(
416 temp_dir, '{}_{}_{}_dex_out.jar.map'.format(
417 app.name, shrinker, compilation_step_index))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100418
419 if compile_result != 0 or not os.path.isfile(out_jar):
420 assert False, "Compilation of app_jar failed"
421 shutil.move(out_jar, app_jar)
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100422 shutil.move(out_mapping, app_mapping)
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100423
424 recomp_jar = None
425 if compilation_step_index < compilation_steps - 1:
426 args['classfile'] = True
427 args['min_api'] = "10000"
428 compile_result = compiledump.run1(temp_dir, args, [])
429 if compile_result == 0:
430 recomp_jar = os.path.join(
431 temp_dir, '{}_{}_{}_cf_out.jar'.format(
432 app.name, shrinker, compilation_step_index))
433 shutil.move(out_jar, recomp_jar)
434
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100435 return (app_jar, app_mapping, recomp_jar)
436
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100437
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100438def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
439 compilation_step_index, mapping):
440 r8jar = os.path.join(
441 temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
442
443 def rewrite_file(file):
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100444 remove_print_lines(file)
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100445 with open(file) as f:
446 lines = f.readlines()
447 with open(file, 'w') as f:
448 for line in lines:
449 if '-applymapping' not in line:
450 f.write(line + '\n')
451 f.write("-applymapping " + mapping + '\n')
452
453 args = AttrDict({
454 'dump': dump_test_for_app(app_dir, app),
455 'r8_jar': r8jar,
456 'ea': False if options.disable_assertions else True,
457 'version': 'master',
458 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
459 'debug_agent': options.debug_agent,
460 'nolib': not is_minified_r8(shrinker),
461 # The config file will have an -applymapping reference to an old map.
462 # Update it to point to mapping file build in the compilation of the app.
463 'config_file_consumer': rewrite_file
464 })
465
466 compile_result = compiledump.run1(temp_dir, args, [])
467
468 out_jar = os.path.join(temp_dir, "out.jar")
469 test_jar = os.path.join(
470 temp_dir, '{}_{}_{}_test_out.jar'.format(
471 app.name, shrinker, compilation_step_index))
472
473 if compile_result != 0 or not os.path.isfile(out_jar):
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100474 return None
475
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100476 shutil.move(out_jar, test_jar)
477
478 return test_jar
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100479
480
481def log_results_for_apps(result_per_shrinker_per_app, options):
482 print('')
483 app_errors = 0
484 for (app, result_per_shrinker) in result_per_shrinker_per_app:
485 app_errors += (1 if log_results_for_app(app, result_per_shrinker, options)
486 else 0)
487 return app_errors
488
489
490def log_results_for_app(app, result_per_shrinker, options):
491 if options.print_dexsegments:
492 log_segments_for_app(app, result_per_shrinker, options)
493 return False
494 else:
495 return log_comparison_results_for_app(app, result_per_shrinker, options)
496
497
498def log_segments_for_app(app, result_per_shrinker, options):
499 for shrinker in SHRINKERS:
500 if shrinker not in result_per_shrinker:
501 continue
502 for result in result_per_shrinker.get(shrinker):
503 benchmark_name = '{}-{}'.format(options.print_dexsegments, app.name)
504 utils.print_dexsegments(benchmark_name, [result.get('output_jar')])
505 duration = result.get('duration')
506 print('%s-Total(RunTimeRaw): %s ms' % (benchmark_name, duration))
507 print('%s-Total(CodeSize): %s' % (benchmark_name, result.get('dex_size')))
508
509
510def percentage_diff_as_string(before, after):
511 if after < before:
512 return '-' + str(round((1.0 - after / before) * 100)) + '%'
513 else:
514 return '+' + str(round((after - before) / before * 100)) + '%'
515
516
517def log_comparison_results_for_app(app, result_per_shrinker, options):
518 print(app.name + ':')
519 app_error = False
520 if result_per_shrinker.get('status', 'success') != 'success':
521 error_message = result_per_shrinker.get('error_message')
522 print(' skipped ({})'.format(error_message))
523 return
524
525 proguard_result = result_per_shrinker.get('pg', {})
526 proguard_dex_size = float(proguard_result.get('dex_size', -1))
527
528 for shrinker in SHRINKERS:
529 if shrinker not in result_per_shrinker:
530 continue
531 compilation_index = 1
532 for result in result_per_shrinker.get(shrinker):
533 build_status = result.get('build_status')
534 if build_status != 'success' and build_status is not None:
535 app_error = True
536 warn(' {}-#{}: {}'.format(shrinker, compilation_index, build_status))
537 continue
538
539 print(' {}-#{}:'.format(shrinker, compilation_index))
540 dex_size = result.get('dex_size')
541 msg = ' dex size: {}'.format(dex_size)
542 if dex_size != proguard_dex_size and proguard_dex_size >= 0:
543 msg = '{} ({}, {})'.format(
544 msg, dex_size - proguard_dex_size,
545 percentage_diff_as_string(proguard_dex_size, dex_size))
546 success(msg) if dex_size < proguard_dex_size else warn(msg)
547 else:
548 print(msg)
549
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100550 if options.monkey:
551 monkey_status = result.get('monkey_status')
552 if monkey_status != 'success':
553 app_error = True
554 warn(' monkey: {}'.format(monkey_status))
555 else:
556 success(' monkey: {}'.format(monkey_status))
557
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100558 if options.run_tests and 'instrumentation_test_status' in result:
559 test_status = result.get('instrumentation_test_status')
560 if test_status != 'success':
561 warn(' instrumentation_tests: {}'.format(test_status))
562 else:
563 success(' instrumentation_tests: {}'.format(test_status))
564
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100565 recompilation_status = result.get('recompilation_status', '')
566 if recompilation_status == 'failed':
567 app_error = True
568 warn(' recompilation {}-#{}: failed'.format(shrinker,
569 compilation_index))
570 continue
571
572 compilation_index += 1
573
574 return app_error
575
576
577def parse_options(argv):
578 result = optparse.OptionParser()
579 result.add_option('--app',
580 help='What app to run on',
581 choices=[app.name for app in APPS],
582 action='append')
583 result.add_option('--bot',
584 help='Running on bot, use third_party dependency.',
585 default=False,
586 action='store_true')
587 result.add_option('--debug-agent',
588 help='Enable Java debug agent and suspend compilation '
589 '(default disabled)',
590 default=False,
591 action='store_true')
592 result.add_option('--disable-assertions', '--disable_assertions',
593 help='Disable assertions when compiling',
594 default=False,
595 action='store_true')
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100596 result.add_option('--emulator-id', '--emulator_id',
597 help='Id of the emulator to use',
598 default='emulator-5554')
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100599 result.add_option('--golem',
600 help='Running on golem, do not download',
601 default=False,
602 action='store_true')
603 result.add_option('--hash',
604 help='The commit of R8 to use')
605 result.add_option('--keystore',
606 help='Path to app.keystore',
607 default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
608 result.add_option('--keystore-password', '--keystore_password',
609 help='Password for app.keystore',
610 default='android')
611 result.add_option('--app-logging-filter', '--app_logging_filter',
612 help='The apps for which to turn on logging',
613 action='append')
614 result.add_option('--monkey',
615 help='Whether to install and run app(s) with monkey',
616 default=False,
617 action='store_true')
618 result.add_option('--monkey-events', '--monkey_events',
619 help='Number of events that the monkey should trigger',
620 default=250,
621 type=int)
622 result.add_option('--no-build', '--no_build',
623 help='Run without building ToT first (only when using ToT)',
624 default=False,
625 action='store_true')
626 result.add_option('--no-logging', '--no_logging',
627 help='Disable logging except for errors',
628 default=False,
629 action='store_true')
630 result.add_option('--print-dexsegments',
631 metavar='BENCHMARKNAME',
632 help='Print the sizes of individual dex segments as ' +
633 '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
634 '<bytes>\'')
635 result.add_option('--quiet',
636 help='Disable verbose logging',
637 default=False,
638 action='store_true')
639 result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
640 help='Number of times R8 should be run on each app',
641 default=2,
642 type=int)
643 result.add_option('--run-tests', '--run_tests',
644 help='Whether to run instrumentation tests',
645 default=False,
646 action='store_true')
647 result.add_option('--sign-apks', '--sign_apks',
648 help='Whether the APKs should be signed',
649 default=False,
650 action='store_true')
651 result.add_option('--shrinker',
652 help='The shrinkers to use (by default, all are run)',
653 action='append')
654 result.add_option('--version',
655 help='The version of R8 to use (e.g., 1.4.51)')
656 (options, args) = result.parse_args(argv)
657 if options.app:
658 options.apps = [app for app in APPS if app.name in options.app]
659 del options.app
660 else:
661 options.apps = APPS
662 if options.app_logging_filter:
663 for app_name in options.app_logging_filter:
664 assert any(app.name == app_name for app in options.apps)
665 if options.shrinker:
666 for shrinker in options.shrinker:
667 assert shrinker in SHRINKERS
668 else:
669 options.shrinker = [shrinker for shrinker in SHRINKERS]
670
671 if options.hash or options.version:
672 # No need to build R8 if a specific version should be used.
673 options.no_build = True
674 if 'r8-nolib' in options.shrinker:
675 warn('Skipping shrinker r8-nolib because a specific version '
676 + 'of r8 was specified')
677 options.shrinker.remove('r8-nolib')
678 if 'r8-nolib-full' in options.shrinker:
679 warn('Skipping shrinker r8-nolib-full because a specific version '
680 + 'of r8 was specified')
681 options.shrinker.remove('r8-nolib-full')
682 return (options, args)
683
684
685def main(argv):
686 (options, args) = parse_options(argv)
687
688 if options.bot:
689 options.no_logging = True
690 options.shrinker = ['r8', 'r8-full']
691 print(options.shrinker)
692
693 if options.golem:
694 golem.link_third_party()
695 options.disable_assertions = True
696 options.no_build = True
697 options.r8_compilation_steps = 1
698 options.quiet = True
699 options.no_logging = True
700
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100701 with utils.TempDir() as temp_dir:
702 if options.hash:
703 # Download r8-<hash>.jar from
704 # https://storage.googleapis.com/r8-releases/raw/.
705 target = 'r8-{}.jar'.format(options.hash)
706 update_prebuilds_in_android.download_hash(
707 temp_dir, 'com/android/tools/r8/' + options.hash, target)
708 as_utils.MoveFile(
709 os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
710 quiet=options.quiet)
711 elif options.version:
712 # Download r8-<version>.jar from
713 # https://storage.googleapis.com/r8-releases/raw/.
714 target = 'r8-{}.jar'.format(options.version)
715 update_prebuilds_in_android.download_version(
716 temp_dir, 'com/android/tools/r8/' + options.version, target)
717 as_utils.MoveFile(
718 os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
719 quiet=options.quiet)
720 else:
721 if not (options.no_build or options.golem):
722 gradle.RunGradle(['r8', '-Pno_internal'])
723 build_r8lib = False
724 for shrinker in options.shrinker:
725 if is_minified_r8(shrinker):
726 build_r8lib = True
727 if build_r8lib:
728 gradle.RunGradle(['r8lib', '-Pno_internal'])
729 # Make a copy of r8.jar and r8lib.jar such that they stay the same for
730 # the entire execution of this script.
731 if 'r8-nolib' in options.shrinker or 'r8-nolib-full' in options.shrinker:
732 assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
733 shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
734 if 'r8' in options.shrinker or 'r8-full' in options.shrinker:
735 assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
736 shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
737
738 result_per_shrinker_per_app = []
739 for app in options.apps:
740 if app.skip:
741 continue
742 result_per_shrinker_per_app.append(
743 (app, get_results_for_app(app, options, temp_dir)))
744 return log_results_for_apps(result_per_shrinker_per_app, options)
745
746
747def success(message):
748 CGREEN = '\033[32m'
749 CEND = '\033[0m'
750 print(CGREEN + message + CEND)
751
752
753def warn(message):
754 CRED = '\033[91m'
755 CEND = '\033[0m'
756 print(CRED + message + CEND)
757
758
759if __name__ == '__main__':
760 sys.exit(main(sys.argv[1:]))