blob: 10dfbf220152cf2b22e582335f033c2ebcff34df [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 }),
Morten Krogh-Jespersenf8e77032020-11-09 23:44:58 +0100195 # TODO(b/172806281): Monkey runner does not work.
196 App({
197 'id': 'org.schabi.newpipe',
198 'name': 'NewPipe',
199 'dump_app': 'dump_app.zip',
200 'apk_app': 'app-release-unsigned.apk',
201 'url': 'https://github.com/TeamNewPipe/NewPipe',
202 'revision': 'f4435f90313281beece70c544032f784418d85fa',
203 'folder': 'newpipe',
204 # TODO(b/172805505): Recompilation fails
205 'skip_recompilation': True,
206 }),
Morten Krogh-Jespersen3e3781f2020-11-09 23:44:33 +0100207 App({
Morten Krogh-Jespersen90912fd2020-11-05 09:21:16 +0100208 'id': 'org.wikipedia',
209 'name': 'Wikipedia',
210 'dump_app': 'dump_app.zip',
211 'apk_app': 'app-prod-release.apk',
212 'url': 'https://github.com/wikimedia/apps-android-wikipedia',
213 'revision': '0fa7cad843c66313be8e25790ef084cf1a1fa67e',
214 'folder': 'wikipedia',
215 }),
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100216]
217
Morten Krogh-Jespersenf8e77032020-11-09 23:44:58 +0100218
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100219def remove_print_lines(file):
220 with open(file) as f:
221 lines = f.readlines()
222 with open(file, 'w') as f:
223 for line in lines:
224 if '-printconfiguration' not in line:
225 f.write(line)
226
227
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100228def download_app(app_sha):
229 utils.DownloadFromGoogleCloudStorage(app_sha)
230
231
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100232def is_logging_enabled_for(app, options):
233 if options.no_logging:
234 return False
235 if options.app_logging_filter and app.name not in options.app_logging_filter:
236 return False
237 return True
238
239
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100240def is_minified_r8(shrinker):
241 return '-nolib' not in shrinker
242
243
244def is_full_r8(shrinker):
245 return '-full' not in shrinker
246
247
248def compute_size_of_dex_files_in_package(path):
249 dex_size = 0
250 z = zipfile.ZipFile(path, 'r')
251 for filename in z.namelist():
252 if filename.endswith('.dex'):
253 dex_size += z.getinfo(filename).file_size
254 return dex_size
255
256
257def dump_for_app(app_dir, app):
258 return os.path.join(app_dir, app.dump_app)
259
260
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100261def dump_test_for_app(app_dir, app):
262 return os.path.join(app_dir, app.dump_test)
263
264
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100265def get_results_for_app(app, options, temp_dir):
266 app_folder = app.folder if app.folder else app.name + "_" + app.revision
267 app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
268
269 if not os.path.exists(app_dir) and not options.golem:
270 # Download the app from google storage.
271 download_app(app_dir + ".tar.gz.sha1")
272
273 # Ensure that the dumps are in place
274 assert os.path.isfile(dump_for_app(app_dir, app)), "Could not find dump " \
275 "for app " + app.name
276
277 result = {}
278 result['status'] = 'success'
279 result_per_shrinker = build_app_with_shrinkers(
280 app, options, temp_dir, app_dir)
281 for shrinker, shrinker_result in result_per_shrinker.iteritems():
282 result[shrinker] = shrinker_result
283 return result
284
285
286def build_app_with_shrinkers(app, options, temp_dir, app_dir):
287 result_per_shrinker = {}
288 for shrinker in options.shrinker:
289 results = []
290 build_app_and_run_with_shrinker(
291 app, options, temp_dir, app_dir, shrinker, results)
292 result_per_shrinker[shrinker] = results
293 if len(options.apps) > 1:
294 print('')
295 log_results_for_app(app, result_per_shrinker, options)
296 print('')
297
298 return result_per_shrinker
299
300
301def is_last_build(index, compilation_steps):
302 return index == compilation_steps - 1
303
304
305def build_app_and_run_with_shrinker(app, options, temp_dir, app_dir, shrinker,
306 results):
307 print('[{}] Building {} with {}'.format(
308 datetime.now().strftime("%H:%M:%S"),
309 app.name,
310 shrinker))
311 print('To compile locally: '
312 'tools/run_on_as_app.py --shrinker {} --r8-compilation-steps {} '
313 '--app {}'.format(
314 shrinker,
315 options.r8_compilation_steps,
316 app.name))
317 print('HINT: use --shrinker r8-nolib --no-build if you have a local R8.jar')
318 recomp_jar = None
319 status = 'success'
320 compilation_steps = 1 if app.skip_recompilation else options.r8_compilation_steps;
321 for compilation_step in range(0, compilation_steps):
322 if status != 'success':
323 break
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100324 print('Compiling {} of {}'.format(compilation_step + 1, compilation_steps))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100325 result = {}
326 try:
327 start = time.time()
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100328 (app_jar, mapping, new_recomp_jar) = \
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100329 build_app_with_shrinker(
330 app, options, temp_dir, app_dir, shrinker, compilation_step,
331 compilation_steps, recomp_jar)
332 end = time.time()
333 dex_size = compute_size_of_dex_files_in_package(app_jar)
334 result['build_status'] = 'success'
335 result['recompilation_status'] = 'success'
336 result['output_jar'] = app_jar
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100337 result['output_mapping'] = mapping
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100338 result['dex_size'] = dex_size
339 result['duration'] = int((end - start) * 1000) # Wall time
340 if (new_recomp_jar is None
341 and not is_last_build(compilation_step, compilation_steps)):
342 result['recompilation_status'] = 'failed'
343 warn('Failed to build {} with {}'.format(app.name, shrinker))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100344 recomp_jar = new_recomp_jar
345 except Exception as e:
346 warn('Failed to build {} with {}'.format(app.name, shrinker))
347 if e:
348 print('Error: ' + str(e))
349 result['build_status'] = 'failed'
350 status = 'failed'
351
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100352 original_app_apk = os.path.join(app_dir, app.apk_app)
353 app_apk_destination = os.path.join(
354 temp_dir,"{}_{}.apk".format(app.id, compilation_step))
355
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100356 if result.get('build_status') == 'success' and options.monkey:
357 # Make a copy of the given APK, move the newly generated dex files into the
358 # copied APK, and then sign the APK.
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100359 apk_masseur.masseur(
360 original_app_apk, dex=app_jar, resources='META-INF/services/*',
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100361 out=app_apk_destination,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100362 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
363 keystore=options.keystore)
364
365 result['monkey_status'] = 'success' if adb.run_monkey(
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100366 app.id, options.emulator_id, app_apk_destination, options.monkey_events,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100367 options.quiet, is_logging_enabled_for(app, options)) else 'failed'
368
Morten Krogh-Jespersen571cfe72020-11-09 23:44:03 +0100369 if (result.get('build_status') == 'success'
370 and options.run_tests and app.dump_test):
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100371 if not os.path.isfile(app_apk_destination):
372 apk_masseur.masseur(
373 original_app_apk, dex=app_jar, resources='META-INF/services/*',
374 out=app_apk_destination,
375 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
376 keystore=options.keystore)
377
378 # Compile the tests with the mapping file.
379 test_jar = build_test_with_shrinker(
380 app, options, temp_dir, app_dir,shrinker, compilation_step,
381 result['output_mapping'])
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100382 if not test_jar:
383 result['instrumentation_test_status'] = 'compilation_failed'
384 else:
385 original_test_apk = os.path.join(app_dir, app.apk_test)
386 test_apk_destination = os.path.join(
387 temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
388 apk_masseur.masseur(
389 original_test_apk, dex=test_jar, resources='META-INF/services/*',
390 out=test_apk_destination,
391 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
392 keystore=options.keystore)
393 result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
394 app.id, app.id_test, options.emulator_id, app_apk_destination,
395 test_apk_destination, options.quiet,
396 is_logging_enabled_for(app, options)) else 'failed'
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100397
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100398 results.append(result)
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100399 if result.get('recompilation_status') != 'success':
400 break
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100401
402
403def build_app_with_shrinker(app, options, temp_dir, app_dir, shrinker,
404 compilation_step_index, compilation_steps,
405 prev_recomp_jar):
406 r8jar = os.path.join(
407 temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
408
409 args = AttrDict({
410 'dump': dump_for_app(app_dir, app),
411 'r8_jar': r8jar,
412 'ea': False if options.disable_assertions else True,
413 'version': 'master',
414 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
415 'debug_agent': options.debug_agent,
416 'program_jar': prev_recomp_jar,
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100417 'nolib': not is_minified_r8(shrinker),
418 'config_file_consumer': remove_print_lines,
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100419 })
420
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100421 compile_result = compiledump.run1(temp_dir, args, [])
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100422
423 out_jar = os.path.join(temp_dir, "out.jar")
424 out_mapping = os.path.join(temp_dir, "out.jar.map")
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100425 app_jar = os.path.join(
426 temp_dir, '{}_{}_{}_dex_out.jar'.format(
427 app.name, shrinker, compilation_step_index))
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100428 app_mapping = os.path.join(
429 temp_dir, '{}_{}_{}_dex_out.jar.map'.format(
430 app.name, shrinker, compilation_step_index))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100431
432 if compile_result != 0 or not os.path.isfile(out_jar):
433 assert False, "Compilation of app_jar failed"
434 shutil.move(out_jar, app_jar)
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100435 shutil.move(out_mapping, app_mapping)
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100436
437 recomp_jar = None
438 if compilation_step_index < compilation_steps - 1:
439 args['classfile'] = True
440 args['min_api'] = "10000"
441 compile_result = compiledump.run1(temp_dir, args, [])
442 if compile_result == 0:
443 recomp_jar = os.path.join(
444 temp_dir, '{}_{}_{}_cf_out.jar'.format(
445 app.name, shrinker, compilation_step_index))
446 shutil.move(out_jar, recomp_jar)
447
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100448 return (app_jar, app_mapping, recomp_jar)
449
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100450
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100451def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
452 compilation_step_index, mapping):
453 r8jar = os.path.join(
454 temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
455
456 def rewrite_file(file):
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100457 remove_print_lines(file)
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100458 with open(file) as f:
459 lines = f.readlines()
460 with open(file, 'w') as f:
461 for line in lines:
462 if '-applymapping' not in line:
463 f.write(line + '\n')
464 f.write("-applymapping " + mapping + '\n')
465
466 args = AttrDict({
467 'dump': dump_test_for_app(app_dir, app),
468 'r8_jar': r8jar,
469 'ea': False if options.disable_assertions else True,
470 'version': 'master',
471 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
472 'debug_agent': options.debug_agent,
473 'nolib': not is_minified_r8(shrinker),
474 # The config file will have an -applymapping reference to an old map.
475 # Update it to point to mapping file build in the compilation of the app.
476 'config_file_consumer': rewrite_file
477 })
478
479 compile_result = compiledump.run1(temp_dir, args, [])
480
481 out_jar = os.path.join(temp_dir, "out.jar")
482 test_jar = os.path.join(
483 temp_dir, '{}_{}_{}_test_out.jar'.format(
484 app.name, shrinker, compilation_step_index))
485
486 if compile_result != 0 or not os.path.isfile(out_jar):
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100487 return None
488
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100489 shutil.move(out_jar, test_jar)
490
491 return test_jar
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100492
493
494def log_results_for_apps(result_per_shrinker_per_app, options):
495 print('')
496 app_errors = 0
497 for (app, result_per_shrinker) in result_per_shrinker_per_app:
498 app_errors += (1 if log_results_for_app(app, result_per_shrinker, options)
499 else 0)
500 return app_errors
501
502
503def log_results_for_app(app, result_per_shrinker, options):
504 if options.print_dexsegments:
505 log_segments_for_app(app, result_per_shrinker, options)
506 return False
507 else:
508 return log_comparison_results_for_app(app, result_per_shrinker, options)
509
510
511def log_segments_for_app(app, result_per_shrinker, options):
512 for shrinker in SHRINKERS:
513 if shrinker not in result_per_shrinker:
514 continue
515 for result in result_per_shrinker.get(shrinker):
516 benchmark_name = '{}-{}'.format(options.print_dexsegments, app.name)
517 utils.print_dexsegments(benchmark_name, [result.get('output_jar')])
518 duration = result.get('duration')
519 print('%s-Total(RunTimeRaw): %s ms' % (benchmark_name, duration))
520 print('%s-Total(CodeSize): %s' % (benchmark_name, result.get('dex_size')))
521
522
523def percentage_diff_as_string(before, after):
524 if after < before:
525 return '-' + str(round((1.0 - after / before) * 100)) + '%'
526 else:
527 return '+' + str(round((after - before) / before * 100)) + '%'
528
529
530def log_comparison_results_for_app(app, result_per_shrinker, options):
531 print(app.name + ':')
532 app_error = False
533 if result_per_shrinker.get('status', 'success') != 'success':
534 error_message = result_per_shrinker.get('error_message')
535 print(' skipped ({})'.format(error_message))
536 return
537
538 proguard_result = result_per_shrinker.get('pg', {})
539 proguard_dex_size = float(proguard_result.get('dex_size', -1))
540
541 for shrinker in SHRINKERS:
542 if shrinker not in result_per_shrinker:
543 continue
544 compilation_index = 1
545 for result in result_per_shrinker.get(shrinker):
546 build_status = result.get('build_status')
547 if build_status != 'success' and build_status is not None:
548 app_error = True
549 warn(' {}-#{}: {}'.format(shrinker, compilation_index, build_status))
550 continue
551
552 print(' {}-#{}:'.format(shrinker, compilation_index))
553 dex_size = result.get('dex_size')
554 msg = ' dex size: {}'.format(dex_size)
555 if dex_size != proguard_dex_size and proguard_dex_size >= 0:
556 msg = '{} ({}, {})'.format(
557 msg, dex_size - proguard_dex_size,
558 percentage_diff_as_string(proguard_dex_size, dex_size))
559 success(msg) if dex_size < proguard_dex_size else warn(msg)
560 else:
561 print(msg)
562
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100563 if options.monkey:
564 monkey_status = result.get('monkey_status')
565 if monkey_status != 'success':
566 app_error = True
567 warn(' monkey: {}'.format(monkey_status))
568 else:
569 success(' monkey: {}'.format(monkey_status))
570
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100571 if options.run_tests and 'instrumentation_test_status' in result:
572 test_status = result.get('instrumentation_test_status')
573 if test_status != 'success':
574 warn(' instrumentation_tests: {}'.format(test_status))
575 else:
576 success(' instrumentation_tests: {}'.format(test_status))
577
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100578 recompilation_status = result.get('recompilation_status', '')
579 if recompilation_status == 'failed':
580 app_error = True
581 warn(' recompilation {}-#{}: failed'.format(shrinker,
582 compilation_index))
583 continue
584
585 compilation_index += 1
586
587 return app_error
588
589
590def parse_options(argv):
591 result = optparse.OptionParser()
592 result.add_option('--app',
593 help='What app to run on',
594 choices=[app.name for app in APPS],
595 action='append')
596 result.add_option('--bot',
597 help='Running on bot, use third_party dependency.',
598 default=False,
599 action='store_true')
600 result.add_option('--debug-agent',
601 help='Enable Java debug agent and suspend compilation '
602 '(default disabled)',
603 default=False,
604 action='store_true')
605 result.add_option('--disable-assertions', '--disable_assertions',
606 help='Disable assertions when compiling',
607 default=False,
608 action='store_true')
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100609 result.add_option('--emulator-id', '--emulator_id',
610 help='Id of the emulator to use',
611 default='emulator-5554')
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100612 result.add_option('--golem',
613 help='Running on golem, do not download',
614 default=False,
615 action='store_true')
616 result.add_option('--hash',
617 help='The commit of R8 to use')
618 result.add_option('--keystore',
619 help='Path to app.keystore',
620 default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
621 result.add_option('--keystore-password', '--keystore_password',
622 help='Password for app.keystore',
623 default='android')
624 result.add_option('--app-logging-filter', '--app_logging_filter',
625 help='The apps for which to turn on logging',
626 action='append')
627 result.add_option('--monkey',
628 help='Whether to install and run app(s) with monkey',
629 default=False,
630 action='store_true')
631 result.add_option('--monkey-events', '--monkey_events',
632 help='Number of events that the monkey should trigger',
633 default=250,
634 type=int)
635 result.add_option('--no-build', '--no_build',
636 help='Run without building ToT first (only when using ToT)',
637 default=False,
638 action='store_true')
639 result.add_option('--no-logging', '--no_logging',
640 help='Disable logging except for errors',
641 default=False,
642 action='store_true')
643 result.add_option('--print-dexsegments',
644 metavar='BENCHMARKNAME',
645 help='Print the sizes of individual dex segments as ' +
646 '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
647 '<bytes>\'')
648 result.add_option('--quiet',
649 help='Disable verbose logging',
650 default=False,
651 action='store_true')
652 result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
653 help='Number of times R8 should be run on each app',
654 default=2,
655 type=int)
656 result.add_option('--run-tests', '--run_tests',
657 help='Whether to run instrumentation tests',
658 default=False,
659 action='store_true')
660 result.add_option('--sign-apks', '--sign_apks',
661 help='Whether the APKs should be signed',
662 default=False,
663 action='store_true')
664 result.add_option('--shrinker',
665 help='The shrinkers to use (by default, all are run)',
666 action='append')
667 result.add_option('--version',
668 help='The version of R8 to use (e.g., 1.4.51)')
669 (options, args) = result.parse_args(argv)
670 if options.app:
671 options.apps = [app for app in APPS if app.name in options.app]
672 del options.app
673 else:
674 options.apps = APPS
675 if options.app_logging_filter:
676 for app_name in options.app_logging_filter:
677 assert any(app.name == app_name for app in options.apps)
678 if options.shrinker:
679 for shrinker in options.shrinker:
680 assert shrinker in SHRINKERS
681 else:
682 options.shrinker = [shrinker for shrinker in SHRINKERS]
683
684 if options.hash or options.version:
685 # No need to build R8 if a specific version should be used.
686 options.no_build = True
687 if 'r8-nolib' in options.shrinker:
688 warn('Skipping shrinker r8-nolib because a specific version '
689 + 'of r8 was specified')
690 options.shrinker.remove('r8-nolib')
691 if 'r8-nolib-full' in options.shrinker:
692 warn('Skipping shrinker r8-nolib-full because a specific version '
693 + 'of r8 was specified')
694 options.shrinker.remove('r8-nolib-full')
695 return (options, args)
696
697
698def main(argv):
699 (options, args) = parse_options(argv)
700
701 if options.bot:
702 options.no_logging = True
703 options.shrinker = ['r8', 'r8-full']
704 print(options.shrinker)
705
706 if options.golem:
707 golem.link_third_party()
708 options.disable_assertions = True
709 options.no_build = True
710 options.r8_compilation_steps = 1
711 options.quiet = True
712 options.no_logging = True
713
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100714 with utils.TempDir() as temp_dir:
715 if options.hash:
716 # Download r8-<hash>.jar from
717 # https://storage.googleapis.com/r8-releases/raw/.
718 target = 'r8-{}.jar'.format(options.hash)
719 update_prebuilds_in_android.download_hash(
720 temp_dir, 'com/android/tools/r8/' + options.hash, target)
721 as_utils.MoveFile(
722 os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
723 quiet=options.quiet)
724 elif options.version:
725 # Download r8-<version>.jar from
726 # https://storage.googleapis.com/r8-releases/raw/.
727 target = 'r8-{}.jar'.format(options.version)
728 update_prebuilds_in_android.download_version(
729 temp_dir, 'com/android/tools/r8/' + options.version, target)
730 as_utils.MoveFile(
731 os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
732 quiet=options.quiet)
733 else:
734 if not (options.no_build or options.golem):
735 gradle.RunGradle(['r8', '-Pno_internal'])
736 build_r8lib = False
737 for shrinker in options.shrinker:
738 if is_minified_r8(shrinker):
739 build_r8lib = True
740 if build_r8lib:
741 gradle.RunGradle(['r8lib', '-Pno_internal'])
742 # Make a copy of r8.jar and r8lib.jar such that they stay the same for
743 # the entire execution of this script.
744 if 'r8-nolib' in options.shrinker or 'r8-nolib-full' in options.shrinker:
745 assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
746 shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
747 if 'r8' in options.shrinker or 'r8-full' in options.shrinker:
748 assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
749 shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
750
751 result_per_shrinker_per_app = []
752 for app in options.apps:
753 if app.skip:
754 continue
755 result_per_shrinker_per_app.append(
756 (app, get_results_for_app(app, options, temp_dir)))
757 return log_results_for_apps(result_per_shrinker_per_app, options)
758
759
760def success(message):
761 CGREEN = '\033[32m'
762 CEND = '\033[0m'
763 print(CGREEN + message + CEND)
764
765
766def warn(message):
767 CRED = '\033[91m'
768 CEND = '\033[0m'
769 print(CRED + message + CEND)
770
771
772if __name__ == '__main__':
773 sys.exit(main(sys.argv[1:]))