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