Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | from distutils.version import LooseVersion |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 7 | from HTMLParser import HTMLParser |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 8 | import os |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 9 | import shutil |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 10 | |
| 11 | import utils |
| 12 | |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 13 | def add_r8_dependency(checkout_dir, temp_dir, minified): |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 14 | build_file = os.path.join(checkout_dir, 'build.gradle') |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 15 | assert os.path.isfile(build_file), ( |
| 16 | 'Expected a file to be present at {}'.format(build_file)) |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 17 | |
| 18 | with open(build_file) as f: |
| 19 | lines = f.readlines() |
| 20 | |
| 21 | added_r8_dependency = False |
| 22 | is_inside_dependencies = False |
| 23 | |
| 24 | with open(build_file, 'w') as f: |
| 25 | gradle_version = None |
| 26 | for line in lines: |
| 27 | stripped = line.strip() |
| 28 | if stripped == 'dependencies {': |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 29 | assert not is_inside_dependencies, ( |
| 30 | 'Unexpected line with \'dependencies {\'') |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 31 | is_inside_dependencies = True |
| 32 | if is_inside_dependencies: |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 33 | if '/r8.jar' in stripped or '/r8lib.jar' in stripped: |
| 34 | # Skip line to avoid dependency on r8.jar |
| 35 | continue |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 36 | elif 'com.android.tools.build:gradle:' in stripped: |
| 37 | gradle_version = stripped[stripped.rindex(':')+1:-1] |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 38 | indent = ''.ljust(line.index('classpath')) |
| 39 | jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar') |
| 40 | f.write('{}classpath files(\'{}\')\n'.format(indent, jar)) |
| 41 | added_r8_dependency = True |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 42 | elif stripped == '}': |
| 43 | is_inside_dependencies = False |
| 44 | f.write(line) |
| 45 | |
| 46 | assert added_r8_dependency, 'Unable to add R8 as a dependency' |
| 47 | assert gradle_version |
| 48 | assert LooseVersion(gradle_version) >= LooseVersion('3.2'), ( |
| 49 | 'Unsupported gradle version: {} (must use at least gradle ' |
| 50 | + 'version 3.2)').format(gradle_version) |
| 51 | |
| 52 | def remove_r8_dependency(checkout_dir): |
| 53 | build_file = os.path.join(checkout_dir, 'build.gradle') |
Christoffer Quist Adamsen | 4038bbe | 2019-01-15 14:31:46 +0100 | [diff] [blame] | 54 | assert os.path.isfile(build_file), ( |
| 55 | 'Expected a file to be present at {}'.format(build_file)) |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 56 | with open(build_file) as f: |
| 57 | lines = f.readlines() |
| 58 | with open(build_file, 'w') as f: |
| 59 | for line in lines: |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 60 | if ('/r8.jar' not in line) and ('/r8lib.jar' not in line): |
Christoffer Quist Adamsen | 10b7db8 | 2018-12-13 14:50:38 +0100 | [diff] [blame] | 61 | f.write(line) |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 62 | |
Christoffer Quist Adamsen | e59840b | 2019-01-22 15:25:15 +0100 | [diff] [blame] | 63 | def GetMinAndCompileSdk(app, config, checkout_dir, apk_reference): |
Christoffer Quist Adamsen | e59840b | 2019-01-22 15:25:15 +0100 | [diff] [blame] | 64 | |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 65 | compile_sdk = config.get('compile_sdk', None) |
| 66 | min_sdk = config.get('min_sdk', None) |
Christoffer Quist Adamsen | e59840b | 2019-01-22 15:25:15 +0100 | [diff] [blame] | 67 | |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 68 | if not compile_sdk or not min_sdk: |
| 69 | app_module = config.get('app_module', 'app') |
| 70 | build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle') |
| 71 | assert os.path.isfile(build_gradle_file), ( |
| 72 | 'Expected to find build.gradle file at {}'.format(build_gradle_file)) |
Christoffer Quist Adamsen | e59840b | 2019-01-22 15:25:15 +0100 | [diff] [blame] | 73 | |
Christoffer Quist Adamsen | bbe5d7d | 2019-01-23 13:15:21 +0100 | [diff] [blame] | 74 | # Attempt to find the sdk values from build.gradle. |
| 75 | with open(build_gradle_file) as f: |
| 76 | for line in f.readlines(): |
| 77 | stripped = line.strip() |
| 78 | if stripped.startswith('compileSdkVersion '): |
| 79 | if 'compile_sdk' not in config: |
| 80 | assert not compile_sdk |
| 81 | compile_sdk = int(stripped[len('compileSdkVersion '):]) |
| 82 | elif stripped.startswith('minSdkVersion '): |
| 83 | if 'min_sdk' not in config: |
| 84 | assert not min_sdk |
| 85 | min_sdk = int(stripped[len('minSdkVersion '):]) |
Christoffer Quist Adamsen | e59840b | 2019-01-22 15:25:15 +0100 | [diff] [blame] | 86 | |
| 87 | assert min_sdk, ( |
| 88 | 'Expected to find `minSdkVersion` in {}'.format(build_gradle_file)) |
| 89 | assert compile_sdk, ( |
| 90 | 'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file)) |
| 91 | |
Christoffer Quist Adamsen | e59840b | 2019-01-22 15:25:15 +0100 | [diff] [blame] | 92 | return (min_sdk, compile_sdk) |
| 93 | |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 94 | def IsGradleTaskName(x): |
| 95 | # Check that it is non-empty. |
| 96 | if not x: |
| 97 | return False |
| 98 | # Check that there is no whitespace. |
| 99 | for c in x: |
| 100 | if c.isspace(): |
| 101 | return False |
| 102 | # Check that the first character following an optional ':' is a lower-case |
| 103 | # alphabetic character. |
| 104 | c = x[0] |
| 105 | if c == ':' and len(x) >= 2: |
| 106 | c = x[1] |
| 107 | return c.isalpha() and c.islower() |
| 108 | |
| 109 | def IsGradleCompilerTask(x, shrinker): |
| 110 | if 'r8' in shrinker: |
| 111 | assert 'transformClassesWithDexBuilderFor' not in x |
| 112 | assert 'transformDexArchiveWithDexMergerFor' not in x |
| 113 | return 'transformClassesAndResourcesWithR8For' in x |
| 114 | |
| 115 | assert shrinker == 'proguard' |
| 116 | return ('transformClassesAndResourcesWithProguard' in x |
| 117 | or 'transformClassesWithDexBuilderFor' in x |
| 118 | or 'transformDexArchiveWithDexMergerFor' in x) |
| 119 | |
Christoffer Quist Adamsen | 4038bbe | 2019-01-15 14:31:46 +0100 | [diff] [blame] | 120 | def SetPrintConfigurationDirective(app, config, checkout_dir, destination): |
| 121 | proguard_config_file = FindProguardConfigurationFile( |
| 122 | app, config, checkout_dir) |
| 123 | with open(proguard_config_file) as f: |
| 124 | lines = f.readlines() |
| 125 | with open(proguard_config_file, 'w') as f: |
| 126 | for line in lines: |
| 127 | if '-printconfiguration' not in line: |
| 128 | f.write(line) |
Morten Krogh-Jespersen | e1aeead | 2019-01-29 11:16:44 +0100 | [diff] [blame] | 129 | # Check that there is a line-break at the end of the file or insert one. |
| 130 | if lines[-1].strip(): |
| 131 | f.write('\n') |
Christoffer Quist Adamsen | 4038bbe | 2019-01-15 14:31:46 +0100 | [diff] [blame] | 132 | f.write('-printconfiguration {}\n'.format(destination)) |
| 133 | |
| 134 | def FindProguardConfigurationFile(app, config, checkout_dir): |
| 135 | app_module = config.get('app_module', 'app') |
| 136 | candidates = ['proguard-rules.pro', 'proguard-rules.txt', 'proguard.cfg'] |
| 137 | for candidate in candidates: |
| 138 | proguard_config_file = os.path.join(checkout_dir, app_module, candidate) |
| 139 | if os.path.isfile(proguard_config_file): |
| 140 | return proguard_config_file |
| 141 | # Currently assuming that the Proguard configuration file can be found at |
| 142 | # one of the predefined locations. |
| 143 | assert False |
| 144 | |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 145 | def Move(src, dst, quiet=False): |
| 146 | if not quiet: |
| 147 | print('Moving `{}` to `{}`'.format(src, dst)) |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 148 | dst_parent = os.path.dirname(dst) |
| 149 | if not os.path.isdir(dst_parent): |
| 150 | os.makedirs(dst_parent) |
| 151 | elif os.path.isdir(dst): |
| 152 | shutil.rmtree(dst) |
| 153 | elif os.path.isfile(dst): |
| 154 | os.remove(dst) |
| 155 | os.rename(src, dst) |
| 156 | |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 157 | def MoveDir(src, dst, quiet=False): |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 158 | assert os.path.isdir(src) |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 159 | Move(src, dst, quiet=quiet) |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 160 | |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 161 | def MoveFile(src, dst, quiet=False): |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 162 | assert os.path.isfile(src) |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 163 | Move(src, dst, quiet=quiet) |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 164 | |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 165 | def MoveProfileReportTo(dest_dir, build_stdout, quiet=False): |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 166 | html_file = None |
| 167 | profile_message = 'See the profiling report at: ' |
| 168 | for line in build_stdout: |
| 169 | if profile_message in line: |
| 170 | html_file = line[len(profile_message):] |
| 171 | if html_file.startswith('file://'): |
| 172 | html_file = html_file[len('file://'):] |
| 173 | break |
| 174 | |
| 175 | if not html_file: |
| 176 | return |
| 177 | |
| 178 | assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format( |
| 179 | html_file) |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 180 | MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet) |
Christoffer Quist Adamsen | 1d87ca6 | 2019-01-11 12:22:26 +0100 | [diff] [blame] | 181 | |
| 182 | html_dir = os.path.dirname(html_file) |
| 183 | for dir_name in ['css', 'js']: |
Christoffer Quist Adamsen | 1de3dde | 2019-01-24 13:17:46 +0100 | [diff] [blame] | 184 | MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name), |
| 185 | quiet=quiet) |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 186 | |
| 187 | def ParseProfileReport(profile_dir): |
| 188 | html_file = os.path.join(profile_dir, 'index.html') |
| 189 | assert os.path.isfile(html_file) |
| 190 | |
| 191 | parser = ProfileReportParser() |
| 192 | with open(html_file) as f: |
| 193 | for line in f.readlines(): |
| 194 | parser.feed(line) |
| 195 | return parser.result |
| 196 | |
| 197 | # A simple HTML parser that recognizes the following pattern: |
| 198 | # |
| 199 | # <tr> |
| 200 | # <td class="indentPath">:app:transformClassesAndResourcesWithR8ForRelease</td> |
| 201 | # <td class="numeric">3.490s</td> |
| 202 | # <td></td> |
| 203 | # </tr> |
| 204 | class ProfileReportParser(HTMLParser): |
Christoffer Quist Adamsen | d0e0a7c | 2019-01-22 10:00:30 +0100 | [diff] [blame] | 205 | def __init__(self): |
| 206 | HTMLParser.__init__(self) |
| 207 | self.entered_table_row = False |
| 208 | self.entered_task_name_cell = False |
| 209 | self.entered_duration_cell = False |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 210 | |
Christoffer Quist Adamsen | d0e0a7c | 2019-01-22 10:00:30 +0100 | [diff] [blame] | 211 | self.current_task_name = None |
| 212 | self.current_duration = None |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 213 | |
Christoffer Quist Adamsen | d0e0a7c | 2019-01-22 10:00:30 +0100 | [diff] [blame] | 214 | self.result = {} |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 215 | |
| 216 | def handle_starttag(self, tag, attrs): |
| 217 | entered_table_row_before = self.entered_table_row |
| 218 | entered_task_name_cell_before = self.entered_task_name_cell |
| 219 | |
| 220 | self.entered_table_row = (tag == 'tr') |
| 221 | self.entered_task_name_cell = (tag == 'td' and entered_table_row_before) |
| 222 | self.entered_duration_cell = ( |
| 223 | self.current_task_name |
| 224 | and tag == 'td' |
| 225 | and entered_task_name_cell_before) |
| 226 | |
| 227 | def handle_endtag(self, tag): |
| 228 | if tag == 'tr': |
| 229 | if self.current_task_name and self.current_duration: |
| 230 | self.result[self.current_task_name] = self.current_duration |
| 231 | self.current_task_name = None |
| 232 | self.current_duration = None |
| 233 | self.entered_table_row = False |
| 234 | |
| 235 | def handle_data(self, data): |
| 236 | stripped = data.strip() |
| 237 | if not stripped: |
| 238 | return |
| 239 | if self.entered_task_name_cell: |
| 240 | if IsGradleTaskName(stripped): |
| 241 | self.current_task_name = stripped |
| 242 | elif self.entered_duration_cell and stripped.endswith('s'): |
Christoffer Quist Adamsen | d0e0a7c | 2019-01-22 10:00:30 +0100 | [diff] [blame] | 243 | duration = stripped[:-1] |
| 244 | if 'm' in duration: |
| 245 | tmp = duration.split('m') |
| 246 | minutes = int(tmp[0]) |
| 247 | seconds = float(tmp[1]) |
| 248 | else: |
| 249 | minutes = 0 |
| 250 | seconds = float(duration) |
| 251 | self.current_duration = 60 * minutes + seconds |
Christoffer Quist Adamsen | 81321b6 | 2019-01-15 14:39:53 +0100 | [diff] [blame] | 252 | self.entered_table_row = False |