def Uninstall(device, package): """Uninstalls and removes all incremental files for the given package.""" main_timer = time_profile.TimeProfile() device.Uninstall(package) device.RunShellCommand( ['rm', '-rf', _GetDeviceIncrementalDir(package)], check_return=True) logging.info('Uninstall took %s seconds.', main_timer.GetDelta())
def LaunchTempEmulators(emulator_count, abi, api_level, wait_for_boot=True): """Create and launch temporary emulators and wait for them to boot. Args: emulator_count: number of emulators to launch. abi: the emulator target platform api_level: the api level (e.g., 19 for Android v4.4 - KitKat release) wait_for_boot: whether or not to wait for emulators to boot up Returns: List of emulators. """ emulators = [] for n in xrange(emulator_count): t = time_profile.TimeProfile('Emulator launch %d' % n) # Creates a temporary AVD. avd_name = 'run_tests_avd_%d' % n logging.info('Emulator launch %d with avd_name=%s and api=%d', n, avd_name, api_level) emulator = Emulator(avd_name, abi) emulator.CreateAVD(api_level) emulator.Launch(kill_all_emulators=n == 0) t.Stop() emulators.append(emulator) # Wait for all emulators to boot completed. if wait_for_boot: for emulator in emulators: emulator.ConfirmLaunch(True) return emulators
def _Execute(concurrently, *funcs): """Calls all functions in |funcs| concurrently or in sequence.""" timer = time_profile.TimeProfile() if concurrently: reraiser_thread.RunAsync(funcs) else: for f in funcs: f() timer.Stop(log=False) return timer
def Uninstall(device, package, enable_device_cache=False): """Uninstalls and removes all incremental files for the given package.""" main_timer = time_profile.TimeProfile() device.Uninstall(package) if enable_device_cache: # Uninstall is rare, so just wipe the cache in this case. cache_path = _DeviceCachePath(device) if os.path.exists(cache_path): os.unlink(cache_path) device.RunShellCommand( ['rm', '-rf', _GetDeviceIncrementalDir(package)], check_return=True) logging.info('Uninstall took %s seconds.', main_timer.GetDelta())
def LaunchTempEmulators(emulator_count, abi, api_level, enable_kvm=False, kill_and_launch=True, sdcard_size=DEFAULT_SDCARD_SIZE, storage_size=DEFAULT_STORAGE_SIZE, wait_for_boot=True, headless=False): """Create and launch temporary emulators and wait for them to boot. Args: emulator_count: number of emulators to launch. abi: the emulator target platform api_level: the api level (e.g., 19 for Android v4.4 - KitKat release) wait_for_boot: whether or not to wait for emulators to boot up headless: running emulator with no ui Returns: List of emulators. """ emulators = [] for n in xrange(emulator_count): t = time_profile.TimeProfile('Emulator launch %d' % n) # Creates a temporary AVD. avd_name = 'run_tests_avd_%d' % n logging.info('Emulator launch %d with avd_name=%s and api=%d', n, avd_name, api_level) emulator = Emulator(avd_name, abi, enable_kvm=enable_kvm, sdcard_size=sdcard_size, storage_size=storage_size, headless=headless) emulator.CreateAVD(api_level) emulator.Launch(kill_all_emulators=(n == 0 and kill_and_launch)) t.Stop() emulators.append(emulator) # Wait for all emulators to boot completed. if wait_for_boot: for emulator in emulators: emulator.ConfirmLaunch(True) logging.info('All emulators are fully booted') return emulators
def Install(device, apk, split_globs=None, native_libs=None, dex_files=None, enable_device_cache=False, use_concurrency=True, show_proguard_warning=False, permissions=()): """Installs the given incremental apk and all required supporting files. Args: device: A DeviceUtils instance. apk: The path to the apk, or an ApkHelper instance. split_globs: Glob patterns for any required apk splits (optional). native_libs: List of app's native libraries (optional). dex_files: List of .dex.jar files that comprise the app's Dalvik code. enable_device_cache: Whether to enable on-device caching of checksums. use_concurrency: Whether to speed things up using multiple threads. show_proguard_warning: Whether to print a warning about Proguard not being enabled after installing. permissions: A list of the permissions to grant, or None to grant all non-blacklisted permissions in the manifest. """ main_timer = time_profile.TimeProfile() install_timer = time_profile.TimeProfile() push_native_timer = time_profile.TimeProfile() push_dex_timer = time_profile.TimeProfile() apk = apk_helper.ToHelper(apk) apk_package = apk.GetPackageName() device_incremental_dir = _GetDeviceIncrementalDir(apk_package) # Install .apk(s) if any of them have changed. def do_install(): install_timer.Start() if split_globs: splits = [] for split_glob in split_globs: splits.extend((f for f in glob.glob(split_glob))) device.InstallSplitApk(apk, splits, reinstall=True, allow_cached_props=True, permissions=permissions) else: device.Install(apk, reinstall=True, permissions=permissions) install_timer.Stop(log=False) # Push .so and .dex files to the device (if they have changed). def do_push_files(): if native_libs: push_native_timer.Start() with build_utils.TempDir() as temp_dir: device_lib_dir = posixpath.join(device_incremental_dir, 'lib') for path in native_libs: # Note: Can't use symlinks as they don't work when # "adb push parent_dir" is used (like we do here). shutil.copy(path, os.path.join(temp_dir, os.path.basename(path))) device.PushChangedFiles([(temp_dir, device_lib_dir)], delete_device_stale=True) push_native_timer.Stop(log=False) if 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(dex_files) for src_path, dest_name in zip(dex_files, transformed_names): # Binary targets with no extra classes create .dex.jar without a # classes.dex (which Android chokes on). if _HasClasses(src_path): shutil.copy(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) def check_selinux(): # Marshmallow has no filesystem access whatsoever. It might be possible to # get things working on Lollipop, but attempts so far have failed. # http://crbug.com/558818 has_selinux = device.build_version_sdk >= version_codes.LOLLIPOP if has_selinux and apk.HasIsolatedProcesses(): raise Exception( 'Cannot use incremental installs on Android L+ without ' 'first disabling isoloated processes.\n' 'To do so, use GN arg:\n' ' disable_incremental_isolated_processes=true') cache_path = _DeviceCachePath(device) def restore_cache(): if not enable_device_cache: logging.info('Ignoring device cache') return if os.path.exists(cache_path): logging.info('Using device cache: %s', cache_path) with open(cache_path) as f: device.LoadCacheData(f.read()) # Delete the cached file so that any exceptions cause it to be cleared. os.unlink(cache_path) else: logging.info('No device cache present: %s', cache_path) def save_cache(): with open(cache_path, 'w') as f: f.write(device.DumpCacheData()) logging.info('Wrote device cache: %s', cache_path) # 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) # 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. setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache, check_selinux) _Execute(use_concurrency, do_install, do_push_files) finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache) logging.info( 'Took %s seconds (setup=%s, install=%s, libs=%s, dex=%s, finalize=%s)', main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(), push_native_timer.GetDelta(), push_dex_timer.GetDelta(), finalize_timer.GetDelta()) if show_proguard_warning: logging.warning( 'Target had proguard enabled, but incremental install uses ' 'non-proguarded .dex files. Performance characteristics ' 'may differ.')
def _RunATestSuite(options): """Run a single test suite. Helper for Dispatch() to allow stop/restart of the emulator across test bundles. If using the emulator, we start it on entry and stop it on exit. Args: options: options for running the tests. Returns: 0 if successful, number of failing tests otherwise. """ step_name = os.path.basename(options.test_suite).replace('-debug.apk', '') buildbot_report.PrintNamedStep(step_name) attached_devices = [] buildbot_emulators = [] if options.use_emulator: for n in range(options.emulator_count): t = time_profile.TimeProfile('Emulator launch %d' % n) avd_name = None if n > 0: # Creates a temporary AVD for the extra emulators. avd_name = 'run_tests_avd_%d' % n buildbot_emulator = emulator.Emulator(avd_name, options.fast_and_loose) buildbot_emulator.Launch(kill_all_emulators=n == 0) t.Stop() buildbot_emulators.append(buildbot_emulator) attached_devices.append(buildbot_emulator.device) # Wait for all emulators to boot completed. map(lambda buildbot_emulator: buildbot_emulator.ConfirmLaunch(True), buildbot_emulators) elif options.test_device: attached_devices = [options.test_device] else: attached_devices = android_commands.GetAttachedDevices() if not attached_devices: logging.critical('A device must be attached and online.') buildbot_report.PrintError() return 1 # Reset the test port allocation. It's important to do it before starting # to dispatch any tests. if not ports.ResetTestServerPortAllocation(): raise Exception('Failed to reset test server port.') if options.gtest_filter: logging.warning('Sharding is not possible with these configurations.') attached_devices = [attached_devices[0]] sharder = TestSharder( attached_devices, options.test_suite, options.gtest_filter, options.test_arguments, options.timeout, options.cleanup_test_files, options.tool, options.log_dump, options.fast_and_loose, options.build_type, options.webkit, options.flakiness_dashboard_server) test_results = sharder.RunShardedTests() for buildbot_emulator in buildbot_emulators: buildbot_emulator.Shutdown() return len(test_results.failed)
def Install(device, install_json, apk=None, enable_device_cache=False, use_concurrency=True, permissions=()): """Installs the given incremental apk and all required supporting files. Args: device: A DeviceUtils instance (to install to). install_json: Path to .json file or already parsed .json object. apk: An existing ApkHelper instance for the apk (optional). enable_device_cache: Whether to enable on-device caching of checksums. use_concurrency: Whether to speed things up using multiple threads. permissions: A list of the permissions to grant, or None to grant all non-denylisted permissions in the manifest. """ if isinstance(install_json, basestring): with open(install_json) as f: install_dict = json.load(f) else: install_dict = install_json if install_dict.get('dont_even_try'): raise Exception(install_dict['dont_even_try']) main_timer = time_profile.TimeProfile() install_timer = time_profile.TimeProfile() push_native_timer = time_profile.TimeProfile() merge_dex_timer = time_profile.TimeProfile() push_dex_timer = time_profile.TimeProfile() def fix_path(p): return os.path.normpath(os.path.join(constants.GetOutDirectory(), p)) if not apk: apk = apk_helper.ToHelper(fix_path(install_dict['apk_path'])) split_globs = [fix_path(p) for p in install_dict['split_globs']] native_libs = [fix_path(p) for p in install_dict['native_libs']] dex_files = [fix_path(p) for p in install_dict['dex_files']] show_proguard_warning = install_dict.get('show_proguard_warning') apk_package = apk.GetPackageName() device_incremental_dir = _GetDeviceIncrementalDir(apk_package) dex_staging_dir = os.path.join(constants.GetOutDirectory(), 'incremental-install', install_dict['apk_path']) device_dex_dir = posixpath.join(device_incremental_dir, 'dex') # Install .apk(s) if any of them have changed. def do_install(): install_timer.Start() if split_globs: splits = [] for split_glob in split_globs: splits.extend((f for f in glob.glob(split_glob))) device.InstallSplitApk(apk, splits, allow_downgrade=True, reinstall=True, allow_cached_props=True, permissions=permissions) else: device.Install(apk, allow_downgrade=True, reinstall=True, permissions=permissions) install_timer.Stop(log=False) # Push .so and .dex files to the device (if they have changed). def do_push_files(): def do_push_native(): push_native_timer.Start() if native_libs: with build_utils.TempDir() as temp_dir: device_lib_dir = posixpath.join(device_incremental_dir, 'lib') for path in native_libs: # Note: Can't use symlinks as they don't work when # "adb push parent_dir" is used (like we do here). shutil.copy( path, os.path.join(temp_dir, os.path.basename(path))) device.PushChangedFiles([(temp_dir, device_lib_dir)], delete_device_stale=True) push_native_timer.Stop(log=False) def do_merge_dex(): merge_dex_timer.Start() shards = _AllocateDexShards(dex_files) build_utils.MakeDirectory(dex_staging_dir) _CreateDexFiles(shards, dex_staging_dir, use_concurrency) merge_dex_timer.Stop(log=False) def do_push_dex(): push_dex_timer.Start() device.PushChangedFiles([(dex_staging_dir, device_dex_dir)], delete_device_stale=True) push_dex_timer.Stop(log=False) _Execute(use_concurrency, do_push_native, do_merge_dex) do_push_dex() def check_device_configured(): target_sdk_version = int(apk.GetTargetSdkVersion()) # Beta Q builds apply whitelist to targetSdk=28 as well. if target_sdk_version >= 28 and device.build_version_sdk >= 28: # In P, there are two settings: # * hidden_api_policy_p_apps # * hidden_api_policy_pre_p_apps # In Q, there is just one: # * hidden_api_policy if device.build_version_sdk == 28: setting_name = 'hidden_api_policy_p_apps' else: setting_name = 'hidden_api_policy' apis_allowed = ''.join( device.RunShellCommand( ['settings', 'get', 'global', setting_name], check_return=True)) if apis_allowed.strip() not in '01': msg = """\ Cannot use incremental installs on Android P+ without first enabling access to non-SDK interfaces (https://developer.android.com/preview/non-sdk-q). To enable access: adb -s {0} shell settings put global {1} 0 To restore back to default: adb -s {0} shell settings delete global {1}""" raise Exception(msg.format(device.serial, setting_name)) cache_path = _DeviceCachePath(device) def restore_cache(): if not enable_device_cache: return if os.path.exists(cache_path): logging.info('Using device cache: %s', cache_path) with open(cache_path) as f: device.LoadCacheData(f.read()) # Delete the cached file so that any exceptions cause it to be cleared. os.unlink(cache_path) else: logging.info('No device cache present: %s', cache_path) def save_cache(): if not enable_device_cache: return with open(cache_path, 'w') as f: f.write(device.DumpCacheData()) logging.info('Wrote device cache: %s', cache_path) # 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, shell=True, 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, shell=True) # 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. setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache, check_device_configured) _Execute(use_concurrency, do_install, do_push_files) finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache) logging.info( 'Install of %s took %s seconds (setup=%s, install=%s, lib_push=%s, ' 'dex_merge=%s dex_push=%s, finalize=%s)', os.path.basename(apk.path), main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(), push_native_timer.GetDelta(), merge_dex_timer.GetDelta(), push_dex_timer.GetDelta(), finalize_timer.GetDelta()) if show_proguard_warning: logging.warning( 'Target had proguard enabled, but incremental install uses ' 'non-proguarded .dex files. Performance characteristics ' 'may differ.')
def Install(device, apk, split_globs=None, lib_dir=None, dex_files=None, enable_device_cache=True, use_concurrency=True): """Installs the given incremental apk and all required supporting files. Args: device: A DeviceUtils instance. apk: The path to the apk, or an ApkHelper instance. split_globs: Glob patterns for any required apk splits (optional). lib_dir: Directory containing the app's native libraries (optional). dex_files: List of .dex.jar files that comprise the app's Dalvik code. enable_device_cache: Whether to enable on-device caching of checksums. use_concurrency: Whether to speed things up using multiple threads. """ main_timer = time_profile.TimeProfile() install_timer = time_profile.TimeProfile() push_native_timer = time_profile.TimeProfile() push_dex_timer = time_profile.TimeProfile() apk = apk_helper.ToHelper(apk) apk_package = apk.GetPackageName() device_incremental_dir = _GetDeviceIncrementalDir(apk_package) # Install .apk(s) if any of them have changed. def do_install(): install_timer.Start() if split_globs: splits = [] for split_glob in split_globs: splits.extend((f for f in glob.glob(split_glob))) device.InstallSplitApk(apk, splits, reinstall=True, allow_cached_props=True, permissions=()) else: device.Install(apk, reinstall=True, permissions=()) install_timer.Stop(log=False) # Push .so and .dex files to the device (if they have changed). def do_push_files(): if lib_dir: push_native_timer.Start() device_lib_dir = posixpath.join(device_incremental_dir, 'lib') device.PushChangedFiles([(lib_dir, device_lib_dir)], delete_device_stale=True) push_native_timer.Stop(log=False) if 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(dex_files) for src_path, dest_name in zip(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) def check_selinux(): # Samsung started using SELinux before Marshmallow. There may be even more # cases where this is required... has_selinux = (device.build_version_sdk >= version_codes.MARSHMALLOW or device.GetProp('selinux.policy_version')) if has_selinux and apk.HasIsolatedProcesses(): raise Exception( 'Cannot use incremental installs on versions of Android ' 'where isoloated processes cannot access the filesystem ' '(this includes Android M+, and Samsung L+) without ' 'first disabling isoloated processes.\n' 'To do so, use GN arg:\n' ' disable_incremental_isolated_processes=true') cache_path = '%s/files-cache.json' % device_incremental_dir def restore_cache(): if not enable_device_cache: logging.info('Ignoring device cache') return # Delete the cached file so that any exceptions cause the next attempt # to re-compute md5s. cmd = 'P=%s;cat $P 2>/dev/null && rm $P' % cache_path lines = device.RunShellCommand(cmd, check_return=False, large_output=True) if lines: device.LoadCacheData(lines[0]) else: logging.info('Device cache not found: %s', cache_path) def save_cache(): cache_data = device.DumpCacheData() device.WriteFile(cache_path, cache_data) # 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) # 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. setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache, check_selinux) _Execute(use_concurrency, do_install, do_push_files) finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache) logging.info( 'Took %s seconds (setup=%s, install=%s, libs=%s, dex=%s, finalize=%s)', main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(), push_native_timer.GetDelta(), push_dex_timer.GetDelta(), finalize_timer.GetDelta())
def Install(device, install_json, apk=None, enable_device_cache=False, use_concurrency=True, permissions=()): """Installs the given incremental apk and all required supporting files. Args: device: A DeviceUtils instance (to install to). install_json: Path to .json file or already parsed .json object. apk: An existing ApkHelper instance for the apk (optional). enable_device_cache: Whether to enable on-device caching of checksums. use_concurrency: Whether to speed things up using multiple threads. permissions: A list of the permissions to grant, or None to grant all non-blacklisted permissions in the manifest. """ if isinstance(install_json, basestring): with open(install_json) as f: install_dict = json.load(f) else: install_dict = install_json if install_dict.get('dont_even_try'): raise Exception(install_dict['dont_even_try']) main_timer = time_profile.TimeProfile() install_timer = time_profile.TimeProfile() push_native_timer = time_profile.TimeProfile() push_dex_timer = time_profile.TimeProfile() def fix_path(p): return os.path.normpath(os.path.join(constants.GetOutDirectory(), p)) if not apk: apk = apk_helper.ToHelper(fix_path(install_dict['apk_path'])) split_globs = [fix_path(p) for p in install_dict['split_globs']] native_libs = [fix_path(p) for p in install_dict['native_libs']] dex_files = [fix_path(p) for p in install_dict['dex_files']] show_proguard_warning = install_dict.get('show_proguard_warning') apk_package = apk.GetPackageName() device_incremental_dir = _GetDeviceIncrementalDir(apk_package) # Install .apk(s) if any of them have changed. def do_install(): install_timer.Start() if split_globs: splits = [] for split_glob in split_globs: splits.extend((f for f in glob.glob(split_glob))) device.InstallSplitApk( apk, splits, allow_downgrade=True, reinstall=True, allow_cached_props=True, permissions=permissions) else: device.Install( apk, allow_downgrade=True, reinstall=True, permissions=permissions) install_timer.Stop(log=False) # Push .so and .dex files to the device (if they have changed). def do_push_files(): push_native_timer.Start() if native_libs: with build_utils.TempDir() as temp_dir: device_lib_dir = posixpath.join(device_incremental_dir, 'lib') for path in native_libs: # Note: Can't use symlinks as they don't work when # "adb push parent_dir" is used (like we do here). shutil.copy(path, os.path.join(temp_dir, os.path.basename(path))) device.PushChangedFiles([(temp_dir, device_lib_dir)], delete_device_stale=True) push_native_timer.Stop(log=False) push_dex_timer.Start() if dex_files: # 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(dex_files) for src_path, dest_name in zip(dex_files, transformed_names): # Binary targets with no extra classes create .dex.jar without a # classes.dex (which Android chokes on). if _HasClasses(src_path): shutil.copy(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) def check_selinux(): # Marshmallow has no filesystem access whatsoever. It might be possible to # get things working on Lollipop, but attempts so far have failed. # http://crbug.com/558818 has_selinux = device.build_version_sdk >= version_codes.LOLLIPOP if has_selinux and apk.HasIsolatedProcesses(): raise Exception('Cannot use incremental installs on Android L+ without ' 'first disabling isolated processes.\n' 'To do so, use GN arg:\n' ' disable_incremental_isolated_processes=true') target_sdk_version = int(apk.GetTargetSdkVersion()) # Beta Q builds apply whitelist to targetSdk=28 as well. if target_sdk_version >= 28 and device.build_version_sdk >= 29: apis_allowed = ''.join( device.RunShellCommand( ['settings', 'get', 'global', 'hidden_api_policy'], check_return=True)) if apis_allowed.strip() not in '01': msg = """\ Cannot use incremental installs on Android Q+ without first enabling access to non-SDK interfaces (https://developer.android.com/preview/non-sdk-q). To enable access: adb -s {0} shell settings put global hidden_api_policy 0 To restore back to default: adb -s {0} shell settings delete global hidden_api_policy""" raise Exception(msg.format(device.serial)) cache_path = _DeviceCachePath(device) def restore_cache(): if not enable_device_cache: return if os.path.exists(cache_path): logging.info('Using device cache: %s', cache_path) with open(cache_path) as f: device.LoadCacheData(f.read()) # Delete the cached file so that any exceptions cause it to be cleared. os.unlink(cache_path) else: logging.info('No device cache present: %s', cache_path) def save_cache(): if not enable_device_cache: return with open(cache_path, 'w') as f: f.write(device.DumpCacheData()) logging.info('Wrote device cache: %s', cache_path) # 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, shell=True, 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, shell=True) # 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. setup_timer = _Execute( use_concurrency, create_lock_files, restore_cache, check_selinux) _Execute(use_concurrency, do_install, do_push_files) finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache) logging.info( 'Install of %s took %s seconds ' '(setup=%s, install=%s, libs=%s, dex=%s, finalize=%s)', os.path.basename(apk.path), main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(), push_native_timer.GetDelta(), push_dex_timer.GetDelta(), finalize_timer.GetDelta()) if show_proguard_warning: logging.warning('Target had proguard enabled, but incremental install uses ' 'non-proguarded .dex files. Performance characteristics ' 'may differ.')
def main(): parser = argparse.ArgumentParser() parser.add_argument('apk_path', help='The path to the APK to install.') parser.add_argument('--split', action='append', dest='splits', help='A glob matching the apk splits. ' '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', action='store_true', default=False, help='Remove the app and all side-loaded files.') parser.add_argument('--output-directory', help='Path to the root build directory.') parser.add_argument('--no-threading', action='store_true', default=False, help='Do not install and push concurrently') parser.add_argument('-v', '--verbose', dest='verbose_count', default=0, action='count', help='Verbose level (multiple times for more)') args = parser.parse_args() 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. device = device_utils.DeviceUtils(args.device, default_retries=0) else: devices = device_utils.DeviceUtils.HealthyDevices(default_retries=0) if not devices: raise device_errors.NoDevicesError() elif len(devices) == 1: device = devices[0] else: all_devices = device_utils.DeviceUtils.parallel(devices) msg = ('More than one device available.\n' 'Use --device=SERIAL to select a device.\n' 'Available devices:\n') descriptions = all_devices.pMap(lambda d: d.build_description).pGet(None) for d, desc in zip(devices, descriptions): msg += ' %s (%s)\n' % (d, desc) raise Exception(msg) 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: device.Uninstall(apk_package) 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: splits.extend((f for f in glob.glob(split_glob))) device.InstallSplitApk(args.apk_path, splits, reinstall=True, allow_cached_props=True, permissions=()) else: device.Install(args.apk_path, reinstall=True, permissions=()) install_timer.Stop(log=False) # 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) 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_files() else: 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())