blob: 69f75621451233808721f044aaed1b87bedaf4f2 [file] [log] [blame]
Ian Zernydcb172e2022-02-22 15:36:45 +01001#!/usr/bin/env python3
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +01002# 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
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +01006import os
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +01007import shutil
Ian Zerny161ff742022-01-20 12:39:40 +01008from distutils.version import LooseVersion
9
10import utils
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010011
Christoffer Quist Adamsen5c9ded12021-01-14 14:29:37 +010012if utils.is_python3():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020013 from html.parser import HTMLParser
Christoffer Quist Adamsen5c9ded12021-01-14 14:29:37 +010014else:
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020015 from HTMLParser import HTMLParser
Christoffer Quist Adamsen5c9ded12021-01-14 14:29:37 +010016
17
Morten Krogh-Jespersend45d95e2019-02-07 13:27:17 +010018def add_r8_dependency(checkout_dir, temp_dir, minified):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020019 build_file = os.path.join(checkout_dir, 'build.gradle')
20 assert os.path.isfile(build_file), (
21 'Expected a file to be present at {}'.format(build_file))
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010022
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020023 with open(build_file) as f:
24 lines = f.readlines()
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010025
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020026 added_r8_dependency = False
27 is_inside_dependencies = False
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010028
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020029 with open(build_file, 'w') as f:
30 gradle_version = None
31 for line in lines:
32 stripped = line.strip()
33 if stripped == 'dependencies {':
34 assert not is_inside_dependencies, (
35 'Unexpected line with \'dependencies {\'')
36 is_inside_dependencies = True
37 if is_inside_dependencies:
38 if '/r8.jar' in stripped or '/r8lib.jar' in stripped:
39 # Skip line to avoid dependency on r8.jar
40 continue
41 elif 'com.android.tools.build:gradle:' in stripped:
42 gradle_version = stripped[stripped.rindex(':') + 1:-1]
43 indent = ''.ljust(line.index('classpath'))
44 jar = os.path.join(temp_dir,
45 'r8lib.jar' if minified else 'r8.jar')
46 f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
47 added_r8_dependency = True
48 elif stripped == '}':
49 is_inside_dependencies = False
50 f.write(line)
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010051
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020052 assert added_r8_dependency, 'Unable to add R8 as a dependency'
53 assert gradle_version
54 assert LooseVersion(gradle_version) >= LooseVersion('3.2'), (
55 'Unsupported gradle version: {} (must use at least gradle ' +
56 'version 3.2)').format(gradle_version)
57
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010058
Morten Krogh-Jespersende566ea2019-02-18 11:59:48 +010059def add_settings_gradle(checkout_dir, name):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020060 settings_file = os.path.join(checkout_dir, 'settings.gradle')
61 if os.path.isfile(settings_file):
62 return
Morten Krogh-Jespersende566ea2019-02-18 11:59:48 +010063
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020064 with open(settings_file, "w+") as f:
65 f.write("rootProject.name = '{}'\n".format(name))
66
Morten Krogh-Jespersende566ea2019-02-18 11:59:48 +010067
Christoffer Quist Adamsen10b7db82018-12-13 14:50:38 +010068def remove_r8_dependency(checkout_dir):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020069 build_file = os.path.join(checkout_dir, 'build.gradle')
70 assert os.path.isfile(build_file), (
71 'Expected a file to be present at {}'.format(build_file))
72 with open(build_file) as f:
73 lines = f.readlines()
74 with open(build_file, 'w') as f:
75 for line in lines:
76 if ('/r8.jar' not in line) and ('/r8lib.jar' not in line):
77 f.write(line)
78
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +010079
Christoffer Quist Adamsenb899c112019-03-06 15:25:00 +010080def GetMinAndCompileSdk(app, checkout_dir, apk_reference):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020081 compile_sdk = app.compile_sdk
82 min_sdk = app.min_sdk
Christoffer Quist Adamsene59840b2019-01-22 15:25:15 +010083
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020084 if not compile_sdk or not min_sdk:
85 build_gradle_file = os.path.join(checkout_dir, app.module,
86 'build.gradle')
87 assert os.path.isfile(build_gradle_file), (
88 'Expected to find build.gradle file at {}'.format(build_gradle_file)
89 )
Christoffer Quist Adamsene59840b2019-01-22 15:25:15 +010090
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020091 # Attempt to find the sdk values from build.gradle.
92 with open(build_gradle_file) as f:
93 for line in f.readlines():
94 stripped = line.strip()
95 if stripped.startswith('compileSdkVersion '):
96 if not app.compile_sdk:
97 assert not compile_sdk
98 compile_sdk = int(stripped[len('compileSdkVersion '):])
99 elif stripped.startswith('minSdkVersion '):
100 if not app.min_sdk:
101 assert not min_sdk
102 min_sdk = int(stripped[len('minSdkVersion '):])
Christoffer Quist Adamsene59840b2019-01-22 15:25:15 +0100103
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200104 assert min_sdk, (
105 'Expected to find `minSdkVersion` in {}'.format(build_gradle_file))
106 assert compile_sdk, (
107 'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file))
Christoffer Quist Adamsene59840b2019-01-22 15:25:15 +0100108
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200109 return (min_sdk, compile_sdk)
110
Christoffer Quist Adamsene59840b2019-01-22 15:25:15 +0100111
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100112def IsGradleTaskName(x):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200113 # Check that it is non-empty.
114 if not x:
115 return False
116 # Check that there is no whitespace.
117 for c in x:
118 if c.isspace():
119 return False
120 # Check that the first character following an optional ':' is a lower-case
121 # alphabetic character.
122 c = x[0]
123 if c == ':' and len(x) >= 2:
124 c = x[1]
125 return c.isalpha() and c.islower()
126
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100127
128def IsGradleCompilerTask(x, shrinker):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200129 if 'r8' in shrinker:
130 assert 'transformClassesWithDexBuilderFor' not in x
131 assert 'transformDexArchiveWithDexMergerFor' not in x
132 return 'transformClassesAndResourcesWithR8For' in x
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100133
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200134 assert shrinker == 'pg'
135 return ('transformClassesAndResourcesWithProguard' in x or
136 'transformClassesWithDexBuilderFor' in x or
137 'transformDexArchiveWithDexMergerFor' in x)
138
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100139
Christoffer Quist Adamsen7d74ef72019-04-12 10:33:38 +0200140def ListFiles(directory, predicate=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200141 files = []
142 for root, directories, filenames in os.walk(directory):
143 for filename in filenames:
144 file = os.path.join(root, filename)
145 if predicate is None or predicate(file):
146 files.append(file)
147 return files
148
Christoffer Quist Adamsen7d74ef72019-04-12 10:33:38 +0200149
Christoffer Quist Adamsenb899c112019-03-06 15:25:00 +0100150def SetPrintConfigurationDirective(app, checkout_dir, destination):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200151 proguard_config_file = FindProguardConfigurationFile(app, checkout_dir)
152 with open(proguard_config_file) as f:
153 lines = f.readlines()
154 with open(proguard_config_file, 'w') as f:
155 for line in lines:
156 if '-printconfiguration' not in line:
157 f.write(line)
158 # Check that there is a line-break at the end of the file or insert one.
159 if len(lines) and lines[-1].strip():
160 f.write('\n')
161 f.write('-printconfiguration {}\n'.format(destination))
162
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100163
Christoffer Quist Adamsenb899c112019-03-06 15:25:00 +0100164def FindProguardConfigurationFile(app, checkout_dir):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200165 candidates = [
166 'proguard.cfg', 'proguard-rules.pro', 'proguard-rules.txt',
167 'proguard-project.txt'
168 ]
169 for candidate in candidates:
170 proguard_config_file = os.path.join(checkout_dir, app.module, candidate)
171 if os.path.isfile(proguard_config_file):
172 return proguard_config_file
173 # Currently assuming that the Proguard configuration file can be found at
174 # one of the predefined locations.
175 assert False, 'Unable to find Proguard configuration file'
176
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100177
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100178def Move(src, dst, quiet=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200179 if not quiet:
180 print('Moving `{}` to `{}`'.format(src, dst))
181 dst_parent = os.path.dirname(dst)
182 if not os.path.isdir(dst_parent):
183 os.makedirs(dst_parent)
184 elif os.path.isdir(dst):
185 shutil.rmtree(dst)
186 elif os.path.isfile(dst):
187 os.remove(dst)
188 shutil.move(src, dst)
189
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +0100190
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100191def MoveDir(src, dst, quiet=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200192 assert os.path.isdir(src)
193 Move(src, dst, quiet=quiet)
194
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +0100195
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100196def MoveFile(src, dst, quiet=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200197 assert os.path.isfile(src), "Expected a file to be present at " + src
198 Move(src, dst, quiet=quiet)
199
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +0100200
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100201def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200202 html_file = None
203 profile_message = 'See the profiling report at: '
204 # We are not interested in the profiling report for buildSrc.
205 for line in build_stdout:
206 if (profile_message in line) and ('buildSrc' not in line):
207 assert not html_file, "Only one report should be created"
208 html_file = line[len(profile_message):]
209 if html_file.startswith('file://'):
210 html_file = html_file[len('file://'):]
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +0100211
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200212 if not html_file:
213 return
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +0100214
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200215 assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format(
216 html_file)
217 MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet)
Christoffer Quist Adamsen1d87ca62019-01-11 12:22:26 +0100218
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200219 html_dir = os.path.dirname(html_file)
220 for dir_name in ['css', 'js']:
221 MoveDir(os.path.join(html_dir, dir_name),
222 os.path.join(dest_dir, dir_name),
223 quiet=quiet)
224
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100225
Christoffer Quist Adamsen7cf4c562019-03-07 10:57:33 +0100226def MoveXMLTestResultFileTo(xml_test_result_dest, test_stdout, quiet=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200227 xml_test_result_file = None
228 xml_result_reporter_message = 'XML test result file generated at '
229 for line in test_stdout:
230 if xml_result_reporter_message in line:
231 index_from = (line.index(xml_result_reporter_message) +
232 len(xml_result_reporter_message))
233 index_to = line.index('.xml') + len('.xml')
234 xml_test_result_file = line[index_from:index_to]
235 break
Christoffer Quist Adamsen7cf4c562019-03-07 10:57:33 +0100236
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200237 assert os.path.isfile(xml_test_result_file), (
238 'Expected to find XML file at {}'.format(xml_test_result_file))
Christoffer Quist Adamsen7cf4c562019-03-07 10:57:33 +0100239
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200240 MoveFile(xml_test_result_file, xml_test_result_dest, quiet=quiet)
241
Christoffer Quist Adamsen7cf4c562019-03-07 10:57:33 +0100242
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100243def ParseProfileReport(profile_dir):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200244 html_file = os.path.join(profile_dir, 'index.html')
245 assert os.path.isfile(html_file)
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100246
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200247 parser = ProfileReportParser()
248 with open(html_file) as f:
249 for line in f.readlines():
250 parser.feed(line)
251 return parser.result
252
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100253
254# A simple HTML parser that recognizes the following pattern:
255#
256# <tr>
257# <td class="indentPath">:app:transformClassesAndResourcesWithR8ForRelease</td>
258# <td class="numeric">3.490s</td>
259# <td></td>
260# </tr>
261class ProfileReportParser(HTMLParser):
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100262
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200263 def __init__(self):
264 HTMLParser.__init__(self)
265 self.entered_table_row = False
266 self.entered_task_name_cell = False
267 self.entered_duration_cell = False
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100268
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200269 self.current_task_name = None
270 self.current_duration = None
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100271
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200272 self.result = {}
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100273
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200274 def handle_starttag(self, tag, attrs):
275 entered_table_row_before = self.entered_table_row
276 entered_task_name_cell_before = self.entered_task_name_cell
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100277
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200278 self.entered_table_row = (tag == 'tr')
279 self.entered_task_name_cell = (tag == 'td' and entered_table_row_before)
280 self.entered_duration_cell = (self.current_task_name and tag == 'td' and
281 entered_task_name_cell_before)
Christoffer Quist Adamsen81321b62019-01-15 14:39:53 +0100282
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200283 def handle_endtag(self, tag):
284 if tag == 'tr':
285 if self.current_task_name and self.current_duration:
286 self.result[self.current_task_name] = self.current_duration
287 self.current_task_name = None
288 self.current_duration = None
289 self.entered_table_row = False
290
291 def handle_data(self, data):
292 stripped = data.strip()
293 if not stripped:
294 return
295 if self.entered_task_name_cell:
296 if IsGradleTaskName(stripped):
297 self.current_task_name = stripped
298 elif self.entered_duration_cell and stripped.endswith('s'):
299 duration = stripped[:-1]
300 if 'm' in duration:
301 tmp = duration.split('m')
302 minutes = int(tmp[0])
303 seconds = float(tmp[1])
304 else:
305 minutes = 0
306 seconds = float(duration)
307 self.current_duration = 60 * minutes + seconds
308 self.entered_table_row = False