Add flag --quiet to run_on_as_app.py
Change-Id: I1fc6ce3a8e172c439e1297ee5ee104421c11197c
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index 6013e6f..fa46485 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -8,7 +8,6 @@
import optparse
import os
import shutil
-import subprocess
import sys
import utils
@@ -32,6 +31,9 @@
parser.add_option('--adb-options',
help='additional adb options when running adb',
default=None)
+ parser.add_option('--quiet',
+ help='disable verbose logging',
+ default=False)
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -41,44 +43,44 @@
def findKeystore():
return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
-def repack(processed_out, original_apk, temp):
+def repack(processed_out, original_apk, temp, quiet):
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(original_apk, processed_apk)
if not processed_out:
- print 'Using original APK as is'
+ utils.Print('Using original APK as is', quiet=quiet)
return processed_apk
- print 'Repacking APK with dex files from', processed_apk
- with utils.ChangedWorkingDirectory(temp):
+ utils.Print(
+ 'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
+ with utils.ChangedWorkingDirectory(temp, quiet=quiet):
cmd = ['zip', '-d', 'processed.apk', '*.dex']
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
cmd = ['unzip', processed_out, '-d', temp]
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ if quiet:
+ cmd.insert(1, '-q')
+ utils.RunCmd(cmd, quiet=quiet)
processed_out = temp
- with utils.ChangedWorkingDirectory(processed_out):
+ with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
dex = glob.glob('*.dex')
cmd = ['zip', '-u', '-9', processed_apk] + dex
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
return processed_apk
-def sign(unsigned_apk, keystore, temp):
+def sign(unsigned_apk, keystore, temp, quiet):
signed_apk = os.path.join(temp, 'unaligned.apk')
- apk_utils.sign(unsigned_apk, signed_apk, keystore)
+ apk_utils.sign(unsigned_apk, signed_apk, keystore, quiet=quiet)
return signed_apk
-def align(signed_apk, temp):
- print 'Aligning'
+def align(signed_apk, temp, quiet):
+ utils.Print('Aligning', quiet=quiet)
aligned_apk = os.path.join(temp, 'aligned.apk')
cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
- print ' '.join(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
return signed_apk
def masseur(
- apk, dex=None, out=None, adb_options=None, keystore=None, install=False):
+ apk, dex=None, out=None, adb_options=None, keystore=None, install=False,
+ quiet=False):
if not out:
out = os.path.basename(apk)
if not keystore:
@@ -86,23 +88,23 @@
with utils.TempDir() as temp:
processed_apk = None
if dex:
- processed_apk = repack(dex, apk, temp)
+ processed_apk = repack(dex, apk, temp, quiet)
else:
- print 'Signing original APK without modifying dex files'
+ utils.Print(
+ 'Signing original APK without modifying dex files', quiet=quiet)
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(apk, processed_apk)
- signed_apk = sign(processed_apk, keystore, temp)
- aligned_apk = align(signed_apk, temp)
- print 'Writing result to', out
+ signed_apk = sign(processed_apk, keystore, temp, quiet=quiet)
+ aligned_apk = align(signed_apk, temp, quiet=quiet)
+ utils.Print('Writing result to {}'.format(out), quiet=quiet)
shutil.copyfile(aligned_apk, out)
- adb_cmd = ['adb']
- if adb_options:
- adb_cmd.extend(
- [option for option in adb_options.split(' ') if option])
if install:
+ adb_cmd = ['adb']
+ if adb_options:
+ adb_cmd.extend(
+ [option for option in adb_options.split(' ') if option])
adb_cmd.extend(['install', '-t', '-r', '-d', out]);
- utils.PrintCmd(adb_cmd)
- subprocess.check_call(adb_cmd)
+ utils.RunCmd(adb_cmd, quiet=quiet)
def main():
(options, apk) = parse_options()
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index aaae1e4..5a6aa94 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -7,11 +7,10 @@
import subprocess
import utils
-def sign(unsigned_apk, signed_apk, keystore):
- print 'Signing (ignore the warnings)'
+def sign(unsigned_apk, signed_apk, keystore, quiet=False):
+ utils.Print('Signing (ignore the warnings)', quiet=quiet)
cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
- utils.PrintCmd(cmd)
- subprocess.call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
cmd = [
'jarsigner',
'-sigalg', 'SHA1withRSA',
@@ -22,8 +21,7 @@
unsigned_apk,
'androiddebugkey'
]
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
cmd = [
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 9c2c8c8..10fb29f 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -30,23 +30,15 @@
'Unexpected line with \'dependencies {\'')
is_inside_dependencies = True
if is_inside_dependencies:
- if '/r8.jar' in stripped:
- if minified:
- # Skip line to avoid dependency on r8.jar
- continue
- added_r8_dependency = True
- elif '/r8lib.jar' in stripped:
- if not minified:
- # Skip line to avoid dependency on r8lib.jar
- continue
- added_r8_dependency = True
+ if '/r8.jar' in stripped or '/r8lib.jar' in stripped:
+ # Skip line to avoid dependency on r8.jar
+ continue
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 = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
- f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
- added_r8_dependency = True
+ indent = ''.ljust(line.index('classpath'))
+ jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
+ f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
+ added_r8_dependency = True
elif stripped == '}':
is_inside_dependencies = False
f.write(line)
@@ -147,8 +139,9 @@
# one of the predefined locations.
assert False
-def Move(src, dst):
- print('Moving `{}` to `{}`'.format(src, dst))
+def Move(src, dst, quiet=False):
+ if not quiet:
+ print('Moving `{}` to `{}`'.format(src, dst))
dst_parent = os.path.dirname(dst)
if not os.path.isdir(dst_parent):
os.makedirs(dst_parent)
@@ -158,15 +151,15 @@
os.remove(dst)
os.rename(src, dst)
-def MoveDir(src, dst):
+def MoveDir(src, dst, quiet=False):
assert os.path.isdir(src)
- Move(src, dst)
+ Move(src, dst, quiet=quiet)
-def MoveFile(src, dst):
+def MoveFile(src, dst, quiet=False):
assert os.path.isfile(src)
- Move(src, dst)
+ Move(src, dst, quiet=quiet)
-def MoveProfileReportTo(dest_dir, build_stdout):
+def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
html_file = None
profile_message = 'See the profiling report at: '
for line in build_stdout:
@@ -181,11 +174,12 @@
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'))
+ MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet)
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))
+ MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name),
+ quiet=quiet)
def ParseProfileReport(profile_dir):
html_file = os.path.join(profile_dir, 'index.html')
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 128b6e3..ae17feb 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -42,6 +42,8 @@
'app_id': 'de.danoeh.antennapod',
'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
'flavor': 'play',
+ 'min_sdk': 14,
+ 'compile_sdk': 26
},
'apps-android-wikipedia': {
'app_id': 'org.wikipedia',
@@ -128,7 +130,7 @@
dex_size += z.getinfo(filename).file_size
return dex_size
-def IsBuiltWithR8(apk, temp_dir):
+def IsBuiltWithR8(apk, temp_dir, options):
r8_jar = os.path.join(temp_dir, 'r8.jar')
# Use the copy of r8.jar if it is there.
@@ -138,7 +140,7 @@
script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
cmd = ['python', script, apk]
- utils.PrintCmd(cmd)
+ utils.PrintCmd(cmd, quiet=options.quiet)
return '~~R8' in subprocess.check_output(cmd).strip()
def IsMinifiedR8(shrinker):
@@ -157,9 +159,13 @@
def GitCheckout(file):
return subprocess.check_output(['git', 'checkout', file]).strip()
-def InstallApkOnEmulator(apk_dest):
- subprocess.check_call(
- ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+def InstallApkOnEmulator(apk_dest, options):
+ cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest]
+ if options.quiet:
+ with open(os.devnull, 'w') as devnull:
+ subprocess.check_call(cmd, stdout=devnull)
+ else:
+ subprocess.check_call(cmd)
def PercentageDiffAsString(before, after):
if after < before:
@@ -167,7 +173,7 @@
else:
return '+' + str(round((after - before) / before * 100)) + '%'
-def UninstallApkOnEmulator(app, config):
+def UninstallApkOnEmulator(app, config, options):
app_id = config.get('app_id')
process = subprocess.Popen(
['adb', 'uninstall', app_id],
@@ -215,10 +221,10 @@
result = {}
if not os.path.exists(checkout_dir):
- with utils.ChangedWorkingDirectory(WORKING_DIR):
+ with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
GitClone(git_repo)
elif options.pull:
- with utils.ChangedWorkingDirectory(checkout_dir):
+ with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
# Checkout build.gradle to avoid merge conflicts.
if IsTrackedByGit('build.gradle'):
GitCheckout('build.gradle')
@@ -240,7 +246,7 @@
def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir, temp_dir):
result_per_shrinker = {}
- with utils.ChangedWorkingDirectory(checkout_dir):
+ with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
for shrinker in SHRINKERS:
if options.shrinker and shrinker not in options.shrinker:
continue
@@ -280,15 +286,9 @@
# Build app with gradle using -D...keepRuleSynthesisForRecompilation=
# true.
out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
- extra_env_vars = {
- 'JAVA_OPTS': ' '.join([
- '-ea:com.android.tools.r8...',
- '-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true'
- ])
- }
(apk_dest, profile_dest_dir, ext_proguard_config_file) = \
BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
- temp_dir, options, extra_env_vars)
+ temp_dir, options, keepRuleSynthesisForRecompilation=True)
dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
recompilation_result = {
'apk_dest': apk_dest,
@@ -314,8 +314,9 @@
recompiled_apk_dest = os.path.join(
checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
RebuildAppWithShrinker(
- previous_apk, recompiled_apk_dest, ext_proguard_config_file,
- shrinker, min_sdk, compile_sdk, temp_dir)
+ app, previous_apk, recompiled_apk_dest,
+ ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
+ options, temp_dir)
recompilation_result = {
'apk_dest': recompiled_apk_dest,
'build_status': 'success',
@@ -334,13 +335,20 @@
result_per_shrinker[shrinker] = result
+ if not options.app:
+ print('')
+ LogResultsForApp(app, result_per_shrinker, options)
+ print('')
+
return result_per_shrinker
def BuildAppWithShrinker(
app, config, shrinker, checkout_dir, out_dir, temp_dir, options,
- env_vars=None):
- print()
- print('Building {} with {}'.format(app, shrinker))
+ keepRuleSynthesisForRecompilation=False):
+ print('Building {} with {}{}'.format(
+ app,
+ shrinker,
+ ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
# Add/remove 'r8.jar' from top-level build.gradle.
if options.disable_tot:
@@ -361,11 +369,9 @@
as_utils.SetPrintConfigurationDirective(
app, config, checkout_dir, proguard_config_dest)
- env = os.environ.copy()
+ env = {}
env['ANDROID_HOME'] = android_home
env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
- if env_vars:
- env.update(env_vars)
releaseTarget = config.get('releaseTarget')
if not releaseTarget:
@@ -381,20 +387,12 @@
'--profile', '--stacktrace',
'-Pandroid.enableR8=' + str(enableR8).lower(),
'-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+ if keepRuleSynthesisForRecompilation:
+ cmd.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
if options.gradle_flags:
cmd.extend(options.gradle_flags.split(' '))
- utils.PrintCmd(cmd)
- build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
- stdout = []
- while True:
- line = build_process.stdout.readline()
- if line != b'':
- stripped = line.rstrip()
- stdout.append(stripped)
- print(stripped)
- else:
- break
+ stdout = utils.RunCmd(cmd, env, quiet=options.quiet)
apk_base_name = (archives_base_name
+ (('-' + flavor) if flavor else '') + '-release')
@@ -425,25 +423,27 @@
if os.path.isfile(signed_apk):
apk_dest = os.path.join(out_dir, signed_apk_name)
- as_utils.MoveFile(signed_apk, apk_dest)
+ as_utils.MoveFile(signed_apk, apk_dest, quiet=options.quiet)
else:
apk_dest = os.path.join(out_dir, unsigned_apk_name)
- as_utils.MoveFile(unsigned_apk, apk_dest)
+ as_utils.MoveFile(unsigned_apk, apk_dest, quiet=options.quiet)
- assert IsBuiltWithR8(apk_dest, temp_dir) == ('r8' in shrinker), (
+ assert IsBuiltWithR8(apk_dest, temp_dir, options) == ('r8' in shrinker), (
'Unexpected marker in generated APK for {}'.format(shrinker))
profile_dest_dir = os.path.join(out_dir, 'profile')
- as_utils.MoveProfileReportTo(profile_dest_dir, stdout)
+ as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)
return (apk_dest, profile_dest_dir, proguard_config_dest)
def RebuildAppWithShrinker(
- apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
- temp_dir):
+ app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
+ options, temp_dir):
assert 'r8' in shrinker
assert apk_dest.endswith('.apk')
+ print('Rebuilding {} with {}'.format(app, shrinker))
+
# Compile given APK with shrinker to temporary zip file.
android_jar = utils.get_android_jar(compile_sdk)
r8_jar = os.path.join(
@@ -462,20 +462,19 @@
cmd.append('--lib')
cmd.append(android_optional_jar)
- utils.PrintCmd(cmd)
-
- subprocess.check_output(cmd)
+ utils.RunCmd(cmd, quiet=options.quiet)
# Make a copy of the given APK, move the newly generated dex files into the
# copied APK, and then sign the APK.
- apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest)
+ apk_masseur.masseur(
+ apk, dex=zip_dest, out=apk_dest, quiet=options.quiet)
def RunMonkey(app, config, options, apk_dest):
if not WaitForEmulator():
return False
- UninstallApkOnEmulator(app, config)
- InstallApkOnEmulator(apk_dest)
+ UninstallApkOnEmulator(app, config, options)
+ InstallApkOnEmulator(apk_dest, options)
app_id = config.get('app_id')
number_of_events_to_generate = options.monkey_events
@@ -486,27 +485,28 @@
cmd = ['adb', 'shell', 'monkey', '-p', app_id, '-s', str(random_seed),
str(number_of_events_to_generate)]
- utils.PrintCmd(cmd)
try:
- stdout = subprocess.check_output(cmd)
+ stdout = utils.RunCmd(cmd, quiet=options.quiet)
succeeded = (
'Events injected: {}'.format(number_of_events_to_generate) in stdout)
except subprocess.CalledProcessError as e:
succeeded = False
- UninstallApkOnEmulator(app, config)
+ UninstallApkOnEmulator(app, config, options)
return succeeded
def LogResultsForApps(result_per_shrinker_per_app, options):
- for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+ print('')
+ for app, result_per_shrinker in sorted(
+ result_per_shrinker_per_app.iteritems()):
LogResultsForApp(app, result_per_shrinker, options)
def LogResultsForApp(app, result_per_shrinker, options):
print(app + ':')
- if result_per_shrinker.get('status') != 'success':
+ if result_per_shrinker.get('status', 'success') != 'success':
error_message = result_per_shrinker.get('error_message')
print(' skipped ({})'.format(error_message))
return
@@ -613,6 +613,10 @@
action='store_true')
result.add_option('--gradle-flags', '--gradle_flags',
help='Flags to pass in to gradle')
+ result.add_option('--quiet',
+ help='Disable verbose logging',
+ default=False,
+ action='store_true')
(options, args) = result.parse_args(argv)
if options.disable_tot:
# r8.jar is required for recompiling the generated APK
@@ -653,7 +657,7 @@
result_per_shrinker_per_app[options.app] = GetResultsForApp(
options.app, APPS.get(options.app), options, temp_dir)
else:
- for app, config in APPS.iteritems():
+ for app, config in sorted(APPS.iteritems()):
if not config.get('skip', False):
result_per_shrinker_per_app[app] = GetResultsForApp(
app, config, options, temp_dir)
diff --git a/tools/utils.py b/tools/utils.py
index 72febdc..8c37a55 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -54,13 +54,86 @@
R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
RETRACE_JAR = os.path.join(THIRD_PARTY, 'proguard', 'proguard6.0.1', 'lib', 'retrace.jar')
-def PrintCmd(s):
- if type(s) is list:
- s = ' '.join(s)
- print 'Running: %s' % s
+def Print(s, quiet=False):
+ if quiet:
+ return
+ print(s)
+
+def Warn(message):
+ CRED = '\033[91m'
+ CEND = '\033[0m'
+ print(CRED + message + CEND)
+
+def PrintCmd(cmd, env=None, quiet=False):
+ if quiet:
+ return
+ if type(cmd) is list:
+ cmd = ' '.join(cmd)
+ if env:
+ env = ' '.join(['{}=\"{}\"'.format(x, y) for x, y in env.iteritems()])
+ print('Running: {} {}'.format(env, cmd))
+ else:
+ print('Running: {}'.format(cmd))
# I know this will hit os on windows eventually if we don't do this.
sys.stdout.flush()
+class ProgressLogger(object):
+ CLEAR_LINE = '\033[K'
+ UP = '\033[F'
+
+ def __init__(self, quiet=False):
+ self._count = 0
+ self._has_printed = False
+ self._quiet = quiet
+
+ def log(self, text):
+ if self._quiet:
+ if self._has_printed:
+ sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+ if len(text) > 140:
+ text = text[0:140] + '...'
+ print(text)
+ self._has_printed = True
+
+ def done(self):
+ if self._quiet and self._has_printed:
+ sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+ print('')
+ sys.stdout.write(ProgressLogger.UP)
+
+def RunCmd(cmd, env_vars=None, quiet=False):
+ PrintCmd(cmd, env=env_vars, quiet=quiet)
+ env = os.environ.copy()
+ if env_vars:
+ env.update(env_vars)
+ process = subprocess.Popen(
+ cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ stdout = []
+ logger = ProgressLogger(quiet=quiet)
+ failed = False
+ while True:
+ line = process.stdout.readline()
+ if line != b'':
+ stripped = line.rstrip()
+ stdout.append(stripped)
+ logger.log(stripped)
+
+ # TODO(christofferqa): r8 should fail with non-zero exit code.
+ if ('AssertionError' in stripped
+ or 'CompilationError' in stripped
+ or 'CompilationFailedException' in stripped
+ or 'Compilation failed' in stripped):
+ failed = True
+ else:
+ logger.done()
+ exit_code = process.poll()
+ if exit_code or failed:
+ for line in stdout:
+ Warn(line)
+ raise subprocess.CalledProcessError(
+ exit_code, cmd, output='\n'.join(stdout))
+ return stdout
+
def IsWindows():
return os.name == 'nt'
@@ -196,16 +269,19 @@
shutil.rmtree(self._temp_dir, ignore_errors=True)
class ChangedWorkingDirectory(object):
- def __init__(self, working_directory):
+ def __init__(self, working_directory, quiet=False):
+ self._quiet = quiet
self._working_directory = working_directory
def __enter__(self):
self._old_cwd = os.getcwd()
- print 'Enter directory = ', self._working_directory
+ if not self._quiet:
+ print 'Enter directory:', self._working_directory
os.chdir(self._working_directory)
def __exit__(self, *_):
- print 'Enter directory = ', self._old_cwd
+ if not self._quiet:
+ print 'Enter directory:', self._old_cwd
os.chdir(self._old_cwd)
# Reading Android CTS test_result.xml