blob: 5c7468d5e118644f71c92f26a6352d43126801e1 [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-Jespersendf700642020-11-09 23:45:25 +0100207 # TODO(b/172806808): Monkey runner does not work.
208 App({
209 'id': 'io.rover.app.debug',
210 'name': 'Rover',
211 'dump_app': 'dump_app.zip',
212 'apk_app': 'example-app-release-unsigned.apk',
213 'url': 'https://github.com/RoverPlatform/rover-android',
214 'revision': '94342117097770ea3ca2c6df6ab496a1a55c3ce7',
215 'folder': 'rover-android',
216 }),
Morten Krogh-Jespersen3e3781f2020-11-09 23:44:33 +0100217 App({
Morten Krogh-Jespersen2f1be952020-11-09 23:45:48 +0100218 'id': 'io.rover.app.debug',
219 'name': 'Rover',
220 'dump_app': 'dump_app.zip',
221 'apk_app': 'example-app-release-unsigned.apk',
222 'url': 'https://github.com/RoverPlatform/rover-android',
223 'revision': '94342117097770ea3ca2c6df6ab496a1a55c3ce7',
224 'folder': 'rover-android',
225 }),
226 # TODO(b/172808159): Monkey runner does not work
227 App({
228 'id': 'com.google.android.apps.santatracker',
229 'name': 'SantaTracker',
230 'dump_app': 'dump_app.zip',
231 'apk_app': 'santa-tracker-release.apk',
232 'url': 'https://github.com/christofferqa/santa-tracker-android',
233 'revision': '8dee74be7d9ee33c69465a07088c53087d24a6dd',
234 'folder': 'santa-tracker',
235 }),
236 App({
Morten Krogh-Jespersen88cc0772020-11-09 23:46:13 +0100237 'id': 'org.thoughtcrime.securesms',
238 'name': 'Signal',
239 'dump_app': 'dump_app.zip',
240 'apk_app': 'Signal-Android-play-prod-universal-release-4.76.2.apk',
241 # TODO(b/172812839): Instrumentation test fails.
242 'id_test': 'org.thoughtcrime.securesms.test',
243 'dump_test': 'dump_test.zip',
244 'apk_test': 'Signal-Android-play-prod-release-androidTest.apk',
245 'url': 'https://github.com/signalapp/Signal-Android',
246 'revision': '91ca19f294362ccee2c2b43c247eba228e2b30a1',
247 'folder': 'signal-android',
248 }),
Morten Krogh-Jespersen733bcff2020-11-09 23:46:43 +0100249 # TODO(b/172815827): Monkey runner does not work
250 App({
251 'id': 'com.simplemobiletools.calendar.pro',
252 'name': 'Simple-Calendar',
253 'dump_app': 'dump_app.zip',
254 'apk_app': 'calendar-release.apk',
255 'url': 'https://github.com/SimpleMobileTools/Simple-Calendar',
256 'revision': '906209874d0a091c7fce5a57972472f272d6b068',
257 'folder': 'simple-calendar',
258 }),
Morten Krogh-Jespersen8a564d32020-11-09 23:47:07 +0100259 # TODO(b/172815534): Monkey runner does not work
260 App({
261 'id': 'com.simplemobiletools.camera.pro',
262 'name': 'Simple-Camera',
263 'dump_app': 'dump_app.zip',
264 'apk_app': 'camera-release.apk',
265 'url': 'https://github.com/SimpleMobileTools/Simple-Camera',
266 'revision': 'ebf9820c51e960912b3238287e30a131244fdee6',
267 'folder': 'simple-camera',
268 }),
Morten Krogh-Jespersen88cc0772020-11-09 23:46:13 +0100269 App({
Morten Krogh-Jespersen805e31a2020-11-09 23:47:32 +0100270 'id': 'com.simplemobiletools.filemanager.pro',
271 'name': 'Simple-File-Manager',
272 'dump_app': 'dump_app.zip',
273 'apk_app': 'file-manager-release.apk',
274 'url': 'https://github.com/SimpleMobileTools/Simple-File-Manager',
275 'revision': '2b7fa68ea251222cc40cf6d62ad1de260a6f54d9',
276 'folder': 'simple-file-manager',
277 }),
278 App({
Morten Krogh-Jespersendc29e992020-11-09 23:48:01 +0100279 'id': 'com.simplemobiletools.gallery.pro',
280 'name': 'Simple-Gallery',
281 'dump_app': 'dump_app.zip',
282 'apk_app': 'gallery-326-foss-release.apk',
283 'url': 'https://github.com/SimpleMobileTools/Simple-Gallery',
284 'revision': '564e56b20d33b28d0018c8087ec705beeb60785e',
285 'folder': 'simple-gallery',
286 # TODO(b/172818477): Cannot recompile.
287 'skip_recompilation': True,
288 }),
289 App({
Morten Krogh-Jespersen4a9657a2020-11-09 23:48:21 +0100290 'id': 'com.example.sqldelight.hockey',
291 'name': 'SQLDelight',
292 'dump_app': 'dump_app.zip',
293 'apk_app': 'android-release.apk',
294 'url': 'https://github.com/christofferqa/sqldelight',
295 'revision': '2e67a1126b6df05e4119d1e3a432fde51d76cdc8',
296 'folder': 'sqldelight',
297 }),
Morten Krogh-Jespersenf12a57e2020-11-09 23:48:53 +0100298 # TODO(b/172824096): Monkey runner does not work.
299 App({
300 'id': 'eu.kanade.tachiyomi',
301 'name': 'Tachiyomi',
302 'dump_app': 'dump_app.zip',
303 'apk_app': 'app-dev-release.apk',
304 'url': 'https://github.com/inorichi/tachiyomi',
305 'revision': '8aa6486bf76ab9a61a5494bee284b1a5e9180bf3',
306 'folder': 'tachiyomi',
307 }),
Morten Krogh-Jespersen58974522020-11-10 00:20:46 +0100308 # TODO(b/172862042): Monkey runner does not work.
309 App({
310 'id': 'app.tivi',
311 'name': 'Tivi',
312 'dump_app': 'dump_app.zip',
313 'apk_app': 'app-release.apk',
314 'url': 'https://github.com/chrisbanes/tivi',
315 'revision': '8e2ddd8fe2d343264a66aa1ef8acbd4cc587e8ce',
316 'folder': 'tivi',
317 # TODO(b/172859303): Fix recompilation
318 'skip_recompilation': True
319 }),
Morten Krogh-Jespersen4a9657a2020-11-09 23:48:21 +0100320 App({
Morten Krogh-Jespersen9033d1a2020-11-09 23:49:23 +0100321 'id': 'com.keylesspalace.tusky',
322 'name': 'Tusky',
323 'dump_app': 'dump_app.zip',
324 'apk_app': 'app-blue-release.apk',
325 'url': 'https://github.com/tuskyapp/Tusky',
326 'revision': '814a9b8f9bacf8d26f712b06a0313a3534a2be95',
327 'folder': 'tusky',
328 # TODO(b/172856713): Fix compilation
329 'skip': True,
330 }),
331 App({
Morten Krogh-Jespersen90912fd2020-11-05 09:21:16 +0100332 'id': 'org.wikipedia',
333 'name': 'Wikipedia',
334 'dump_app': 'dump_app.zip',
335 'apk_app': 'app-prod-release.apk',
336 'url': 'https://github.com/wikimedia/apps-android-wikipedia',
337 'revision': '0fa7cad843c66313be8e25790ef084cf1a1fa67e',
338 'folder': 'wikipedia',
339 }),
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100340]
341
Morten Krogh-Jespersenf8e77032020-11-09 23:44:58 +0100342
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100343def remove_print_lines(file):
344 with open(file) as f:
345 lines = f.readlines()
346 with open(file, 'w') as f:
347 for line in lines:
348 if '-printconfiguration' not in line:
349 f.write(line)
350
351
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100352def download_app(app_sha):
353 utils.DownloadFromGoogleCloudStorage(app_sha)
354
355
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100356def is_logging_enabled_for(app, options):
357 if options.no_logging:
358 return False
359 if options.app_logging_filter and app.name not in options.app_logging_filter:
360 return False
361 return True
362
363
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100364def is_minified_r8(shrinker):
365 return '-nolib' not in shrinker
366
367
368def is_full_r8(shrinker):
Morten Krogh-Jespersen270d0932020-11-11 11:05:57 +0100369 return '-full' in shrinker
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100370
371
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100372def version_is_built_jar(version):
373 return version != 'master' and version != 'source'
374
375
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100376def compute_size_of_dex_files_in_package(path):
377 dex_size = 0
378 z = zipfile.ZipFile(path, 'r')
379 for filename in z.namelist():
380 if filename.endswith('.dex'):
381 dex_size += z.getinfo(filename).file_size
382 return dex_size
383
384
385def dump_for_app(app_dir, app):
386 return os.path.join(app_dir, app.dump_app)
387
388
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100389def dump_test_for_app(app_dir, app):
390 return os.path.join(app_dir, app.dump_test)
391
392
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100393def get_r8_jar(options, temp_dir, shrinker):
394 if (options.version == 'source'):
395 return None
396 return os.path.join(
397 temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
398
399
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100400def get_results_for_app(app, options, temp_dir):
401 app_folder = app.folder if app.folder else app.name + "_" + app.revision
402 app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
403
404 if not os.path.exists(app_dir) and not options.golem:
405 # Download the app from google storage.
406 download_app(app_dir + ".tar.gz.sha1")
407
408 # Ensure that the dumps are in place
409 assert os.path.isfile(dump_for_app(app_dir, app)), "Could not find dump " \
410 "for app " + app.name
411
412 result = {}
413 result['status'] = 'success'
414 result_per_shrinker = build_app_with_shrinkers(
415 app, options, temp_dir, app_dir)
416 for shrinker, shrinker_result in result_per_shrinker.iteritems():
417 result[shrinker] = shrinker_result
418 return result
419
420
421def build_app_with_shrinkers(app, options, temp_dir, app_dir):
422 result_per_shrinker = {}
423 for shrinker in options.shrinker:
424 results = []
425 build_app_and_run_with_shrinker(
426 app, options, temp_dir, app_dir, shrinker, results)
427 result_per_shrinker[shrinker] = results
428 if len(options.apps) > 1:
429 print('')
430 log_results_for_app(app, result_per_shrinker, options)
431 print('')
432
433 return result_per_shrinker
434
435
436def is_last_build(index, compilation_steps):
437 return index == compilation_steps - 1
438
439
440def build_app_and_run_with_shrinker(app, options, temp_dir, app_dir, shrinker,
441 results):
442 print('[{}] Building {} with {}'.format(
443 datetime.now().strftime("%H:%M:%S"),
444 app.name,
445 shrinker))
446 print('To compile locally: '
Morten Krogh-Jespersenf098b422020-11-11 13:53:52 +0100447 'tools/run_on_app_dump.py --shrinker {} --r8-compilation-steps {} '
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100448 '--app {}'.format(
449 shrinker,
450 options.r8_compilation_steps,
451 app.name))
452 print('HINT: use --shrinker r8-nolib --no-build if you have a local R8.jar')
453 recomp_jar = None
454 status = 'success'
455 compilation_steps = 1 if app.skip_recompilation else options.r8_compilation_steps;
456 for compilation_step in range(0, compilation_steps):
457 if status != 'success':
458 break
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100459 print('Compiling {} of {}'.format(compilation_step + 1, compilation_steps))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100460 result = {}
461 try:
462 start = time.time()
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100463 (app_jar, mapping, new_recomp_jar) = \
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100464 build_app_with_shrinker(
465 app, options, temp_dir, app_dir, shrinker, compilation_step,
466 compilation_steps, recomp_jar)
467 end = time.time()
468 dex_size = compute_size_of_dex_files_in_package(app_jar)
469 result['build_status'] = 'success'
470 result['recompilation_status'] = 'success'
471 result['output_jar'] = app_jar
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100472 result['output_mapping'] = mapping
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100473 result['dex_size'] = dex_size
474 result['duration'] = int((end - start) * 1000) # Wall time
475 if (new_recomp_jar is None
476 and not is_last_build(compilation_step, compilation_steps)):
477 result['recompilation_status'] = 'failed'
478 warn('Failed to build {} with {}'.format(app.name, shrinker))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100479 recomp_jar = new_recomp_jar
480 except Exception as e:
481 warn('Failed to build {} with {}'.format(app.name, shrinker))
482 if e:
483 print('Error: ' + str(e))
484 result['build_status'] = 'failed'
485 status = 'failed'
486
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100487 original_app_apk = os.path.join(app_dir, app.apk_app)
488 app_apk_destination = os.path.join(
489 temp_dir,"{}_{}.apk".format(app.id, compilation_step))
490
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100491 if result.get('build_status') == 'success' and options.monkey:
492 # Make a copy of the given APK, move the newly generated dex files into the
493 # copied APK, and then sign the APK.
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100494 apk_masseur.masseur(
495 original_app_apk, dex=app_jar, resources='META-INF/services/*',
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100496 out=app_apk_destination,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100497 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
498 keystore=options.keystore)
499
500 result['monkey_status'] = 'success' if adb.run_monkey(
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100501 app.id, options.emulator_id, app_apk_destination, options.monkey_events,
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100502 options.quiet, is_logging_enabled_for(app, options)) else 'failed'
503
Morten Krogh-Jespersen571cfe72020-11-09 23:44:03 +0100504 if (result.get('build_status') == 'success'
505 and options.run_tests and app.dump_test):
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100506 if not os.path.isfile(app_apk_destination):
507 apk_masseur.masseur(
508 original_app_apk, dex=app_jar, resources='META-INF/services/*',
509 out=app_apk_destination,
510 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
511 keystore=options.keystore)
512
513 # Compile the tests with the mapping file.
514 test_jar = build_test_with_shrinker(
515 app, options, temp_dir, app_dir,shrinker, compilation_step,
516 result['output_mapping'])
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100517 if not test_jar:
518 result['instrumentation_test_status'] = 'compilation_failed'
519 else:
520 original_test_apk = os.path.join(app_dir, app.apk_test)
521 test_apk_destination = os.path.join(
522 temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
523 apk_masseur.masseur(
524 original_test_apk, dex=test_jar, resources='META-INF/services/*',
525 out=test_apk_destination,
526 quiet=options.quiet, logging=is_logging_enabled_for(app, options),
527 keystore=options.keystore)
528 result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
529 app.id, app.id_test, options.emulator_id, app_apk_destination,
530 test_apk_destination, options.quiet,
531 is_logging_enabled_for(app, options)) else 'failed'
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100532
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100533 results.append(result)
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100534 if result.get('recompilation_status') != 'success':
535 break
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100536
537
538def build_app_with_shrinker(app, options, temp_dir, app_dir, shrinker,
539 compilation_step_index, compilation_steps,
540 prev_recomp_jar):
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100541
542 args = AttrDict({
543 'dump': dump_for_app(app_dir, app),
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100544 'r8_jar': get_r8_jar(options, temp_dir, shrinker),
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100545 'ea': False if options.disable_assertions else True,
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100546 'version': options.version,
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100547 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
548 'debug_agent': options.debug_agent,
549 'program_jar': prev_recomp_jar,
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100550 'nolib': not is_minified_r8(shrinker),
551 'config_file_consumer': remove_print_lines,
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100552 })
553
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100554 compile_result = compiledump.run1(temp_dir, args, [])
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100555
556 out_jar = os.path.join(temp_dir, "out.jar")
557 out_mapping = os.path.join(temp_dir, "out.jar.map")
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100558 app_jar = os.path.join(
559 temp_dir, '{}_{}_{}_dex_out.jar'.format(
560 app.name, shrinker, compilation_step_index))
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100561 app_mapping = os.path.join(
562 temp_dir, '{}_{}_{}_dex_out.jar.map'.format(
563 app.name, shrinker, compilation_step_index))
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100564
565 if compile_result != 0 or not os.path.isfile(out_jar):
566 assert False, "Compilation of app_jar failed"
567 shutil.move(out_jar, app_jar)
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100568 shutil.move(out_mapping, app_mapping)
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100569
570 recomp_jar = None
571 if compilation_step_index < compilation_steps - 1:
572 args['classfile'] = True
573 args['min_api'] = "10000"
574 compile_result = compiledump.run1(temp_dir, args, [])
575 if compile_result == 0:
576 recomp_jar = os.path.join(
577 temp_dir, '{}_{}_{}_cf_out.jar'.format(
578 app.name, shrinker, compilation_step_index))
579 shutil.move(out_jar, recomp_jar)
580
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100581 return (app_jar, app_mapping, recomp_jar)
582
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100583
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100584def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
585 compilation_step_index, mapping):
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100586
587 def rewrite_file(file):
Morten Krogh-Jespersendfeb0e32020-11-04 14:55:55 +0100588 remove_print_lines(file)
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100589 with open(file) as f:
590 lines = f.readlines()
591 with open(file, 'w') as f:
592 for line in lines:
593 if '-applymapping' not in line:
594 f.write(line + '\n')
595 f.write("-applymapping " + mapping + '\n')
596
597 args = AttrDict({
598 'dump': dump_test_for_app(app_dir, app),
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100599 'r8_jar': get_r8_jar(options, temp_dir, shrinker),
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100600 'ea': False if options.disable_assertions else True,
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100601 'version': options.version,
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100602 'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
603 'debug_agent': options.debug_agent,
604 'nolib': not is_minified_r8(shrinker),
605 # The config file will have an -applymapping reference to an old map.
606 # Update it to point to mapping file build in the compilation of the app.
607 'config_file_consumer': rewrite_file
608 })
609
610 compile_result = compiledump.run1(temp_dir, args, [])
611
612 out_jar = os.path.join(temp_dir, "out.jar")
613 test_jar = os.path.join(
614 temp_dir, '{}_{}_{}_test_out.jar'.format(
615 app.name, shrinker, compilation_step_index))
616
617 if compile_result != 0 or not os.path.isfile(out_jar):
Morten Krogh-Jespersen162b3452020-11-05 13:07:10 +0100618 return None
619
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100620 shutil.move(out_jar, test_jar)
621
622 return test_jar
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100623
624
625def log_results_for_apps(result_per_shrinker_per_app, options):
626 print('')
627 app_errors = 0
628 for (app, result_per_shrinker) in result_per_shrinker_per_app:
629 app_errors += (1 if log_results_for_app(app, result_per_shrinker, options)
630 else 0)
631 return app_errors
632
633
634def log_results_for_app(app, result_per_shrinker, options):
635 if options.print_dexsegments:
636 log_segments_for_app(app, result_per_shrinker, options)
637 return False
638 else:
639 return log_comparison_results_for_app(app, result_per_shrinker, options)
640
641
642def log_segments_for_app(app, result_per_shrinker, options):
643 for shrinker in SHRINKERS:
644 if shrinker not in result_per_shrinker:
645 continue
646 for result in result_per_shrinker.get(shrinker):
647 benchmark_name = '{}-{}'.format(options.print_dexsegments, app.name)
648 utils.print_dexsegments(benchmark_name, [result.get('output_jar')])
649 duration = result.get('duration')
650 print('%s-Total(RunTimeRaw): %s ms' % (benchmark_name, duration))
651 print('%s-Total(CodeSize): %s' % (benchmark_name, result.get('dex_size')))
652
653
654def percentage_diff_as_string(before, after):
655 if after < before:
656 return '-' + str(round((1.0 - after / before) * 100)) + '%'
657 else:
658 return '+' + str(round((after - before) / before * 100)) + '%'
659
660
661def log_comparison_results_for_app(app, result_per_shrinker, options):
662 print(app.name + ':')
663 app_error = False
664 if result_per_shrinker.get('status', 'success') != 'success':
665 error_message = result_per_shrinker.get('error_message')
666 print(' skipped ({})'.format(error_message))
667 return
668
669 proguard_result = result_per_shrinker.get('pg', {})
670 proguard_dex_size = float(proguard_result.get('dex_size', -1))
671
672 for shrinker in SHRINKERS:
673 if shrinker not in result_per_shrinker:
674 continue
675 compilation_index = 1
676 for result in result_per_shrinker.get(shrinker):
677 build_status = result.get('build_status')
678 if build_status != 'success' and build_status is not None:
679 app_error = True
680 warn(' {}-#{}: {}'.format(shrinker, compilation_index, build_status))
681 continue
682
683 print(' {}-#{}:'.format(shrinker, compilation_index))
684 dex_size = result.get('dex_size')
685 msg = ' dex size: {}'.format(dex_size)
686 if dex_size != proguard_dex_size and proguard_dex_size >= 0:
687 msg = '{} ({}, {})'.format(
688 msg, dex_size - proguard_dex_size,
689 percentage_diff_as_string(proguard_dex_size, dex_size))
690 success(msg) if dex_size < proguard_dex_size else warn(msg)
691 else:
692 print(msg)
693
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100694 if options.monkey:
695 monkey_status = result.get('monkey_status')
696 if monkey_status != 'success':
697 app_error = True
698 warn(' monkey: {}'.format(monkey_status))
699 else:
700 success(' monkey: {}'.format(monkey_status))
701
Morten Krogh-Jespersen51a16352020-11-04 09:31:15 +0100702 if options.run_tests and 'instrumentation_test_status' in result:
703 test_status = result.get('instrumentation_test_status')
704 if test_status != 'success':
705 warn(' instrumentation_tests: {}'.format(test_status))
706 else:
707 success(' instrumentation_tests: {}'.format(test_status))
708
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100709 recompilation_status = result.get('recompilation_status', '')
710 if recompilation_status == 'failed':
711 app_error = True
712 warn(' recompilation {}-#{}: failed'.format(shrinker,
713 compilation_index))
714 continue
715
716 compilation_index += 1
717
718 return app_error
719
720
721def parse_options(argv):
722 result = optparse.OptionParser()
723 result.add_option('--app',
724 help='What app to run on',
725 choices=[app.name for app in APPS],
726 action='append')
727 result.add_option('--bot',
728 help='Running on bot, use third_party dependency.',
729 default=False,
730 action='store_true')
731 result.add_option('--debug-agent',
732 help='Enable Java debug agent and suspend compilation '
733 '(default disabled)',
734 default=False,
735 action='store_true')
736 result.add_option('--disable-assertions', '--disable_assertions',
737 help='Disable assertions when compiling',
738 default=False,
739 action='store_true')
Morten Krogh-Jespersencd55f812020-11-04 09:13:31 +0100740 result.add_option('--emulator-id', '--emulator_id',
741 help='Id of the emulator to use',
742 default='emulator-5554')
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100743 result.add_option('--golem',
744 help='Running on golem, do not download',
745 default=False,
746 action='store_true')
747 result.add_option('--hash',
748 help='The commit of R8 to use')
749 result.add_option('--keystore',
750 help='Path to app.keystore',
751 default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
752 result.add_option('--keystore-password', '--keystore_password',
753 help='Password for app.keystore',
754 default='android')
755 result.add_option('--app-logging-filter', '--app_logging_filter',
756 help='The apps for which to turn on logging',
757 action='append')
758 result.add_option('--monkey',
759 help='Whether to install and run app(s) with monkey',
760 default=False,
761 action='store_true')
762 result.add_option('--monkey-events', '--monkey_events',
763 help='Number of events that the monkey should trigger',
764 default=250,
765 type=int)
766 result.add_option('--no-build', '--no_build',
767 help='Run without building ToT first (only when using ToT)',
768 default=False,
769 action='store_true')
770 result.add_option('--no-logging', '--no_logging',
771 help='Disable logging except for errors',
772 default=False,
773 action='store_true')
774 result.add_option('--print-dexsegments',
775 metavar='BENCHMARKNAME',
776 help='Print the sizes of individual dex segments as ' +
777 '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
778 '<bytes>\'')
779 result.add_option('--quiet',
780 help='Disable verbose logging',
781 default=False,
782 action='store_true')
783 result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
784 help='Number of times R8 should be run on each app',
785 default=2,
786 type=int)
787 result.add_option('--run-tests', '--run_tests',
788 help='Whether to run instrumentation tests',
789 default=False,
790 action='store_true')
791 result.add_option('--sign-apks', '--sign_apks',
792 help='Whether the APKs should be signed',
793 default=False,
794 action='store_true')
795 result.add_option('--shrinker',
796 help='The shrinkers to use (by default, all are run)',
797 action='append')
798 result.add_option('--version',
799 help='The version of R8 to use (e.g., 1.4.51)')
800 (options, args) = result.parse_args(argv)
801 if options.app:
802 options.apps = [app for app in APPS if app.name in options.app]
803 del options.app
804 else:
805 options.apps = APPS
806 if options.app_logging_filter:
807 for app_name in options.app_logging_filter:
808 assert any(app.name == app_name for app in options.apps)
809 if options.shrinker:
810 for shrinker in options.shrinker:
811 assert shrinker in SHRINKERS
812 else:
813 options.shrinker = [shrinker for shrinker in SHRINKERS]
814
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100815 if options.hash or version_is_built_jar(options.version):
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100816 # No need to build R8 if a specific version should be used.
817 options.no_build = True
818 if 'r8-nolib' in options.shrinker:
819 warn('Skipping shrinker r8-nolib because a specific version '
820 + 'of r8 was specified')
821 options.shrinker.remove('r8-nolib')
822 if 'r8-nolib-full' in options.shrinker:
823 warn('Skipping shrinker r8-nolib-full because a specific version '
824 + 'of r8 was specified')
825 options.shrinker.remove('r8-nolib-full')
826 return (options, args)
827
828
829def main(argv):
830 (options, args) = parse_options(argv)
831
832 if options.bot:
833 options.no_logging = True
834 options.shrinker = ['r8', 'r8-full']
835 print(options.shrinker)
836
837 if options.golem:
838 golem.link_third_party()
839 options.disable_assertions = True
840 options.no_build = True
841 options.r8_compilation_steps = 1
842 options.quiet = True
843 options.no_logging = True
844
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100845 with utils.TempDir() as temp_dir:
846 if options.hash:
847 # Download r8-<hash>.jar from
848 # https://storage.googleapis.com/r8-releases/raw/.
849 target = 'r8-{}.jar'.format(options.hash)
850 update_prebuilds_in_android.download_hash(
851 temp_dir, 'com/android/tools/r8/' + options.hash, target)
852 as_utils.MoveFile(
853 os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
854 quiet=options.quiet)
Morten Krogh-Jespersen51db2b02020-11-11 12:49:26 +0100855 elif version_is_built_jar(options.version):
856 # Download r8-<version>.jar from
857 # https://storage.googleapis.com/r8-releases/raw/.
858 target = 'r8-{}.jar'.format(options.version)
859 update_prebuilds_in_android.download_version(
860 temp_dir, 'com/android/tools/r8/' + options.version, target)
861 as_utils.MoveFile(
862 os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
863 quiet=options.quiet)
864 elif options.version == 'master':
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100865 if not (options.no_build or options.golem):
866 gradle.RunGradle(['r8', '-Pno_internal'])
867 build_r8lib = False
868 for shrinker in options.shrinker:
869 if is_minified_r8(shrinker):
870 build_r8lib = True
871 if build_r8lib:
872 gradle.RunGradle(['r8lib', '-Pno_internal'])
873 # Make a copy of r8.jar and r8lib.jar such that they stay the same for
874 # the entire execution of this script.
875 if 'r8-nolib' in options.shrinker or 'r8-nolib-full' in options.shrinker:
876 assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
877 shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
878 if 'r8' in options.shrinker or 'r8-full' in options.shrinker:
879 assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
880 shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
881
882 result_per_shrinker_per_app = []
883 for app in options.apps:
884 if app.skip:
885 continue
886 result_per_shrinker_per_app.append(
887 (app, get_results_for_app(app, options, temp_dir)))
Morten Krogh-Jespersenf098b422020-11-11 13:53:52 +0100888 errors = log_results_for_apps(result_per_shrinker_per_app, options)
889 if errors > 0:
890 dest = 'gs://r8-test-results/r8-libs/' + str(int(time.time()))
891 utils.upload_file_to_cloud_storage(os.path.join(temp_dir, 'r8lib.jar'), dest)
892 print('R8lib saved to %s' % dest)
893 return errors
Morten Krogh-Jespersen45d7a7b2020-11-02 08:31:09 +0100894
895
896def success(message):
897 CGREEN = '\033[32m'
898 CEND = '\033[0m'
899 print(CGREEN + message + CEND)
900
901
902def warn(message):
903 CRED = '\033[91m'
904 CEND = '\033[0m'
905 print(CRED + message + CEND)
906
907
908if __name__ == '__main__':
909 sys.exit(main(sys.argv[1:]))