| Index: build/android/incremental_install/installer.py
|
| diff --git a/build/android/incremental_install.py b/build/android/incremental_install/installer.py
|
| similarity index 55%
|
| rename from build/android/incremental_install.py
|
| rename to build/android/incremental_install/installer.py
|
| index d88a04d6d7680bcc2419e631af7a71d2608c2c69..1d70335491e3016a155c6763e8ff25bd9817df31 100755
|
| --- a/build/android/incremental_install.py
|
| +++ b/build/android/incremental_install/installer.py
|
| @@ -9,20 +9,32 @@
|
| import argparse
|
| import glob
|
| import logging
|
| +import os
|
| import posixpath
|
| +import shutil
|
| import sys
|
| -import time
|
|
|
| +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
|
| from devil.android import apk_helper
|
| from devil.android import device_utils
|
| from devil.android import device_errors
|
| +from devil.android.sdk import version_codes
|
| from devil.utils import reraiser_thread
|
| from pylib import constants
|
| from pylib.utils import run_tests_helper
|
| +from pylib.utils import time_profile
|
| +
|
| +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
|
| +from util import build_utils
|
| +
|
| +
|
| +def _TransformDexPaths(paths):
|
| + """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"]."""
|
| + prefix_len = len(os.path.commonprefix(paths))
|
| + return [p[prefix_len:].replace(os.sep, '.') for p in paths]
|
|
|
|
|
| def main():
|
| - start_time = time.time()
|
| parser = argparse.ArgumentParser()
|
| parser.add_argument('apk_path',
|
| help='The path to the APK to install.')
|
| @@ -33,6 +45,10 @@ def main():
|
| 'Can be specified multiple times.')
|
| parser.add_argument('--lib-dir',
|
| help='Path to native libraries directory.')
|
| + parser.add_argument('--dex-files',
|
| + help='List of dex files to push.',
|
| + action='append',
|
| + default=[])
|
| parser.add_argument('-d', '--device', dest='device',
|
| help='Target device for apk to install on.')
|
| parser.add_argument('--uninstall',
|
| @@ -54,12 +70,16 @@ def main():
|
|
|
| args = parser.parse_args()
|
|
|
| - logging.basicConfig(format='%(asctime)s (%(thread)d) %(message)s')
|
| run_tests_helper.SetLogLevel(args.verbose_count)
|
| constants.SetBuildType('Debug')
|
| if args.output_directory:
|
| constants.SetOutputDirectory(args.output_directory)
|
|
|
| + main_timer = time_profile.TimeProfile()
|
| + install_timer = time_profile.TimeProfile()
|
| + push_native_timer = time_profile.TimeProfile()
|
| + push_dex_timer = time_profile.TimeProfile()
|
| +
|
| if args.device:
|
| # Retries are annoying when commands fail for legitimate reasons. Might want
|
| # to enable them if this is ever used on bots though.
|
| @@ -80,19 +100,26 @@ def main():
|
| msg += ' %s (%s)\n' % (d, desc)
|
| raise Exception(msg)
|
|
|
| - apk_package = apk_helper.ApkHelper(args.apk_path).GetPackageName()
|
| + apk_help = apk_helper.ApkHelper(args.apk_path)
|
| + apk_package = apk_help.GetPackageName()
|
| device_incremental_dir = '/data/local/tmp/incremental-app-%s' % apk_package
|
|
|
| if args.uninstall:
|
| - logging.info('Uninstalling .apk')
|
| device.Uninstall(apk_package)
|
| - logging.info('Removing side-loaded files')
|
| device.RunShellCommand(['rm', '-rf', device_incremental_dir],
|
| check_return=True)
|
| + logging.info('Uninstall took %s seconds.', main_timer.GetDelta())
|
| return
|
|
|
| + if device.build_version_sdk >= version_codes.MARSHMALLOW:
|
| + if apk_help.HasIsolatedProcesses():
|
| + raise Exception('Cannot use perform incremental installs on Android M+ '
|
| + 'without first disabling isolated processes. Use GN arg: '
|
| + 'disable_incremental_isolated_processes=true to do so.')
|
| +
|
| # Install .apk(s) if any of them have changed.
|
| def do_install():
|
| + install_timer.Start()
|
| if args.splits:
|
| splits = []
|
| for split_glob in args.splits:
|
| @@ -101,25 +128,60 @@ def main():
|
| allow_cached_props=True)
|
| else:
|
| device.Install(args.apk_path, reinstall=True)
|
| - logging.info('Finished installing .apk')
|
| + install_timer.Stop(log=False)
|
|
|
| - # Push .so files to the device (if they have changed).
|
| - def do_push_libs():
|
| + # Push .so and .dex files to the device (if they have changed).
|
| + def do_push_files():
|
| if args.lib_dir:
|
| + push_native_timer.Start()
|
| device_lib_dir = posixpath.join(device_incremental_dir, 'lib')
|
| device.PushChangedFiles([(args.lib_dir, device_lib_dir)],
|
| delete_device_stale=True)
|
| - logging.info('Finished pushing native libs')
|
| + push_native_timer.Stop(log=False)
|
| +
|
| + if args.dex_files:
|
| + push_dex_timer.Start()
|
| + # Put all .dex files to be pushed into a temporary directory so that we
|
| + # can use delete_device_stale=True.
|
| + with build_utils.TempDir() as temp_dir:
|
| + device_dex_dir = posixpath.join(device_incremental_dir, 'dex')
|
| + # Ensure no two files have the same name.
|
| + transformed_names = _TransformDexPaths(args.dex_files)
|
| + for src_path, dest_name in zip(args.dex_files, transformed_names):
|
| + shutil.copyfile(src_path, os.path.join(temp_dir, dest_name))
|
| + device.PushChangedFiles([(temp_dir, device_dex_dir)],
|
| + delete_device_stale=True)
|
| + push_dex_timer.Stop(log=False)
|
| +
|
| + # Create 2 lock files:
|
| + # * install.lock tells the app to pause on start-up (until we release it).
|
| + # * firstrun.lock is used by the app to pause all secondary processes until
|
| + # the primary process finishes loading the .dex / .so files.
|
| + def create_lock_files():
|
| + # Creates or zeros out lock files.
|
| + cmd = ('D="%s";'
|
| + 'mkdir -p $D &&'
|
| + 'echo -n >$D/install.lock 2>$D/firstrun.lock')
|
| + device.RunShellCommand(cmd % device_incremental_dir, check_return=True)
|
| +
|
| + # The firstrun.lock is released by the app itself.
|
| + def release_installer_lock():
|
| + device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir,
|
| + check_return=True)
|
|
|
| + create_lock_files()
|
| # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't
|
| # been designed for multi-threading. Enabling only because this is a
|
| # developer-only tool.
|
| if args.no_threading:
|
| do_install()
|
| - do_push_libs()
|
| + do_push_files()
|
| else:
|
| - reraiser_thread.RunAsync((do_install, do_push_libs))
|
| - logging.info('Took %s seconds', round(time.time() - start_time, 1))
|
| + reraiser_thread.RunAsync((do_install, do_push_files))
|
| + release_installer_lock()
|
| + logging.info('Took %s seconds (install=%s, libs=%s, dex=%s)',
|
| + main_timer.GetDelta(), install_timer.GetDelta(),
|
| + push_native_timer.GetDelta(), push_dex_timer.GetDelta())
|
|
|
|
|
| if __name__ == '__main__':
|
|
|