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