Extend script for running shrinkers on Android Studio projects
Change-Id: I1f37f199a0eb0db564851a3ce7b962e8da57043c
diff --git a/tools/as_utils.py b/tools/as_utils.py
new file mode 100644
index 0000000..1eaa206
--- /dev/null
+++ b/tools/as_utils.py
@@ -0,0 +1,55 @@
+#!/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
+import os
+
+import utils
+
+def add_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()
+
+ 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:
+ 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'))
+ f.write('{}classpath files(\'{}\')\n'.format(indent, utils.R8_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:
+ f.write(line)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 25385dd..0c6ea6c 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -10,7 +10,13 @@
import sys
import utils
+import as_utils
+
SHRINKERS = ['r8', 'r8full', 'proguard']
+WORKING_DIR = utils.BUILD
+
+if 'R8_BENCHMARK_DIR' in os.environ and os.path.isdir(os.environ['R8_BENCHMARK_DIR']):
+ WORKING_DIR = os.environ['R8_BENCHMARK_DIR']
APPS = {
# 'app-name': {
@@ -19,6 +25,10 @@
# 'archives_base_name': ... (default same as app_module)
# 'flavor': ... (default no flavor)
# },
+ 'AntennaPod': {
+ 'git_repo': 'https://github.com/AntennaPod/AntennaPod.git',
+ 'flavor': 'play',
+ },
'tachiyomi': {
'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
'flavor': 'standard',
@@ -31,6 +41,13 @@
},
}
+def IsBuiltWithR8(apk):
+ script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
+ return '~~R8' in subprocess.check_output(['python', script, apk]).strip()
+
+def IsTrackedByGit(file):
+ return subprocess.check_output(['git', 'ls-files', file]).strip() != ''
+
def GitClone(git_url):
return subprocess.check_output(['git', 'clone', git_url]).strip()
@@ -40,15 +57,35 @@
def GitCheckout(file):
return subprocess.check_output(['git', 'checkout', file]).strip()
+def MoveApkToDest(apk, apk_dest):
+ print('Moving `{}` to `{}`'.format(apk, apk_dest))
+ assert os.path.isfile(apk)
+ if os.path.isfile(apk_dest):
+ os.remove(apk_dest)
+ os.rename(apk, apk_dest)
+
def ParseOptions(argv):
result = optparse.OptionParser()
result.add_option('--app',
help='What app to run on',
choices=APPS.keys())
+ result.add_option('--sign_apks',
+ help='Whether the APKs should be signed',
+ default=False,
+ action='store_true')
+ result.add_option('--shrinker',
+ help='The shrinker to use (by default, all are run)',
+ choices=SHRINKERS)
+ result.add_option('--use_tot',
+ help='Whether to use the ToT version of R8',
+ default=False,
+ action='store_true')
return result.parse_args(argv)
def main(argv):
(options, args) = ParseOptions(argv)
+ assert not options.use_tot or os.path.isfile(utils.R8_JAR), (
+ 'Cannot build from ToT without r8.jar')
# Common environment setup.
user_home = os.path.expanduser('~')
@@ -69,26 +106,38 @@
flavor = config.get('flavor')
# Checkout and build in the build directory.
- working_dir = utils.BUILD
- checkout_dir = os.path.join(working_dir, app)
+ checkout_dir = os.path.join(WORKING_DIR, app)
if not os.path.exists(checkout_dir):
- with utils.ChangedWorkingDirectory(working_dir):
+ with utils.ChangedWorkingDirectory(WORKING_DIR):
GitClone(git_repo)
else:
with utils.ChangedWorkingDirectory(checkout_dir):
GitPull()
+ if options.use_tot:
+ as_utils.add_r8_dependency(checkout_dir)
+ else:
+ as_utils.remove_r8_dependency(checkout_dir)
+
with utils.ChangedWorkingDirectory(checkout_dir):
for shrinker in SHRINKERS:
+ if options.shrinker is not None and shrinker != options.shrinker:
+ continue
+
+ print('Building with {}'.format(shrinker))
# Ensure that gradle.properties are not modified before modifying it to
# select shrinker.
- GitCheckout('gradle.properties')
+ if IsTrackedByGit('gradle.properties'):
+ GitCheckout('gradle.properties')
with open("gradle.properties", "a") as gradle_properties:
- if shrinker == 'r8full':
- gradle_properties.write('\nandroid.enableR8.fullMode=true\n')
- if shrinker == 'proguard':
+ if 'r8' in shrinker:
+ gradle_properties.write('\nandroid.enableR8=true\n')
+ if shrinker == 'r8full':
+ gradle_properties.write('android.enableR8.fullMode=true\n')
+ else:
+ assert shrinker == 'proguard'
gradle_properties.write('\nandroid.enableR8=false\n')
out = os.path.join(checkout_dir, 'out', shrinker)
@@ -97,35 +146,51 @@
env = os.environ.copy()
env['ANDROID_HOME'] = android_home
- releaseTarget = app_module + ':' + 'assembleRelease'
- subprocess.check_call(
- ['./gradlew', '--no-daemon', 'clean', releaseTarget], env=env)
+ env['JAVA_OPTS'] = '-ea'
+ releaseTarget = app_module + ':' + 'assemble' + (
+ flavor.capitalize() if flavor else '') + 'Release'
- unsigned_apk_name = (archives_base_name
- + (('-' + flavor) if flavor else '')
- + '-release-unsigned.apk')
- signed_apk_name = archives_base_name + '-release.apk'
+ cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget, '--stacktrace']
+ utils.PrintCmd(cmd)
+ subprocess.check_call(cmd, env=env)
+
+ apk_base_name = (archives_base_name
+ + (('-' + flavor) if flavor else '') + '-release')
+ signed_apk_name = apk_base_name + '.apk'
+ unsigned_apk_name = apk_base_name + '-unsigned.apk'
build_output_apks = os.path.join(app_module, 'build', 'outputs', 'apk')
if flavor:
- unsigned_apk = os.path.join(
- build_output_apks, flavor, 'release', unsigned_apk_name)
+ build_output_apks = os.path.join(build_output_apks, flavor, 'release')
else:
- unsigned_apk = os.path.join(
- build_output_apks, 'release', unsigned_apk_name)
+ build_output_apks = os.path.join(build_output_apks, 'release')
- signed_apk = os.path.join(out, signed_apk_name)
+ signed_apk = os.path.join(build_output_apks, signed_apk_name)
+ unsigned_apk = os.path.join(build_output_apks, unsigned_apk_name)
- keystore = 'app.keystore'
- keystore_password = 'android'
- apk_utils.sign_with_apksigner(
- android_build_tools,
- unsigned_apk,
- signed_apk,
- keystore,
- keystore_password)
+ if options.sign_apks and not os.path.isfile(signed_apk):
+ assert os.path.isfile(unsigned_apk)
+ if options.sign_apks:
+ keystore = 'app.keystore'
+ keystore_password = 'android'
+ apk_utils.sign_with_apksigner(
+ android_build_tools,
+ unsigned_apk,
+ signed_apk,
+ keystore,
+ keystore_password)
- GitCheckout('gradle.properties')
+ if os.path.isfile(signed_apk):
+ apk_dest = os.path.join(out, signed_apk_name)
+ MoveApkToDest(signed_apk, apk_dest)
+ else:
+ apk_dest = os.path.join(out, unsigned_apk_name)
+ MoveApkToDest(unsigned_apk, apk_dest)
+
+ assert IsBuiltWithR8(apk_dest) == ('r8' in shrinker)
+
+ if IsTrackedByGit('gradle.properties'):
+ GitCheckout('gradle.properties')
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))