Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright 2015 The Chromium Authors. All rights reserved. | 3 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """Install *_incremental.apk targets as well as their dependent files.""" | 7 """Install *_incremental.apk targets as well as their dependent files.""" |
| 8 | 8 |
| 9 import argparse | 9 import argparse |
| 10 import glob | 10 import glob |
| 11 import logging | 11 import logging |
| 12 import os | 12 import os |
| 13 import posixpath | 13 import posixpath |
| 14 import shutil | 14 import shutil |
| 15 import sys | 15 import sys |
| 16 import zipfile | |
| 16 | 17 |
| 17 sys.path.append( | 18 sys.path.append( |
| 18 os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) | 19 os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) |
| 19 import devil_chromium | 20 import devil_chromium |
| 20 from devil.android import apk_helper | 21 from devil.android import apk_helper |
| 21 from devil.android import device_utils | 22 from devil.android import device_utils |
| 22 from devil.android import device_errors | 23 from devil.android import device_errors |
| 23 from devil.android.sdk import version_codes | 24 from devil.android.sdk import version_codes |
| 24 from devil.utils import reraiser_thread | 25 from devil.utils import reraiser_thread |
| 25 from pylib import constants | 26 from pylib import constants |
| 26 from pylib.utils import run_tests_helper | 27 from pylib.utils import run_tests_helper |
| 27 from pylib.utils import time_profile | 28 from pylib.utils import time_profile |
| 28 | 29 |
| 29 prev_sys_path = list(sys.path) | 30 prev_sys_path = list(sys.path) |
| 30 sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'gyp')) | 31 sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'gyp')) |
| 31 from util import build_utils | 32 from util import build_utils |
| 32 sys.path = prev_sys_path | 33 sys.path = prev_sys_path |
| 33 | 34 |
| 34 | 35 |
| 36 def _DeviceCachePath(device): | |
| 37 file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial() | |
| 38 return os.path.join(constants.GetOutDirectory(), file_name) | |
|
nyquist
2016/02/10 07:08:32
Does constants.GetOutDirectory magically change de
agrieve
2016/02/10 16:47:15
It is somewhat comically named. There's a setter f
jbudorick
2016/02/10 16:53:14
constants.GetOutDirectory() is one of my pet peeve
| |
| 39 | |
| 40 | |
| 35 def _TransformDexPaths(paths): | 41 def _TransformDexPaths(paths): |
| 36 """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"].""" | 42 """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"].""" |
| 37 if len(paths) == 1: | 43 if len(paths) == 1: |
| 38 return [os.path.basename(paths[0])] | 44 return [os.path.basename(paths[0])] |
| 39 | 45 |
| 40 prefix_len = len(os.path.commonprefix(paths)) | 46 prefix_len = len(os.path.commonprefix(paths)) |
| 41 return [p[prefix_len:].replace(os.sep, '.') for p in paths] | 47 return [p[prefix_len:].replace(os.sep, '.') for p in paths] |
| 42 | 48 |
| 43 | 49 |
| 44 def _Execute(concurrently, *funcs): | 50 def _Execute(concurrently, *funcs): |
| 45 """Calls all functions in |funcs| concurrently or in sequence.""" | 51 """Calls all functions in |funcs| concurrently or in sequence.""" |
| 46 timer = time_profile.TimeProfile() | 52 timer = time_profile.TimeProfile() |
| 47 if concurrently: | 53 if concurrently: |
| 48 reraiser_thread.RunAsync(funcs) | 54 reraiser_thread.RunAsync(funcs) |
| 49 else: | 55 else: |
| 50 for f in funcs: | 56 for f in funcs: |
| 51 f() | 57 f() |
| 52 timer.Stop(log=False) | 58 timer.Stop(log=False) |
| 53 return timer | 59 return timer |
| 54 | 60 |
| 55 | 61 |
| 56 def _GetDeviceIncrementalDir(package): | 62 def _GetDeviceIncrementalDir(package): |
| 57 """Returns the device path to put incremental files for the given package.""" | 63 """Returns the device path to put incremental files for the given package.""" |
| 58 return '/data/local/tmp/incremental-app-%s' % package | 64 return '/data/local/tmp/incremental-app-%s' % package |
| 59 | 65 |
| 60 | 66 |
| 61 def Uninstall(device, package): | 67 def _HasClasses(jar_path): |
| 68 """Returns whether the given jar contains classes.dex.""" | |
| 69 with zipfile.ZipFile(jar_path) as jar: | |
| 70 return 'classes.dex' in jar.namelist() | |
| 71 | |
| 72 | |
| 73 def Uninstall(device, package, enable_device_cache=False): | |
| 62 """Uninstalls and removes all incremental files for the given package.""" | 74 """Uninstalls and removes all incremental files for the given package.""" |
| 63 main_timer = time_profile.TimeProfile() | 75 main_timer = time_profile.TimeProfile() |
| 64 device.Uninstall(package) | 76 device.Uninstall(package) |
| 77 if enable_device_cache: | |
| 78 # Uninstall is rare, so just wipe the cache in this case. | |
| 79 cache_path = _DeviceCachePath(device) | |
| 80 if os.path.exists(cache_path): | |
| 81 os.unlink(cache_path) | |
| 65 device.RunShellCommand(['rm', '-rf', _GetDeviceIncrementalDir(package)], | 82 device.RunShellCommand(['rm', '-rf', _GetDeviceIncrementalDir(package)], |
| 66 check_return=True) | 83 check_return=True) |
| 67 logging.info('Uninstall took %s seconds.', main_timer.GetDelta()) | 84 logging.info('Uninstall took %s seconds.', main_timer.GetDelta()) |
| 68 | 85 |
| 69 | 86 |
| 70 def Install(device, apk, split_globs=None, native_libs=None, dex_files=None, | 87 def Install(device, apk, split_globs=None, native_libs=None, dex_files=None, |
| 71 enable_device_cache=True, use_concurrency=True, | 88 enable_device_cache=False, use_concurrency=True, |
| 72 show_proguard_warning=False): | 89 show_proguard_warning=False): |
| 73 """Installs the given incremental apk and all required supporting files. | 90 """Installs the given incremental apk and all required supporting files. |
| 74 | 91 |
| 75 Args: | 92 Args: |
| 76 device: A DeviceUtils instance. | 93 device: A DeviceUtils instance. |
| 77 apk: The path to the apk, or an ApkHelper instance. | 94 apk: The path to the apk, or an ApkHelper instance. |
| 78 split_globs: Glob patterns for any required apk splits (optional). | 95 split_globs: Glob patterns for any required apk splits (optional). |
| 79 native_libs: List of app's native libraries (optional). | 96 native_libs: List of app's native libraries (optional). |
| 80 dex_files: List of .dex.jar files that comprise the app's Dalvik code. | 97 dex_files: List of .dex.jar files that comprise the app's Dalvik code. |
| 81 enable_device_cache: Whether to enable on-device caching of checksums. | 98 enable_device_cache: Whether to enable on-device caching of checksums. |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 121 | 138 |
| 122 if dex_files: | 139 if dex_files: |
| 123 push_dex_timer.Start() | 140 push_dex_timer.Start() |
| 124 # Put all .dex files to be pushed into a temporary directory so that we | 141 # Put all .dex files to be pushed into a temporary directory so that we |
| 125 # can use delete_device_stale=True. | 142 # can use delete_device_stale=True. |
| 126 with build_utils.TempDir() as temp_dir: | 143 with build_utils.TempDir() as temp_dir: |
| 127 device_dex_dir = posixpath.join(device_incremental_dir, 'dex') | 144 device_dex_dir = posixpath.join(device_incremental_dir, 'dex') |
| 128 # Ensure no two files have the same name. | 145 # Ensure no two files have the same name. |
| 129 transformed_names = _TransformDexPaths(dex_files) | 146 transformed_names = _TransformDexPaths(dex_files) |
| 130 for src_path, dest_name in zip(dex_files, transformed_names): | 147 for src_path, dest_name in zip(dex_files, transformed_names): |
| 131 shutil.copy(src_path, os.path.join(temp_dir, dest_name)) | 148 # Binary targets with no extra classes create .dex.jar without a |
| 149 # classes.dex (which Android chokes on). | |
| 150 if _HasClasses(src_path): | |
| 151 shutil.copy(src_path, os.path.join(temp_dir, dest_name)) | |
| 132 device.PushChangedFiles([(temp_dir, device_dex_dir)], | 152 device.PushChangedFiles([(temp_dir, device_dex_dir)], |
| 133 delete_device_stale=True) | 153 delete_device_stale=True) |
| 134 push_dex_timer.Stop(log=False) | 154 push_dex_timer.Stop(log=False) |
| 135 | 155 |
| 136 def check_selinux(): | 156 def check_selinux(): |
| 137 # Marshmallow has no filesystem access whatsoever. It might be possible to | 157 # Marshmallow has no filesystem access whatsoever. It might be possible to |
| 138 # get things working on Lollipop, but attempts so far have failed. | 158 # get things working on Lollipop, but attempts so far have failed. |
| 139 # http://crbug.com/558818 | 159 # http://crbug.com/558818 |
| 140 has_selinux = device.build_version_sdk >= version_codes.LOLLIPOP | 160 has_selinux = device.build_version_sdk >= version_codes.LOLLIPOP |
| 141 if has_selinux and apk.HasIsolatedProcesses(): | 161 if has_selinux and apk.HasIsolatedProcesses(): |
| 142 raise Exception('Cannot use incremental installs on Android L+ without ' | 162 raise Exception('Cannot use incremental installs on Android L+ without ' |
| 143 'first disabling isoloated processes.\n' | 163 'first disabling isoloated processes.\n' |
| 144 'To do so, use GN arg:\n' | 164 'To do so, use GN arg:\n' |
| 145 ' disable_incremental_isolated_processes=true') | 165 ' disable_incremental_isolated_processes=true') |
| 146 | 166 |
| 147 cache_path = '%s/files-cache.json' % device_incremental_dir | 167 cache_path = _DeviceCachePath(device) |
| 148 def restore_cache(): | 168 def restore_cache(): |
| 149 if not enable_device_cache: | 169 if not enable_device_cache: |
| 150 logging.info('Ignoring device cache') | 170 logging.info('Ignoring device cache') |
| 151 return | 171 return |
| 152 # Delete the cached file so that any exceptions cause the next attempt | 172 if os.path.exists(cache_path): |
| 153 # to re-compute md5s. | 173 logging.info('Using device cache: %s', cache_path) |
| 154 cmd = 'P=%s;cat $P 2>/dev/null && rm $P' % cache_path | 174 with open(cache_path) as f: |
| 155 lines = device.RunShellCommand(cmd, check_return=False, large_output=True) | 175 device.LoadCacheData(f.read()) |
| 156 if lines: | 176 # Delete the cached file so that any exceptions cause it to be cleared. |
| 157 device.LoadCacheData(lines[0]) | 177 os.unlink(cache_path) |
| 158 else: | 178 else: |
| 159 logging.info('Device cache not found: %s', cache_path) | 179 logging.info('No device cache present: %s', cache_path) |
| 160 | 180 |
| 161 def save_cache(): | 181 def save_cache(): |
| 162 cache_data = device.DumpCacheData() | 182 with open(cache_path, 'w') as f: |
| 163 device.WriteFile(cache_path, cache_data) | 183 f.write(device.DumpCacheData()) |
| 184 logging.info('Wrote device cache: %s', cache_path) | |
| 164 | 185 |
| 165 # Create 2 lock files: | 186 # Create 2 lock files: |
| 166 # * install.lock tells the app to pause on start-up (until we release it). | 187 # * install.lock tells the app to pause on start-up (until we release it). |
| 167 # * firstrun.lock is used by the app to pause all secondary processes until | 188 # * firstrun.lock is used by the app to pause all secondary processes until |
| 168 # the primary process finishes loading the .dex / .so files. | 189 # the primary process finishes loading the .dex / .so files. |
| 169 def create_lock_files(): | 190 def create_lock_files(): |
| 170 # Creates or zeros out lock files. | 191 # Creates or zeros out lock files. |
| 171 cmd = ('D="%s";' | 192 cmd = ('D="%s";' |
| 172 'mkdir -p $D &&' | 193 'mkdir -p $D &&' |
| 173 'echo -n >$D/install.lock 2>$D/firstrun.lock') | 194 'echo -n >$D/install.lock 2>$D/firstrun.lock') |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 274 msg = ('More than one device available.\n' | 295 msg = ('More than one device available.\n' |
| 275 'Use --device=SERIAL to select a device.\n' | 296 'Use --device=SERIAL to select a device.\n' |
| 276 'Available devices:\n') | 297 'Available devices:\n') |
| 277 descriptions = all_devices.pMap(lambda d: d.build_description).pGet(None) | 298 descriptions = all_devices.pMap(lambda d: d.build_description).pGet(None) |
| 278 for d, desc in zip(devices, descriptions): | 299 for d, desc in zip(devices, descriptions): |
| 279 msg += ' %s (%s)\n' % (d, desc) | 300 msg += ' %s (%s)\n' % (d, desc) |
| 280 raise Exception(msg) | 301 raise Exception(msg) |
| 281 | 302 |
| 282 apk = apk_helper.ToHelper(args.apk_path) | 303 apk = apk_helper.ToHelper(args.apk_path) |
| 283 if args.uninstall: | 304 if args.uninstall: |
| 284 Uninstall(device, apk.GetPackageName()) | 305 Uninstall(device, apk.GetPackageName(), enable_device_cache=args.cache) |
| 285 else: | 306 else: |
| 286 Install(device, apk, split_globs=args.splits, native_libs=args.native_libs, | 307 Install(device, apk, split_globs=args.splits, native_libs=args.native_libs, |
| 287 dex_files=args.dex_files, enable_device_cache=args.cache, | 308 dex_files=args.dex_files, enable_device_cache=args.cache, |
| 288 use_concurrency=args.threading, | 309 use_concurrency=args.threading, |
| 289 show_proguard_warning=args.show_proguard_warning) | 310 show_proguard_warning=args.show_proguard_warning) |
| 290 | 311 |
| 291 | 312 |
| 292 if __name__ == '__main__': | 313 if __name__ == '__main__': |
| 293 sys.exit(main()) | 314 sys.exit(main()) |
| OLD | NEW |