blob: 024419793a25a3ccae236c003dd17623ecf1aafd [file] [log] [blame]
Mads Ager418d1ca2017-05-22 09:35:49 +02001# Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
2# for details. All rights reserved. Use of this source code is governed by a
3# BSD-style license that can be found in the LICENSE file.
4
5# Different utility functions used accross scripts
6
7import hashlib
8import os
Tamas Kenez82efeb52017-06-12 13:56:22 +02009import re
Mads Ager418d1ca2017-05-22 09:35:49 +020010import shutil
11import subprocess
12import sys
Jean-Marie Henaff7a64eec2018-05-31 15:30:35 +020013import tarfile
Mads Ager418d1ca2017-05-22 09:35:49 +020014import tempfile
Morten Krogh-Jespersen16e925d2019-01-25 14:40:38 +010015import zipfile
Mads Ager418d1ca2017-05-22 09:35:49 +020016
Christoffer Quist Adamsen17879c12019-01-22 16:13:54 +010017ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
18ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
Mads Ager418d1ca2017-05-22 09:35:49 +020019TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
20REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
Rico Wind6238f222018-10-03 10:36:10 +020021THIRD_PARTY = os.path.join(REPO_ROOT, 'third_party')
Tamas Kenezfc34cd82017-07-13 12:43:57 +020022MEMORY_USE_TMP_FILE = 'memory_use.tmp'
Tamas Kenez02bff032017-07-18 12:13:58 +020023DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
Mads Ager12a56bc2017-11-27 11:51:25 +010024BUILD = os.path.join(REPO_ROOT, 'build')
Ian Zerny0f5fc732018-11-15 14:34:41 +010025BUILD_DEPS_DIR = os.path.join(BUILD, 'deps')
26BUILD_MAIN_DIR = os.path.join(BUILD, 'classes', 'main')
27BUILD_TEST_DIR = os.path.join(BUILD, 'classes', 'test')
Mads Ager12a56bc2017-11-27 11:51:25 +010028LIBS = os.path.join(BUILD, 'libs')
29GENERATED_LICENSE_DIR = os.path.join(BUILD, 'generatedLicense')
Mads Agera4911eb2017-11-22 13:19:36 +010030SRC_ROOT = os.path.join(REPO_ROOT, 'src', 'main', 'java')
Søren Gjessedc9d8a22017-10-12 12:40:59 +020031
32D8 = 'd8'
33R8 = 'r8'
Tamas Kenez03ab76f2018-12-07 14:33:25 +010034R8LIB = 'r8lib'
Morten Krogh-Jespersene28db462019-01-09 13:32:15 +010035R8LIB_NO_DEPS = 'r8LibNoDeps'
Mads Agerb10c07f2017-11-27 13:25:52 +010036R8_SRC = 'sourceJar'
Søren Gjessedc9d8a22017-10-12 12:40:59 +020037COMPATDX = 'compatdx'
Tamas Kenez03ab76f2018-12-07 14:33:25 +010038COMPATDXLIB = 'compatdxlib'
Søren Gjessedc9d8a22017-10-12 12:40:59 +020039COMPATPROGUARD = 'compatproguard'
Tamas Kenez03ab76f2018-12-07 14:33:25 +010040COMPATPROGUARDLIB = 'compatproguardlib'
Søren Gjessedc9d8a22017-10-12 12:40:59 +020041
Rico Wind74fab302017-10-02 07:25:33 +020042D8_JAR = os.path.join(LIBS, 'd8.jar')
43R8_JAR = os.path.join(LIBS, 'r8.jar')
Tamas Kenez03ab76f2018-12-07 14:33:25 +010044R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')
Mads Agerb10c07f2017-11-27 13:25:52 +010045R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
Tamas Kenez03ab76f2018-12-07 14:33:25 +010046R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
Tamas Kenez180be092018-12-05 15:23:06 +010047R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
Rico Wind74fab302017-10-02 07:25:33 +020048COMPATDX_JAR = os.path.join(LIBS, 'compatdx.jar')
Tamas Kenez03ab76f2018-12-07 14:33:25 +010049COMPATDXLIB_JAR = os.path.join(LIBS, 'compatdxlib.jar')
Søren Gjessedc9d8a22017-10-12 12:40:59 +020050COMPATPROGUARD_JAR = os.path.join(LIBS, 'compatproguard.jar')
Tamas Kenez03ab76f2018-12-07 14:33:25 +010051COMPATPROGUARDLIB_JAR = os.path.join(LIBS, 'compatproguardlib.jar')
Mads Ager12a56bc2017-11-27 11:51:25 +010052MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
53GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
Mathias Rav3fb4a3a2018-05-29 15:41:36 +020054RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
Mathias Ravb46dc002018-06-06 09:37:11 +020055R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
Morten Krogh-Jespersen38c7ca02019-02-04 10:39:57 +010056RETRACE_JAR = os.path.join(
57 THIRD_PARTY,
58 'proguard',
59 'proguard6.0.1',
60 'lib',
61 'retrace.jar')
Morten Krogh-Jespersen480784d2019-02-05 08:10:46 +010062CF_SEGMENTS_TOOL = os.path.join(THIRD_PARTY, 'cf_segments')
Morten Krogh-Jespersen38c7ca02019-02-04 10:39:57 +010063PINNED_R8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8.jar')
64PINNED_PGR8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8-pg6.0.1.jar')
65
Mads Ager418d1ca2017-05-22 09:35:49 +020066
Morten Krogh-Jespersenc8efedd2019-01-28 11:36:17 +010067# Common environment setup.
68USER_HOME = os.path.expanduser('~')
69ANDROID_HOME = os.path.join(USER_HOME, 'Android', 'Sdk')
70ANDROID_BUILD_TOOLS_VERSION = '28.0.3'
71ANDROID_BUILD_TOOLS = os.path.join(
72 ANDROID_HOME, 'build-tools', ANDROID_BUILD_TOOLS_VERSION)
73
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010074def Print(s, quiet=False):
75 if quiet:
76 return
77 print(s)
78
79def Warn(message):
80 CRED = '\033[91m'
81 CEND = '\033[0m'
82 print(CRED + message + CEND)
83
84def PrintCmd(cmd, env=None, quiet=False):
85 if quiet:
86 return
87 if type(cmd) is list:
88 cmd = ' '.join(cmd)
89 if env:
90 env = ' '.join(['{}=\"{}\"'.format(x, y) for x, y in env.iteritems()])
91 print('Running: {} {}'.format(env, cmd))
92 else:
93 print('Running: {}'.format(cmd))
Mads Ager418d1ca2017-05-22 09:35:49 +020094 # I know this will hit os on windows eventually if we don't do this.
95 sys.stdout.flush()
96
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010097class ProgressLogger(object):
98 CLEAR_LINE = '\033[K'
99 UP = '\033[F'
100
101 def __init__(self, quiet=False):
102 self._count = 0
103 self._has_printed = False
104 self._quiet = quiet
105
106 def log(self, text):
107 if self._quiet:
108 if self._has_printed:
109 sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
110 if len(text) > 140:
111 text = text[0:140] + '...'
112 print(text)
113 self._has_printed = True
114
115 def done(self):
116 if self._quiet and self._has_printed:
117 sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
118 print('')
119 sys.stdout.write(ProgressLogger.UP)
120
121def RunCmd(cmd, env_vars=None, quiet=False):
122 PrintCmd(cmd, env=env_vars, quiet=quiet)
123 env = os.environ.copy()
124 if env_vars:
125 env.update(env_vars)
126 process = subprocess.Popen(
127 cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
128 stdout = []
129 logger = ProgressLogger(quiet=quiet)
130 failed = False
131 while True:
132 line = process.stdout.readline()
133 if line != b'':
134 stripped = line.rstrip()
135 stdout.append(stripped)
136 logger.log(stripped)
137
138 # TODO(christofferqa): r8 should fail with non-zero exit code.
Morten Krogh-Jespersen121c47b2019-01-25 09:57:21 +0100139 if ('AssertionError:' in stripped
140 or 'CompilationError:' in stripped
141 or 'CompilationFailedException:' in stripped
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100142 or 'Compilation failed' in stripped):
143 failed = True
144 else:
145 logger.done()
146 exit_code = process.poll()
147 if exit_code or failed:
148 for line in stdout:
149 Warn(line)
150 raise subprocess.CalledProcessError(
151 exit_code, cmd, output='\n'.join(stdout))
152 return stdout
153
Rico Windf80f5a22017-06-16 09:15:57 +0200154def IsWindows():
155 return os.name == 'nt'
156
Jean-Marie Henaffe4e36d12018-04-05 10:33:50 +0200157def DownloadFromX20(sha1_file):
158 download_script = os.path.join(REPO_ROOT, 'tools', 'download_from_x20.py')
159 cmd = [download_script, sha1_file]
160 PrintCmd(cmd)
161 subprocess.check_call(cmd)
162
Mads Ager418d1ca2017-05-22 09:35:49 +0200163def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps'):
Rico Windf80f5a22017-06-16 09:15:57 +0200164 suffix = '.bat' if IsWindows() else ''
165 download_script = 'download_from_google_storage%s' % suffix
166 cmd = [download_script, '-n', '-b', bucket, '-u', '-s',
Mads Ager418d1ca2017-05-22 09:35:49 +0200167 sha1_file]
168 PrintCmd(cmd)
169 subprocess.check_call(cmd)
170
171def get_sha1(filename):
172 sha1 = hashlib.sha1()
173 with open(filename, 'rb') as f:
174 while True:
175 chunk = f.read(1024*1024)
176 if not chunk:
177 break
178 sha1.update(chunk)
179 return sha1.hexdigest()
180
Rico Wind1b09c562019-01-17 08:53:09 +0100181def is_master():
182 remotes = subprocess.check_output(['git', 'branch', '-r', '--contains',
183 'HEAD'])
184 return 'origin/master' in remotes
185
Jean-Marie Henaff7a64eec2018-05-31 15:30:35 +0200186def get_HEAD_sha1():
187 cmd = ['git', 'rev-parse', 'HEAD']
188 PrintCmd(cmd)
189 with ChangedWorkingDirectory(REPO_ROOT):
190 return subprocess.check_output(cmd).strip()
191
Tamas Kenez971eec62017-05-24 11:08:40 +0200192def makedirs_if_needed(path):
193 try:
194 os.makedirs(path)
195 except OSError:
196 if not os.path.isdir(path):
197 raise
198
Rico Wind800fd712018-09-24 11:29:33 +0200199def upload_dir_to_cloud_storage(directory, destination, is_html=False, public_read=True):
Rico Winda94f01c2017-06-27 10:32:34 +0200200 # Upload and make the content encoding right for viewing directly
Rico Windd0d88cf2018-02-09 09:46:11 +0100201 cmd = ['gsutil.py', 'cp']
202 if is_html:
203 cmd += ['-z', 'html']
Rico Wind800fd712018-09-24 11:29:33 +0200204 if public_read:
205 cmd += ['-a', 'public-read']
206 cmd += ['-R', directory, destination]
Rico Winda94f01c2017-06-27 10:32:34 +0200207 PrintCmd(cmd)
208 subprocess.check_call(cmd)
209
Rico Wind800fd712018-09-24 11:29:33 +0200210def upload_file_to_cloud_storage(source, destination, public_read=True):
211 cmd = ['gsutil.py', 'cp']
212 if public_read:
213 cmd += ['-a', 'public-read']
214 cmd += [source, destination]
Rico Windb4621c12017-08-28 12:48:53 +0200215 PrintCmd(cmd)
216 subprocess.check_call(cmd)
217
Rico Wind139eece2018-09-25 09:42:09 +0200218def delete_file_from_cloud_storage(destination):
219 cmd = ['gsutil.py', 'rm', destination]
220 PrintCmd(cmd)
221 subprocess.check_call(cmd)
222
Rico Wind4fd2dda2018-09-26 17:41:45 +0200223def ls_files_on_cloud_storage(destination):
224 cmd = ['gsutil.py', 'ls', destination]
225 PrintCmd(cmd)
226 return subprocess.check_output(cmd)
227
Rico Wind139eece2018-09-25 09:42:09 +0200228def cat_file_on_cloud_storage(destination, ignore_errors=False):
229 cmd = ['gsutil.py', 'cat', destination]
230 PrintCmd(cmd)
231 try:
232 return subprocess.check_output(cmd)
233 except subprocess.CalledProcessError as e:
234 if ignore_errors:
235 return ''
236 else:
237 raise e
238
239def file_exists_on_cloud_storage(destination):
240 cmd = ['gsutil.py', 'ls', destination]
241 PrintCmd(cmd)
242 return subprocess.call(cmd) == 0
243
Jean-Marie Henaff7a64eec2018-05-31 15:30:35 +0200244def download_file_from_cloud_storage(source, destination):
245 cmd = ['gsutil.py', 'cp', source, destination]
246 PrintCmd(cmd)
247 subprocess.check_call(cmd)
248
249def create_archive(name):
250 tarname = '%s.tar.gz' % name
251 with tarfile.open(tarname, 'w:gz') as tar:
252 tar.add(name)
253 return tarname
254
255def extract_dir(filename):
256 return filename[0:len(filename) - len('.tar.gz')]
257
258def unpack_archive(filename):
259 dest_dir = extract_dir(filename)
260 if os.path.exists(dest_dir):
261 print 'Deleting existing dir %s' % dest_dir
262 shutil.rmtree(dest_dir)
263 dirname = os.path.dirname(os.path.abspath(filename))
264 with tarfile.open(filename, 'r:gz') as tar:
265 tar.extractall(path=dirname)
266
Rico Wind1a29c4f2018-01-25 08:43:08 +0100267# Note that gcs is eventually consistent with regards to list operations.
268# This is not a problem in our case, but don't ever use this method
269# for synchronization.
270def cloud_storage_exists(destination):
271 cmd = ['gsutil.py', 'ls', destination]
272 PrintCmd(cmd)
273 exit_code = subprocess.call(cmd)
274 return exit_code == 0
275
Mads Ager418d1ca2017-05-22 09:35:49 +0200276class TempDir(object):
277 def __init__(self, prefix=''):
278 self._temp_dir = None
279 self._prefix = prefix
280
281 def __enter__(self):
282 self._temp_dir = tempfile.mkdtemp(self._prefix)
283 return self._temp_dir
284
285 def __exit__(self, *_):
286 shutil.rmtree(self._temp_dir, ignore_errors=True)
287
288class ChangedWorkingDirectory(object):
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100289 def __init__(self, working_directory, quiet=False):
290 self._quiet = quiet
Mads Ager418d1ca2017-05-22 09:35:49 +0200291 self._working_directory = working_directory
292
293 def __enter__(self):
294 self._old_cwd = os.getcwd()
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100295 if not self._quiet:
296 print 'Enter directory:', self._working_directory
Mads Ager418d1ca2017-05-22 09:35:49 +0200297 os.chdir(self._working_directory)
298
299 def __exit__(self, *_):
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100300 if not self._quiet:
301 print 'Enter directory:', self._old_cwd
Mads Ager418d1ca2017-05-22 09:35:49 +0200302 os.chdir(self._old_cwd)
Tamas Kenez82efeb52017-06-12 13:56:22 +0200303
304# Reading Android CTS test_result.xml
305
306class CtsModule(object):
307 def __init__(self, module_name):
308 self.name = module_name
309
310class CtsTestCase(object):
311 def __init__(self, test_case_name):
312 self.name = test_case_name
313
314class CtsTest(object):
315 def __init__(self, test_name, outcome):
316 self.name = test_name
317 self.outcome = outcome
318
319# Generator yielding CtsModule, CtsTestCase or CtsTest from
320# reading through a CTS test_result.xml file.
321def read_cts_test_result(file_xml):
322 re_module = re.compile('<Module name="([^"]*)"')
323 re_test_case = re.compile('<TestCase name="([^"]*)"')
324 re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
325 with open(file_xml) as f:
326 for line in f:
327 m = re_module.search(line)
328 if m:
329 yield CtsModule(m.groups()[0])
330 continue
331 m = re_test_case.search(line)
332 if m:
333 yield CtsTestCase(m.groups()[0])
334 continue
335 m = re_test.search(line)
336 if m:
337 outcome = m.groups()[0]
Rico Windf80f5a22017-06-16 09:15:57 +0200338 assert outcome in ['fail', 'pass']
Tamas Kenez82efeb52017-06-12 13:56:22 +0200339 yield CtsTest(m.groups()[1], outcome == 'pass')
Tamas Kenezfc34cd82017-07-13 12:43:57 +0200340
341def grep_memoryuse(logfile):
342 re_vmhwm = re.compile('^VmHWM:[ \t]*([0-9]+)[ \t]*([a-zA-Z]*)')
343 result = None
344 with open(logfile) as f:
345 for line in f:
346 m = re_vmhwm.search(line)
347 if m:
348 groups = m.groups()
349 s = len(groups)
350 if s >= 1:
351 result = int(groups[0])
352 if s >= 2:
353 unit = groups[1]
354 if unit == 'kB':
355 result *= 1024
356 elif unit != '':
357 raise Exception('Unrecognized unit in memory usage log: {}'
358 .format(unit))
359 if result is None:
360 raise Exception('No memory usage found in log: {}'.format(logfile))
Tamas Kenez02bff032017-07-18 12:13:58 +0200361 return result
362
363# Return a dictionary: {segment_name -> segments_size}
364def getDexSegmentSizes(dex_files):
365 assert len(dex_files) > 0
Mathias Rav9cf6a742018-05-22 09:34:45 +0200366 cmd = ['java', '-jar', R8_JAR, 'dexsegments']
Tamas Kenez02bff032017-07-18 12:13:58 +0200367 cmd.extend(dex_files)
368 PrintCmd(cmd)
369 output = subprocess.check_output(cmd)
370
371 matches = DEX_SEGMENTS_RESULT_PATTERN.findall(output)
372
373 if matches is None or len(matches) == 0:
374 raise Exception('DexSegments failed to return any output for' \
375 ' these files: {}'.format(dex_files))
376
377 result = {}
378
379 for match in matches:
380 result[match[0]] = int(match[1])
381
382 return result
383
Morten Krogh-Jespersen38c7ca02019-02-04 10:39:57 +0100384# Return a dictionary: {segment_name -> segments_size}
385def getCfSegmentSizes(cfFile):
386 cmd = ['java',
387 '-cp',
Morten Krogh-Jespersen480784d2019-02-05 08:10:46 +0100388 CF_SEGMENTS_TOOL,
Morten Krogh-Jespersen38c7ca02019-02-04 10:39:57 +0100389 'com.android.tools.r8.cf_segments.MeasureLib',
390 cfFile]
391 PrintCmd(cmd)
392 output = subprocess.check_output(cmd)
393
394 matches = DEX_SEGMENTS_RESULT_PATTERN.findall(output)
395
396 if matches is None or len(matches) == 0:
397 raise Exception('CfSegments failed to return any output for' \
398 ' the file: ' + cfFile)
399
400 result = {}
401
402 for match in matches:
403 result[match[0]] = int(match[1])
404
405 return result
406
407
Rico Windc0b16382018-05-17 13:23:43 +0200408def get_maven_path(version):
409 return os.path.join('com', 'android', 'tools', 'r8', version)
410
Morten Krogh-Jespersen38c7ca02019-02-04 10:39:57 +0100411def print_cfsegments(prefix, cf_files):
412 for cf_file in cf_files:
413 for segment_name, size in getCfSegmentSizes(cf_file).items():
414 print('{}-{}(CodeSize): {}'
415 .format(prefix, segment_name, size))
416
Tamas Kenez02bff032017-07-18 12:13:58 +0200417def print_dexsegments(prefix, dex_files):
418 for segment_name, size in getDexSegmentSizes(dex_files).items():
419 print('{}-{}(CodeSize): {}'
420 .format(prefix, segment_name, size))
Tamas Kenez2cf47cf2017-07-25 10:22:52 +0200421
Mads Agerbc7b2ce2018-02-05 11:28:47 +0100422# Ensure that we are not benchmarking with a google jvm.
Tamas Kenez2cf47cf2017-07-25 10:22:52 +0200423def check_java_version():
424 cmd= ['java', '-version']
425 output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
426 m = re.search('openjdk version "([^"]*)"', output)
427 if m is None:
428 raise Exception("Can't check java version: no version string in output"
429 " of 'java -version': '{}'".format(output))
430 version = m.groups(0)[0]
Mads Agerbc7b2ce2018-02-05 11:28:47 +0100431 m = re.search('google', version)
432 if m is not None:
433 raise Exception("Do not use google JVM for benchmarking: " + version)
Tamas Kenez0cad51c2017-08-21 14:42:01 +0200434
Christoffer Quist Adamsen17879c12019-01-22 16:13:54 +0100435def get_android_jar_dir(api):
436 return os.path.join(REPO_ROOT, ANDROID_JAR_DIR.format(api=api))
437
Rico Wind9d70f612018-08-31 09:17:43 +0200438def get_android_jar(api):
439 return os.path.join(REPO_ROOT, ANDROID_JAR.format(api=api))
Rico Windda6836e2018-12-07 12:32:03 +0100440
Christoffer Quist Adamsen17879c12019-01-22 16:13:54 +0100441def get_android_optional_jars(api):
442 android_optional_jars_dir = os.path.join(get_android_jar_dir(api), 'optional')
443 android_optional_jars = [
444 os.path.join(android_optional_jars_dir, 'android.test.base.jar'),
445 os.path.join(android_optional_jars_dir, 'android.test.mock.jar'),
446 os.path.join(android_optional_jars_dir, 'android.test.runner.jar'),
447 os.path.join(android_optional_jars_dir, 'org.apache.http.legacy.jar')
448 ]
449 return [
450 android_optional_jar for android_optional_jar in android_optional_jars
451 if os.path.isfile(android_optional_jar)]
452
Rico Windda6836e2018-12-07 12:32:03 +0100453def is_bot():
454 return 'BUILDBOT_BUILDERNAME' in os.environ
Morten Krogh-Jespersen16e925d2019-01-25 14:40:38 +0100455
456
457def uncompressed_size(path):
458 return sum(z.file_size for z in zipfile.ZipFile(path).infolist())
459
460