1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3 2*8975f5c5SAndroid Build Coastguard Worker# 3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2015 The Chromium Authors 4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 6*8975f5c5SAndroid Build Coastguard Worker 7*8975f5c5SAndroid Build Coastguard Worker"""Install *_incremental.apk targets as well as their dependent files.""" 8*8975f5c5SAndroid Build Coastguard Worker 9*8975f5c5SAndroid Build Coastguard Workerimport argparse 10*8975f5c5SAndroid Build Coastguard Workerimport collections 11*8975f5c5SAndroid Build Coastguard Workerimport functools 12*8975f5c5SAndroid Build Coastguard Workerimport glob 13*8975f5c5SAndroid Build Coastguard Workerimport hashlib 14*8975f5c5SAndroid Build Coastguard Workerimport json 15*8975f5c5SAndroid Build Coastguard Workerimport logging 16*8975f5c5SAndroid Build Coastguard Workerimport os 17*8975f5c5SAndroid Build Coastguard Workerimport posixpath 18*8975f5c5SAndroid Build Coastguard Workerimport shutil 19*8975f5c5SAndroid Build Coastguard Workerimport sys 20*8975f5c5SAndroid Build Coastguard Worker 21*8975f5c5SAndroid Build Coastguard Workersys.path.append( 22*8975f5c5SAndroid Build Coastguard Worker os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) 23*8975f5c5SAndroid Build Coastguard Workerimport devil_chromium 24*8975f5c5SAndroid Build Coastguard Workerfrom devil.android import apk_helper 25*8975f5c5SAndroid Build Coastguard Workerfrom devil.android import device_utils 26*8975f5c5SAndroid Build Coastguard Workerfrom devil.utils import reraiser_thread 27*8975f5c5SAndroid Build Coastguard Workerfrom devil.utils import run_tests_helper 28*8975f5c5SAndroid Build Coastguard Workerfrom pylib import constants 29*8975f5c5SAndroid Build Coastguard Workerfrom pylib.utils import time_profile 30*8975f5c5SAndroid Build Coastguard Worker 31*8975f5c5SAndroid Build Coastguard Workerprev_sys_path = list(sys.path) 32*8975f5c5SAndroid Build Coastguard Workersys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'gyp')) 33*8975f5c5SAndroid Build Coastguard Workerimport dex 34*8975f5c5SAndroid Build Coastguard Workerfrom util import build_utils 35*8975f5c5SAndroid Build Coastguard Workersys.path = prev_sys_path 36*8975f5c5SAndroid Build Coastguard Worker 37*8975f5c5SAndroid Build Coastguard Worker 38*8975f5c5SAndroid Build Coastguard Worker_R8_PATH = os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'r8', 39*8975f5c5SAndroid Build Coastguard Worker 'cipd', 'lib', 'r8.jar') 40*8975f5c5SAndroid Build Coastguard Worker_SHARD_JSON_FILENAME = 'shards.json' 41*8975f5c5SAndroid Build Coastguard Worker 42*8975f5c5SAndroid Build Coastguard Worker 43*8975f5c5SAndroid Build Coastguard Workerdef _DeviceCachePath(device): 44*8975f5c5SAndroid Build Coastguard Worker file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial() 45*8975f5c5SAndroid Build Coastguard Worker return os.path.join(constants.GetOutDirectory(), file_name) 46*8975f5c5SAndroid Build Coastguard Worker 47*8975f5c5SAndroid Build Coastguard Worker 48*8975f5c5SAndroid Build Coastguard Workerdef _Execute(concurrently, *funcs): 49*8975f5c5SAndroid Build Coastguard Worker """Calls all functions in |funcs| concurrently or in sequence.""" 50*8975f5c5SAndroid Build Coastguard Worker timer = time_profile.TimeProfile() 51*8975f5c5SAndroid Build Coastguard Worker if concurrently: 52*8975f5c5SAndroid Build Coastguard Worker reraiser_thread.RunAsync(funcs) 53*8975f5c5SAndroid Build Coastguard Worker else: 54*8975f5c5SAndroid Build Coastguard Worker for f in funcs: 55*8975f5c5SAndroid Build Coastguard Worker f() 56*8975f5c5SAndroid Build Coastguard Worker timer.Stop(log=False) 57*8975f5c5SAndroid Build Coastguard Worker return timer 58*8975f5c5SAndroid Build Coastguard Worker 59*8975f5c5SAndroid Build Coastguard Worker 60*8975f5c5SAndroid Build Coastguard Workerdef _GetDeviceIncrementalDir(package): 61*8975f5c5SAndroid Build Coastguard Worker """Returns the device path to put incremental files for the given package.""" 62*8975f5c5SAndroid Build Coastguard Worker return '/data/local/tmp/incremental-app-%s' % package 63*8975f5c5SAndroid Build Coastguard Worker 64*8975f5c5SAndroid Build Coastguard Worker 65*8975f5c5SAndroid Build Coastguard Workerdef _IsStale(src_paths, old_src_paths, dest_path): 66*8975f5c5SAndroid Build Coastguard Worker """Returns if |dest| is older than any of |src_paths|, or missing.""" 67*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(dest_path): 68*8975f5c5SAndroid Build Coastguard Worker return True 69*8975f5c5SAndroid Build Coastguard Worker # Always mark as stale if any paths were added or removed. 70*8975f5c5SAndroid Build Coastguard Worker if set(src_paths) != set(old_src_paths): 71*8975f5c5SAndroid Build Coastguard Worker return True 72*8975f5c5SAndroid Build Coastguard Worker dest_time = os.path.getmtime(dest_path) 73*8975f5c5SAndroid Build Coastguard Worker for path in src_paths: 74*8975f5c5SAndroid Build Coastguard Worker if os.path.getmtime(path) > dest_time: 75*8975f5c5SAndroid Build Coastguard Worker return True 76*8975f5c5SAndroid Build Coastguard Worker return False 77*8975f5c5SAndroid Build Coastguard Worker 78*8975f5c5SAndroid Build Coastguard Worker 79*8975f5c5SAndroid Build Coastguard Workerdef _LoadPrevShards(dex_staging_dir): 80*8975f5c5SAndroid Build Coastguard Worker shards_json_path = os.path.join(dex_staging_dir, _SHARD_JSON_FILENAME) 81*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(shards_json_path): 82*8975f5c5SAndroid Build Coastguard Worker return {} 83*8975f5c5SAndroid Build Coastguard Worker with open(shards_json_path) as f: 84*8975f5c5SAndroid Build Coastguard Worker return json.load(f) 85*8975f5c5SAndroid Build Coastguard Worker 86*8975f5c5SAndroid Build Coastguard Worker 87*8975f5c5SAndroid Build Coastguard Workerdef _SaveNewShards(shards, dex_staging_dir): 88*8975f5c5SAndroid Build Coastguard Worker shards_json_path = os.path.join(dex_staging_dir, _SHARD_JSON_FILENAME) 89*8975f5c5SAndroid Build Coastguard Worker with open(shards_json_path, 'w') as f: 90*8975f5c5SAndroid Build Coastguard Worker json.dump(shards, f) 91*8975f5c5SAndroid Build Coastguard Worker 92*8975f5c5SAndroid Build Coastguard Worker 93*8975f5c5SAndroid Build Coastguard Workerdef _AllocateDexShards(dex_files): 94*8975f5c5SAndroid Build Coastguard Worker """Divides input dex files into buckets.""" 95*8975f5c5SAndroid Build Coastguard Worker # Goals: 96*8975f5c5SAndroid Build Coastguard Worker # * Make shards small enough that they are fast to merge. 97*8975f5c5SAndroid Build Coastguard Worker # * Minimize the number of shards so they load quickly on device. 98*8975f5c5SAndroid Build Coastguard Worker # * Partition files into shards such that a change in one file results in only 99*8975f5c5SAndroid Build Coastguard Worker # one shard having to be re-created. 100*8975f5c5SAndroid Build Coastguard Worker shards = collections.defaultdict(list) 101*8975f5c5SAndroid Build Coastguard Worker # As of Oct 2019, 10 shards results in a min/max size of 582K/2.6M. 102*8975f5c5SAndroid Build Coastguard Worker NUM_CORE_SHARDS = 10 103*8975f5c5SAndroid Build Coastguard Worker # As of Oct 2019, 17 dex files are larger than 1M. 104*8975f5c5SAndroid Build Coastguard Worker SHARD_THRESHOLD = 2**20 105*8975f5c5SAndroid Build Coastguard Worker for src_path in dex_files: 106*8975f5c5SAndroid Build Coastguard Worker if os.path.getsize(src_path) >= SHARD_THRESHOLD: 107*8975f5c5SAndroid Build Coastguard Worker # Use the path as the name rather than an incrementing number to ensure 108*8975f5c5SAndroid Build Coastguard Worker # that it shards to the same name every time. 109*8975f5c5SAndroid Build Coastguard Worker name = os.path.relpath(src_path, constants.GetOutDirectory()).replace( 110*8975f5c5SAndroid Build Coastguard Worker os.sep, '.') 111*8975f5c5SAndroid Build Coastguard Worker shards[name].append(src_path) 112*8975f5c5SAndroid Build Coastguard Worker else: 113*8975f5c5SAndroid Build Coastguard Worker # The stdlib hash(string) function is salted differently across python3 114*8975f5c5SAndroid Build Coastguard Worker # invocations. Thus we use md5 instead to consistently shard the same 115*8975f5c5SAndroid Build Coastguard Worker # file to the same shard across runs. 116*8975f5c5SAndroid Build Coastguard Worker hex_hash = hashlib.md5(src_path.encode('utf-8')).hexdigest() 117*8975f5c5SAndroid Build Coastguard Worker name = 'shard{}.dex.jar'.format(int(hex_hash, 16) % NUM_CORE_SHARDS) 118*8975f5c5SAndroid Build Coastguard Worker shards[name].append(src_path) 119*8975f5c5SAndroid Build Coastguard Worker logging.info('Sharding %d dex files into %d buckets', len(dex_files), 120*8975f5c5SAndroid Build Coastguard Worker len(shards)) 121*8975f5c5SAndroid Build Coastguard Worker return shards 122*8975f5c5SAndroid Build Coastguard Worker 123*8975f5c5SAndroid Build Coastguard Worker 124*8975f5c5SAndroid Build Coastguard Workerdef _CreateDexFiles(shards, prev_shards, dex_staging_dir, min_api, 125*8975f5c5SAndroid Build Coastguard Worker use_concurrency): 126*8975f5c5SAndroid Build Coastguard Worker """Creates dex files within |dex_staging_dir| defined by |shards|.""" 127*8975f5c5SAndroid Build Coastguard Worker tasks = [] 128*8975f5c5SAndroid Build Coastguard Worker for name, src_paths in shards.items(): 129*8975f5c5SAndroid Build Coastguard Worker dest_path = os.path.join(dex_staging_dir, name) 130*8975f5c5SAndroid Build Coastguard Worker if _IsStale(src_paths=src_paths, 131*8975f5c5SAndroid Build Coastguard Worker old_src_paths=prev_shards.get(name, []), 132*8975f5c5SAndroid Build Coastguard Worker dest_path=dest_path): 133*8975f5c5SAndroid Build Coastguard Worker tasks.append( 134*8975f5c5SAndroid Build Coastguard Worker functools.partial(dex.MergeDexForIncrementalInstall, _R8_PATH, 135*8975f5c5SAndroid Build Coastguard Worker src_paths, dest_path, min_api)) 136*8975f5c5SAndroid Build Coastguard Worker 137*8975f5c5SAndroid Build Coastguard Worker # TODO(agrieve): It would be more performant to write a custom d8.jar 138*8975f5c5SAndroid Build Coastguard Worker # wrapper in java that would process these in bulk, rather than spinning 139*8975f5c5SAndroid Build Coastguard Worker # up a new process for each one. 140*8975f5c5SAndroid Build Coastguard Worker _Execute(use_concurrency, *tasks) 141*8975f5c5SAndroid Build Coastguard Worker 142*8975f5c5SAndroid Build Coastguard Worker # Remove any stale shards. 143*8975f5c5SAndroid Build Coastguard Worker for name in os.listdir(dex_staging_dir): 144*8975f5c5SAndroid Build Coastguard Worker if name not in shards: 145*8975f5c5SAndroid Build Coastguard Worker os.unlink(os.path.join(dex_staging_dir, name)) 146*8975f5c5SAndroid Build Coastguard Worker 147*8975f5c5SAndroid Build Coastguard Worker 148*8975f5c5SAndroid Build Coastguard Workerdef Uninstall(device, package, enable_device_cache=False): 149*8975f5c5SAndroid Build Coastguard Worker """Uninstalls and removes all incremental files for the given package.""" 150*8975f5c5SAndroid Build Coastguard Worker main_timer = time_profile.TimeProfile() 151*8975f5c5SAndroid Build Coastguard Worker device.Uninstall(package) 152*8975f5c5SAndroid Build Coastguard Worker if enable_device_cache: 153*8975f5c5SAndroid Build Coastguard Worker # Uninstall is rare, so just wipe the cache in this case. 154*8975f5c5SAndroid Build Coastguard Worker cache_path = _DeviceCachePath(device) 155*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(cache_path): 156*8975f5c5SAndroid Build Coastguard Worker os.unlink(cache_path) 157*8975f5c5SAndroid Build Coastguard Worker device.RunShellCommand(['rm', '-rf', _GetDeviceIncrementalDir(package)], 158*8975f5c5SAndroid Build Coastguard Worker check_return=True) 159*8975f5c5SAndroid Build Coastguard Worker logging.info('Uninstall took %s seconds.', main_timer.GetDelta()) 160*8975f5c5SAndroid Build Coastguard Worker 161*8975f5c5SAndroid Build Coastguard Worker 162*8975f5c5SAndroid Build Coastguard Workerdef Install(device, install_json, apk=None, enable_device_cache=False, 163*8975f5c5SAndroid Build Coastguard Worker use_concurrency=True, permissions=()): 164*8975f5c5SAndroid Build Coastguard Worker """Installs the given incremental apk and all required supporting files. 165*8975f5c5SAndroid Build Coastguard Worker 166*8975f5c5SAndroid Build Coastguard Worker Args: 167*8975f5c5SAndroid Build Coastguard Worker device: A DeviceUtils instance (to install to). 168*8975f5c5SAndroid Build Coastguard Worker install_json: Path to .json file or already parsed .json object. 169*8975f5c5SAndroid Build Coastguard Worker apk: An existing ApkHelper instance for the apk (optional). 170*8975f5c5SAndroid Build Coastguard Worker enable_device_cache: Whether to enable on-device caching of checksums. 171*8975f5c5SAndroid Build Coastguard Worker use_concurrency: Whether to speed things up using multiple threads. 172*8975f5c5SAndroid Build Coastguard Worker permissions: A list of the permissions to grant, or None to grant all 173*8975f5c5SAndroid Build Coastguard Worker non-denylisted permissions in the manifest. 174*8975f5c5SAndroid Build Coastguard Worker """ 175*8975f5c5SAndroid Build Coastguard Worker if isinstance(install_json, str): 176*8975f5c5SAndroid Build Coastguard Worker with open(install_json) as f: 177*8975f5c5SAndroid Build Coastguard Worker install_dict = json.load(f) 178*8975f5c5SAndroid Build Coastguard Worker else: 179*8975f5c5SAndroid Build Coastguard Worker install_dict = install_json 180*8975f5c5SAndroid Build Coastguard Worker 181*8975f5c5SAndroid Build Coastguard Worker main_timer = time_profile.TimeProfile() 182*8975f5c5SAndroid Build Coastguard Worker install_timer = time_profile.TimeProfile() 183*8975f5c5SAndroid Build Coastguard Worker push_native_timer = time_profile.TimeProfile() 184*8975f5c5SAndroid Build Coastguard Worker merge_dex_timer = time_profile.TimeProfile() 185*8975f5c5SAndroid Build Coastguard Worker push_dex_timer = time_profile.TimeProfile() 186*8975f5c5SAndroid Build Coastguard Worker 187*8975f5c5SAndroid Build Coastguard Worker def fix_path(p): 188*8975f5c5SAndroid Build Coastguard Worker return os.path.normpath(os.path.join(constants.GetOutDirectory(), p)) 189*8975f5c5SAndroid Build Coastguard Worker 190*8975f5c5SAndroid Build Coastguard Worker if not apk: 191*8975f5c5SAndroid Build Coastguard Worker apk = apk_helper.ToHelper(fix_path(install_dict['apk_path'])) 192*8975f5c5SAndroid Build Coastguard Worker split_globs = [fix_path(p) for p in install_dict['split_globs']] 193*8975f5c5SAndroid Build Coastguard Worker native_libs = [fix_path(p) for p in install_dict['native_libs']] 194*8975f5c5SAndroid Build Coastguard Worker dex_files = [fix_path(p) for p in install_dict['dex_files']] 195*8975f5c5SAndroid Build Coastguard Worker show_proguard_warning = install_dict.get('show_proguard_warning') 196*8975f5c5SAndroid Build Coastguard Worker 197*8975f5c5SAndroid Build Coastguard Worker apk_package = apk.GetPackageName() 198*8975f5c5SAndroid Build Coastguard Worker device_incremental_dir = _GetDeviceIncrementalDir(apk_package) 199*8975f5c5SAndroid Build Coastguard Worker dex_staging_dir = os.path.join(constants.GetOutDirectory(), 200*8975f5c5SAndroid Build Coastguard Worker 'incremental-install', 201*8975f5c5SAndroid Build Coastguard Worker install_dict['apk_path']) 202*8975f5c5SAndroid Build Coastguard Worker device_dex_dir = posixpath.join(device_incremental_dir, 'dex') 203*8975f5c5SAndroid Build Coastguard Worker 204*8975f5c5SAndroid Build Coastguard Worker # Install .apk(s) if any of them have changed. 205*8975f5c5SAndroid Build Coastguard Worker def do_install(): 206*8975f5c5SAndroid Build Coastguard Worker install_timer.Start() 207*8975f5c5SAndroid Build Coastguard Worker if split_globs: 208*8975f5c5SAndroid Build Coastguard Worker splits = [] 209*8975f5c5SAndroid Build Coastguard Worker for split_glob in split_globs: 210*8975f5c5SAndroid Build Coastguard Worker splits.extend((f for f in glob.glob(split_glob))) 211*8975f5c5SAndroid Build Coastguard Worker device.InstallSplitApk( 212*8975f5c5SAndroid Build Coastguard Worker apk, 213*8975f5c5SAndroid Build Coastguard Worker splits, 214*8975f5c5SAndroid Build Coastguard Worker allow_downgrade=True, 215*8975f5c5SAndroid Build Coastguard Worker reinstall=True, 216*8975f5c5SAndroid Build Coastguard Worker allow_cached_props=True, 217*8975f5c5SAndroid Build Coastguard Worker permissions=permissions) 218*8975f5c5SAndroid Build Coastguard Worker else: 219*8975f5c5SAndroid Build Coastguard Worker device.Install( 220*8975f5c5SAndroid Build Coastguard Worker apk, allow_downgrade=True, reinstall=True, permissions=permissions) 221*8975f5c5SAndroid Build Coastguard Worker install_timer.Stop(log=False) 222*8975f5c5SAndroid Build Coastguard Worker 223*8975f5c5SAndroid Build Coastguard Worker # Push .so and .dex files to the device (if they have changed). 224*8975f5c5SAndroid Build Coastguard Worker def do_push_files(): 225*8975f5c5SAndroid Build Coastguard Worker 226*8975f5c5SAndroid Build Coastguard Worker def do_push_native(): 227*8975f5c5SAndroid Build Coastguard Worker push_native_timer.Start() 228*8975f5c5SAndroid Build Coastguard Worker if native_libs: 229*8975f5c5SAndroid Build Coastguard Worker with build_utils.TempDir() as temp_dir: 230*8975f5c5SAndroid Build Coastguard Worker device_lib_dir = posixpath.join(device_incremental_dir, 'lib') 231*8975f5c5SAndroid Build Coastguard Worker for path in native_libs: 232*8975f5c5SAndroid Build Coastguard Worker # Note: Can't use symlinks as they don't work when 233*8975f5c5SAndroid Build Coastguard Worker # "adb push parent_dir" is used (like we do here). 234*8975f5c5SAndroid Build Coastguard Worker shutil.copy(path, os.path.join(temp_dir, os.path.basename(path))) 235*8975f5c5SAndroid Build Coastguard Worker device.PushChangedFiles([(temp_dir, device_lib_dir)], 236*8975f5c5SAndroid Build Coastguard Worker delete_device_stale=True) 237*8975f5c5SAndroid Build Coastguard Worker push_native_timer.Stop(log=False) 238*8975f5c5SAndroid Build Coastguard Worker 239*8975f5c5SAndroid Build Coastguard Worker def do_merge_dex(): 240*8975f5c5SAndroid Build Coastguard Worker merge_dex_timer.Start() 241*8975f5c5SAndroid Build Coastguard Worker prev_shards = _LoadPrevShards(dex_staging_dir) 242*8975f5c5SAndroid Build Coastguard Worker shards = _AllocateDexShards(dex_files) 243*8975f5c5SAndroid Build Coastguard Worker build_utils.MakeDirectory(dex_staging_dir) 244*8975f5c5SAndroid Build Coastguard Worker _CreateDexFiles(shards, prev_shards, dex_staging_dir, 245*8975f5c5SAndroid Build Coastguard Worker apk.GetMinSdkVersion(), use_concurrency) 246*8975f5c5SAndroid Build Coastguard Worker # New shard information must be saved after _CreateDexFiles since 247*8975f5c5SAndroid Build Coastguard Worker # _CreateDexFiles removes all non-dex files from the staging dir. 248*8975f5c5SAndroid Build Coastguard Worker _SaveNewShards(shards, dex_staging_dir) 249*8975f5c5SAndroid Build Coastguard Worker merge_dex_timer.Stop(log=False) 250*8975f5c5SAndroid Build Coastguard Worker 251*8975f5c5SAndroid Build Coastguard Worker def do_push_dex(): 252*8975f5c5SAndroid Build Coastguard Worker push_dex_timer.Start() 253*8975f5c5SAndroid Build Coastguard Worker device.PushChangedFiles([(dex_staging_dir, device_dex_dir)], 254*8975f5c5SAndroid Build Coastguard Worker delete_device_stale=True) 255*8975f5c5SAndroid Build Coastguard Worker push_dex_timer.Stop(log=False) 256*8975f5c5SAndroid Build Coastguard Worker 257*8975f5c5SAndroid Build Coastguard Worker _Execute(use_concurrency, do_push_native, do_merge_dex) 258*8975f5c5SAndroid Build Coastguard Worker do_push_dex() 259*8975f5c5SAndroid Build Coastguard Worker 260*8975f5c5SAndroid Build Coastguard Worker cache_path = _DeviceCachePath(device) 261*8975f5c5SAndroid Build Coastguard Worker def restore_cache(): 262*8975f5c5SAndroid Build Coastguard Worker if not enable_device_cache: 263*8975f5c5SAndroid Build Coastguard Worker return 264*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(cache_path): 265*8975f5c5SAndroid Build Coastguard Worker logging.info('Using device cache: %s', cache_path) 266*8975f5c5SAndroid Build Coastguard Worker with open(cache_path) as f: 267*8975f5c5SAndroid Build Coastguard Worker device.LoadCacheData(f.read()) 268*8975f5c5SAndroid Build Coastguard Worker # Delete the cached file so that any exceptions cause it to be cleared. 269*8975f5c5SAndroid Build Coastguard Worker os.unlink(cache_path) 270*8975f5c5SAndroid Build Coastguard Worker else: 271*8975f5c5SAndroid Build Coastguard Worker logging.info('No device cache present: %s', cache_path) 272*8975f5c5SAndroid Build Coastguard Worker 273*8975f5c5SAndroid Build Coastguard Worker def save_cache(): 274*8975f5c5SAndroid Build Coastguard Worker if not enable_device_cache: 275*8975f5c5SAndroid Build Coastguard Worker return 276*8975f5c5SAndroid Build Coastguard Worker with open(cache_path, 'w') as f: 277*8975f5c5SAndroid Build Coastguard Worker f.write(device.DumpCacheData()) 278*8975f5c5SAndroid Build Coastguard Worker logging.info('Wrote device cache: %s', cache_path) 279*8975f5c5SAndroid Build Coastguard Worker 280*8975f5c5SAndroid Build Coastguard Worker # Create 2 lock files: 281*8975f5c5SAndroid Build Coastguard Worker # * install.lock tells the app to pause on start-up (until we release it). 282*8975f5c5SAndroid Build Coastguard Worker # * firstrun.lock is used by the app to pause all secondary processes until 283*8975f5c5SAndroid Build Coastguard Worker # the primary process finishes loading the .dex / .so files. 284*8975f5c5SAndroid Build Coastguard Worker def create_lock_files(): 285*8975f5c5SAndroid Build Coastguard Worker # Creates or zeros out lock files. 286*8975f5c5SAndroid Build Coastguard Worker cmd = ('D="%s";' 287*8975f5c5SAndroid Build Coastguard Worker 'mkdir -p $D &&' 288*8975f5c5SAndroid Build Coastguard Worker 'echo -n >$D/install.lock 2>$D/firstrun.lock') 289*8975f5c5SAndroid Build Coastguard Worker device.RunShellCommand( 290*8975f5c5SAndroid Build Coastguard Worker cmd % device_incremental_dir, shell=True, check_return=True) 291*8975f5c5SAndroid Build Coastguard Worker 292*8975f5c5SAndroid Build Coastguard Worker # The firstrun.lock is released by the app itself. 293*8975f5c5SAndroid Build Coastguard Worker def release_installer_lock(): 294*8975f5c5SAndroid Build Coastguard Worker device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir, 295*8975f5c5SAndroid Build Coastguard Worker check_return=True, shell=True) 296*8975f5c5SAndroid Build Coastguard Worker 297*8975f5c5SAndroid Build Coastguard Worker # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't 298*8975f5c5SAndroid Build Coastguard Worker # been designed for multi-threading. Enabling only because this is a 299*8975f5c5SAndroid Build Coastguard Worker # developer-only tool. 300*8975f5c5SAndroid Build Coastguard Worker setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache) 301*8975f5c5SAndroid Build Coastguard Worker 302*8975f5c5SAndroid Build Coastguard Worker _Execute(use_concurrency, do_install, do_push_files) 303*8975f5c5SAndroid Build Coastguard Worker 304*8975f5c5SAndroid Build Coastguard Worker finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache) 305*8975f5c5SAndroid Build Coastguard Worker 306*8975f5c5SAndroid Build Coastguard Worker logging.info( 307*8975f5c5SAndroid Build Coastguard Worker 'Install of %s took %s seconds (setup=%s, install=%s, lib_push=%s, ' 308*8975f5c5SAndroid Build Coastguard Worker 'dex_merge=%s dex_push=%s, finalize=%s)', os.path.basename(apk.path), 309*8975f5c5SAndroid Build Coastguard Worker main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(), 310*8975f5c5SAndroid Build Coastguard Worker push_native_timer.GetDelta(), merge_dex_timer.GetDelta(), 311*8975f5c5SAndroid Build Coastguard Worker push_dex_timer.GetDelta(), finalize_timer.GetDelta()) 312*8975f5c5SAndroid Build Coastguard Worker if show_proguard_warning: 313*8975f5c5SAndroid Build Coastguard Worker logging.warning('Target had proguard enabled, but incremental install uses ' 314*8975f5c5SAndroid Build Coastguard Worker 'non-proguarded .dex files. Performance characteristics ' 315*8975f5c5SAndroid Build Coastguard Worker 'may differ.') 316*8975f5c5SAndroid Build Coastguard Worker 317*8975f5c5SAndroid Build Coastguard Worker 318*8975f5c5SAndroid Build Coastguard Workerdef main(): 319*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 320*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('json_path', 321*8975f5c5SAndroid Build Coastguard Worker help='The path to the generated incremental apk .json.') 322*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('-d', '--device', dest='device', 323*8975f5c5SAndroid Build Coastguard Worker help='Target device for apk to install on.') 324*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--uninstall', 325*8975f5c5SAndroid Build Coastguard Worker action='store_true', 326*8975f5c5SAndroid Build Coastguard Worker default=False, 327*8975f5c5SAndroid Build Coastguard Worker help='Remove the app and all side-loaded files.') 328*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--output-directory', 329*8975f5c5SAndroid Build Coastguard Worker help='Path to the root build directory.') 330*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--no-threading', 331*8975f5c5SAndroid Build Coastguard Worker action='store_false', 332*8975f5c5SAndroid Build Coastguard Worker default=True, 333*8975f5c5SAndroid Build Coastguard Worker dest='threading', 334*8975f5c5SAndroid Build Coastguard Worker help='Do not install and push concurrently') 335*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--no-cache', 336*8975f5c5SAndroid Build Coastguard Worker action='store_false', 337*8975f5c5SAndroid Build Coastguard Worker default=True, 338*8975f5c5SAndroid Build Coastguard Worker dest='cache', 339*8975f5c5SAndroid Build Coastguard Worker help='Do not use cached information about what files are ' 340*8975f5c5SAndroid Build Coastguard Worker 'currently on the target device.') 341*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('-v', 342*8975f5c5SAndroid Build Coastguard Worker '--verbose', 343*8975f5c5SAndroid Build Coastguard Worker dest='verbose_count', 344*8975f5c5SAndroid Build Coastguard Worker default=0, 345*8975f5c5SAndroid Build Coastguard Worker action='count', 346*8975f5c5SAndroid Build Coastguard Worker help='Verbose level (multiple times for more)') 347*8975f5c5SAndroid Build Coastguard Worker 348*8975f5c5SAndroid Build Coastguard Worker args = parser.parse_args() 349*8975f5c5SAndroid Build Coastguard Worker 350*8975f5c5SAndroid Build Coastguard Worker run_tests_helper.SetLogLevel(args.verbose_count) 351*8975f5c5SAndroid Build Coastguard Worker if args.output_directory: 352*8975f5c5SAndroid Build Coastguard Worker constants.SetOutputDirectory(args.output_directory) 353*8975f5c5SAndroid Build Coastguard Worker 354*8975f5c5SAndroid Build Coastguard Worker devil_chromium.Initialize(output_directory=constants.GetOutDirectory()) 355*8975f5c5SAndroid Build Coastguard Worker 356*8975f5c5SAndroid Build Coastguard Worker # Retries are annoying when commands fail for legitimate reasons. Might want 357*8975f5c5SAndroid Build Coastguard Worker # to enable them if this is ever used on bots though. 358*8975f5c5SAndroid Build Coastguard Worker device = device_utils.DeviceUtils.HealthyDevices( 359*8975f5c5SAndroid Build Coastguard Worker device_arg=args.device, 360*8975f5c5SAndroid Build Coastguard Worker default_retries=0, 361*8975f5c5SAndroid Build Coastguard Worker enable_device_files_cache=True)[0] 362*8975f5c5SAndroid Build Coastguard Worker 363*8975f5c5SAndroid Build Coastguard Worker if args.uninstall: 364*8975f5c5SAndroid Build Coastguard Worker with open(args.json_path) as f: 365*8975f5c5SAndroid Build Coastguard Worker install_dict = json.load(f) 366*8975f5c5SAndroid Build Coastguard Worker apk = apk_helper.ToHelper(install_dict['apk_path']) 367*8975f5c5SAndroid Build Coastguard Worker Uninstall(device, apk.GetPackageName(), enable_device_cache=args.cache) 368*8975f5c5SAndroid Build Coastguard Worker else: 369*8975f5c5SAndroid Build Coastguard Worker Install(device, args.json_path, enable_device_cache=args.cache, 370*8975f5c5SAndroid Build Coastguard Worker use_concurrency=args.threading) 371*8975f5c5SAndroid Build Coastguard Worker 372*8975f5c5SAndroid Build Coastguard Worker 373*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 374*8975f5c5SAndroid Build Coastguard Worker sys.exit(main()) 375