blob: 65ab470c404366895799f21f7633c3d440b87c48 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
from distutils.version import LooseVersion
from HTMLParser import HTMLParser
import os
import shutil
import utils
def add_r8_dependency(checkout_dir, minified):
build_file = os.path.join(checkout_dir, 'build.gradle')
assert os.path.isfile(build_file), 'Expected a file to be present at {}'.format(build_file)
with open(build_file) as f:
lines = f.readlines()
added_r8_dependency = False
is_inside_dependencies = False
with open(build_file, 'w') as f:
gradle_version = None
for line in lines:
stripped = line.strip()
if stripped == 'dependencies {':
assert not is_inside_dependencies, 'Unexpected line with \'dependencies {\''
is_inside_dependencies = True
if is_inside_dependencies:
if utils.R8_JAR in stripped:
if minified:
# Skip line to avoid dependency on r8.jar
continue
added_r8_dependency = True
elif utils.R8LIB_JAR in stripped:
if not minified:
# Skip line to avoid dependency on r8lib.jar
continue
added_r8_dependency = True
elif 'com.android.tools.build:gradle:' in stripped:
gradle_version = stripped[stripped.rindex(':')+1:-1]
if not added_r8_dependency:
indent = ''.ljust(line.index('classpath'))
jar = utils.R8LIB_JAR if minified else utils.R8_JAR
f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
added_r8_dependency = True
elif stripped == '}':
is_inside_dependencies = False
f.write(line)
assert added_r8_dependency, 'Unable to add R8 as a dependency'
assert gradle_version
assert LooseVersion(gradle_version) >= LooseVersion('3.2'), (
'Unsupported gradle version: {} (must use at least gradle '
+ 'version 3.2)').format(gradle_version)
def remove_r8_dependency(checkout_dir):
build_file = os.path.join(checkout_dir, 'build.gradle')
assert os.path.isfile(build_file), (
'Expected a file to be present at {}'.format(build_file))
with open(build_file) as f:
lines = f.readlines()
with open(build_file, 'w') as f:
for line in lines:
if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
f.write(line)
def IsGradleTaskName(x):
# Check that it is non-empty.
if not x:
return False
# Check that there is no whitespace.
for c in x:
if c.isspace():
return False
# Check that the first character following an optional ':' is a lower-case
# alphabetic character.
c = x[0]
if c == ':' and len(x) >= 2:
c = x[1]
return c.isalpha() and c.islower()
def IsGradleCompilerTask(x, shrinker):
if 'r8' in shrinker:
assert 'transformClassesWithDexBuilderFor' not in x
assert 'transformDexArchiveWithDexMergerFor' not in x
return 'transformClassesAndResourcesWithR8For' in x
assert shrinker == 'proguard'
return ('transformClassesAndResourcesWithProguard' in x
or 'transformClassesWithDexBuilderFor' in x
or 'transformDexArchiveWithDexMergerFor' in x)
def SetPrintConfigurationDirective(app, config, checkout_dir, destination):
proguard_config_file = FindProguardConfigurationFile(
app, config, checkout_dir)
with open(proguard_config_file) as f:
lines = f.readlines()
with open(proguard_config_file, 'w') as f:
for line in lines:
if '-printconfiguration' not in line:
f.write(line)
f.write('-printconfiguration {}\n'.format(destination))
def FindProguardConfigurationFile(app, config, checkout_dir):
app_module = config.get('app_module', 'app')
candidates = ['proguard-rules.pro', 'proguard-rules.txt', 'proguard.cfg']
for candidate in candidates:
proguard_config_file = os.path.join(checkout_dir, app_module, candidate)
if os.path.isfile(proguard_config_file):
return proguard_config_file
# Currently assuming that the Proguard configuration file can be found at
# one of the predefined locations.
assert False
def Move(src, dst):
print('Moving `{}` to `{}`'.format(src, dst))
dst_parent = os.path.dirname(dst)
if not os.path.isdir(dst_parent):
os.makedirs(dst_parent)
elif os.path.isdir(dst):
shutil.rmtree(dst)
elif os.path.isfile(dst):
os.remove(dst)
os.rename(src, dst)
def MoveDir(src, dst):
assert os.path.isdir(src)
Move(src, dst)
def MoveFile(src, dst):
assert os.path.isfile(src)
Move(src, dst)
def MoveProfileReportTo(dest_dir, build_stdout):
html_file = None
profile_message = 'See the profiling report at: '
for line in build_stdout:
if profile_message in line:
html_file = line[len(profile_message):]
if html_file.startswith('file://'):
html_file = html_file[len('file://'):]
break
if not html_file:
return
assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format(
html_file)
MoveFile(html_file, os.path.join(dest_dir, 'index.html'))
html_dir = os.path.dirname(html_file)
for dir_name in ['css', 'js']:
MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name))
def ParseProfileReport(profile_dir):
html_file = os.path.join(profile_dir, 'index.html')
assert os.path.isfile(html_file)
parser = ProfileReportParser()
with open(html_file) as f:
for line in f.readlines():
parser.feed(line)
return parser.result
# A simple HTML parser that recognizes the following pattern:
#
# <tr>
# <td class="indentPath">:app:transformClassesAndResourcesWithR8ForRelease</td>
# <td class="numeric">3.490s</td>
# <td></td>
# </tr>
class ProfileReportParser(HTMLParser):
entered_table_row = False
entered_task_name_cell = False
entered_duration_cell = False
current_task_name = None
current_duration = None
result = {}
def handle_starttag(self, tag, attrs):
entered_table_row_before = self.entered_table_row
entered_task_name_cell_before = self.entered_task_name_cell
self.entered_table_row = (tag == 'tr')
self.entered_task_name_cell = (tag == 'td' and entered_table_row_before)
self.entered_duration_cell = (
self.current_task_name
and tag == 'td'
and entered_task_name_cell_before)
def handle_endtag(self, tag):
if tag == 'tr':
if self.current_task_name and self.current_duration:
self.result[self.current_task_name] = self.current_duration
self.current_task_name = None
self.current_duration = None
self.entered_table_row = False
def handle_data(self, data):
stripped = data.strip()
if not stripped:
return
if self.entered_task_name_cell:
if IsGradleTaskName(stripped):
self.current_task_name = stripped
elif self.entered_duration_cell and stripped.endswith('s'):
self.current_duration = float(stripped[:-1])
self.entered_table_row = False