Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(38)

Side by Side Diff: build/android/incremental_install/installer.py

Issue 1375043002: Speed up incremental_install by caching device checksums between runs. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@device-utils-brace-fix
Patch Set: rebase Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
(...skipping 16 matching lines...) Expand all
27 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp')) 27 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
28 from util import build_utils 28 from util import build_utils
29 29
30 30
31 def _TransformDexPaths(paths): 31 def _TransformDexPaths(paths):
32 """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"].""" 32 """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"]."""
33 prefix_len = len(os.path.commonprefix(paths)) 33 prefix_len = len(os.path.commonprefix(paths))
34 return [p[prefix_len:].replace(os.sep, '.') for p in paths] 34 return [p[prefix_len:].replace(os.sep, '.') for p in paths]
35 35
36 36
37 def _Execute(concurrently, funcs):
38 """Calls all functions in |funcs| concurrently or in sequence."""
39 timer = time_profile.TimeProfile()
40 if concurrently:
41 reraiser_thread.RunAsync(funcs)
42 else:
43 for f in funcs:
44 f()
45 timer.Stop(log=False)
46 return timer
47
48
37 def main(): 49 def main():
38 parser = argparse.ArgumentParser() 50 parser = argparse.ArgumentParser()
39 parser.add_argument('apk_path', 51 parser.add_argument('apk_path',
40 help='The path to the APK to install.') 52 help='The path to the APK to install.')
41 parser.add_argument('--split', 53 parser.add_argument('--split',
42 action='append', 54 action='append',
43 dest='splits', 55 dest='splits',
44 help='A glob matching the apk splits. ' 56 help='A glob matching the apk splits. '
45 'Can be specified multiple times.') 57 'Can be specified multiple times.')
46 parser.add_argument('--lib-dir', 58 parser.add_argument('--lib-dir',
47 help='Path to native libraries directory.') 59 help='Path to native libraries directory.')
48 parser.add_argument('--dex-files', 60 parser.add_argument('--dex-files',
49 help='List of dex files to push.', 61 help='List of dex files to push.',
50 action='append', 62 action='append',
51 default=[]) 63 default=[])
52 parser.add_argument('-d', '--device', dest='device', 64 parser.add_argument('-d', '--device', dest='device',
53 help='Target device for apk to install on.') 65 help='Target device for apk to install on.')
54 parser.add_argument('--uninstall', 66 parser.add_argument('--uninstall',
55 action='store_true', 67 action='store_true',
56 default=False, 68 default=False,
57 help='Remove the app and all side-loaded files.') 69 help='Remove the app and all side-loaded files.')
58 parser.add_argument('--output-directory', 70 parser.add_argument('--output-directory',
59 help='Path to the root build directory.') 71 help='Path to the root build directory.')
60 parser.add_argument('--no-threading', 72 parser.add_argument('--no-threading',
61 action='store_true', 73 action='store_true',
jbudorick 2015/10/01 17:15:26 nit: using dest='threading', action='store_false'
agrieve 2015/10/01 18:30:16 Done.
62 default=False, 74 default=False,
63 help='Do not install and push concurrently') 75 help='Do not install and push concurrently')
64 parser.add_argument('-v', 76 parser.add_argument('-v',
65 '--verbose', 77 '--verbose',
66 dest='verbose_count', 78 dest='verbose_count',
67 default=0, 79 default=0,
68 action='count', 80 action='count',
69 help='Verbose level (multiple times for more)') 81 help='Verbose level (multiple times for more)')
70 82
71 args = parser.parse_args() 83 args = parser.parse_args()
72 84
73 run_tests_helper.SetLogLevel(args.verbose_count) 85 run_tests_helper.SetLogLevel(args.verbose_count)
74 constants.SetBuildType('Debug') 86 constants.SetBuildType('Debug')
75 if args.output_directory: 87 if args.output_directory:
76 constants.SetOutputDirectory(args.output_directory) 88 constants.SetOutputDirectory(args.output_directory)
77 89
78 main_timer = time_profile.TimeProfile() 90 main_timer = time_profile.TimeProfile()
79 install_timer = time_profile.TimeProfile() 91 install_timer = time_profile.TimeProfile()
80 push_native_timer = time_profile.TimeProfile() 92 push_native_timer = time_profile.TimeProfile()
81 push_dex_timer = time_profile.TimeProfile() 93 push_dex_timer = time_profile.TimeProfile()
82 94
83 if args.device: 95 if args.device:
84 # Retries are annoying when commands fail for legitimate reasons. Might want 96 # Retries are annoying when commands fail for legitimate reasons. Might want
85 # to enable them if this is ever used on bots though. 97 # to enable them if this is ever used on bots though.
86 device = device_utils.DeviceUtils(args.device, default_retries=0) 98 device = device_utils.DeviceUtils(
99 args.device, default_retries=0, enable_device_files_cache=True)
87 else: 100 else:
88 devices = device_utils.DeviceUtils.HealthyDevices(default_retries=0) 101 devices = device_utils.DeviceUtils.HealthyDevices(
102 default_retries=0, enable_device_files_cache=True)
89 if not devices: 103 if not devices:
90 raise device_errors.NoDevicesError() 104 raise device_errors.NoDevicesError()
91 elif len(devices) == 1: 105 elif len(devices) == 1:
92 device = devices[0] 106 device = devices[0]
93 else: 107 else:
94 all_devices = device_utils.DeviceUtils.parallel(devices) 108 all_devices = device_utils.DeviceUtils.parallel(devices)
95 msg = ('More than one device available.\n' 109 msg = ('More than one device available.\n'
96 'Use --device=SERIAL to select a device.\n' 110 'Use --device=SERIAL to select a device.\n'
97 'Available devices:\n') 111 'Available devices:\n')
98 descriptions = all_devices.pMap(lambda d: d.build_description).pGet(None) 112 descriptions = all_devices.pMap(lambda d: d.build_description).pGet(None)
99 for d, desc in zip(devices, descriptions): 113 for d, desc in zip(devices, descriptions):
100 msg += ' %s (%s)\n' % (d, desc) 114 msg += ' %s (%s)\n' % (d, desc)
101 raise Exception(msg) 115 raise Exception(msg)
102 116
103 apk_help = apk_helper.ApkHelper(args.apk_path) 117 apk_help = apk_helper.ApkHelper(args.apk_path)
104 apk_package = apk_help.GetPackageName() 118 apk_package = apk_help.GetPackageName()
105 device_incremental_dir = '/data/local/tmp/incremental-app-%s' % apk_package 119 device_incremental_dir = '/data/local/tmp/incremental-app-%s' % apk_package
106 120
107 if args.uninstall: 121 if args.uninstall:
108 device.Uninstall(apk_package) 122 device.Uninstall(apk_package)
109 device.RunShellCommand(['rm', '-rf', device_incremental_dir], 123 device.RunShellCommand(['rm', '-rf', device_incremental_dir],
110 check_return=True) 124 check_return=True)
111 logging.info('Uninstall took %s seconds.', main_timer.GetDelta()) 125 logging.info('Uninstall took %s seconds.', main_timer.GetDelta())
112 return 126 return
113 127
114 if device.build_version_sdk >= version_codes.MARSHMALLOW:
115 if apk_help.HasIsolatedProcesses():
116 raise Exception('Cannot use perform incremental installs on Android M+ '
117 'without first disabling isolated processes. Use GN arg: '
118 'disable_incremental_isolated_processes=true to do so.')
119
120 # Install .apk(s) if any of them have changed. 128 # Install .apk(s) if any of them have changed.
121 def do_install(): 129 def do_install():
122 install_timer.Start() 130 install_timer.Start()
123 if args.splits: 131 if args.splits:
124 splits = [] 132 splits = []
125 for split_glob in args.splits: 133 for split_glob in args.splits:
126 splits.extend((f for f in glob.glob(split_glob))) 134 splits.extend((f for f in glob.glob(split_glob)))
127 device.InstallSplitApk(args.apk_path, splits, reinstall=True, 135 device.InstallSplitApk(args.apk_path, splits, reinstall=True,
128 allow_cached_props=True, permissions=()) 136 allow_cached_props=True, permissions=())
129 else: 137 else:
(...skipping 16 matching lines...) Expand all
146 with build_utils.TempDir() as temp_dir: 154 with build_utils.TempDir() as temp_dir:
147 device_dex_dir = posixpath.join(device_incremental_dir, 'dex') 155 device_dex_dir = posixpath.join(device_incremental_dir, 'dex')
148 # Ensure no two files have the same name. 156 # Ensure no two files have the same name.
149 transformed_names = _TransformDexPaths(args.dex_files) 157 transformed_names = _TransformDexPaths(args.dex_files)
150 for src_path, dest_name in zip(args.dex_files, transformed_names): 158 for src_path, dest_name in zip(args.dex_files, transformed_names):
151 shutil.copyfile(src_path, os.path.join(temp_dir, dest_name)) 159 shutil.copyfile(src_path, os.path.join(temp_dir, dest_name))
152 device.PushChangedFiles([(temp_dir, device_dex_dir)], 160 device.PushChangedFiles([(temp_dir, device_dex_dir)],
153 delete_device_stale=True) 161 delete_device_stale=True)
154 push_dex_timer.Stop(log=False) 162 push_dex_timer.Stop(log=False)
155 163
164 def check_sdk_version():
165 if device.build_version_sdk >= version_codes.MARSHMALLOW:
166 if apk_help.HasIsolatedProcesses():
167 raise Exception('Cannot use perform incremental installs on Android M+ '
168 'without first disabling isolated processes.\n'
169 'To do so, use GN arg:\n'
170 ' disable_incremental_isolated_processes=true')
171
172 cache_path = '%s/files-cache.json' % device_incremental_dir
173 def restore_cache():
174 # Delete the cached file so that any exceptions cause the next attempt
175 # to re-compute md5s.
176 cmd = 'P=%s;cat $P 2>/dev/null && rm $P' % cache_path
177 lines = device.RunShellCommand(cmd, large_output=True)
jbudorick 2015/10/01 17:15:27 This appears to be using the failure mode as a val
agrieve 2015/10/01 18:30:16 Done.
178 if lines:
179 device.LoadCacheData(lines[0])
180 else:
181 logging.info('Device cache not found: %s', cache_path)
182
183 def save_cache():
184 cache_data = device.DumpCacheData()
185 device.WriteFile(cache_path, cache_data)
186
156 # Create 2 lock files: 187 # Create 2 lock files:
157 # * install.lock tells the app to pause on start-up (until we release it). 188 # * install.lock tells the app to pause on start-up (until we release it).
158 # * firstrun.lock is used by the app to pause all secondary processes until 189 # * firstrun.lock is used by the app to pause all secondary processes until
159 # the primary process finishes loading the .dex / .so files. 190 # the primary process finishes loading the .dex / .so files.
160 def create_lock_files(): 191 def create_lock_files():
161 # Creates or zeros out lock files. 192 # Creates or zeros out lock files.
162 cmd = ('D="%s";' 193 cmd = ('D="%s";'
163 'mkdir -p $D &&' 194 'mkdir -p $D &&'
164 'echo -n >$D/install.lock 2>$D/firstrun.lock') 195 'echo -n >$D/install.lock 2>$D/firstrun.lock')
165 device.RunShellCommand(cmd % device_incremental_dir, check_return=True) 196 device.RunShellCommand(cmd % device_incremental_dir, check_return=True)
166 197
167 # The firstrun.lock is released by the app itself. 198 # The firstrun.lock is released by the app itself.
168 def release_installer_lock(): 199 def release_installer_lock():
169 device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir, 200 device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir,
170 check_return=True) 201 check_return=True)
171 202
172 create_lock_files()
173 # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't 203 # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't
174 # been designed for multi-threading. Enabling only because this is a 204 # been designed for multi-threading. Enabling only because this is a
175 # developer-only tool. 205 # developer-only tool.
176 if args.no_threading: 206 concurrently = not args.no_threading
jbudorick 2015/10/01 17:15:27 by using dest='threading' above, you could elimina
agrieve 2015/10/01 18:30:16 Done. Although sadly the 80 character limit adds a
177 do_install() 207 setup_timer = _Execute(
178 do_push_files() 208 concurrently, (create_lock_files, restore_cache, check_sdk_version))
179 else: 209
180 reraiser_thread.RunAsync((do_install, do_push_files)) 210 _Execute(concurrently, (do_install, do_push_files))
181 release_installer_lock() 211
182 logging.info('Took %s seconds (install=%s, libs=%s, dex=%s)', 212 finalize_timer = _Execute(concurrently, (release_installer_lock, save_cache))
183 main_timer.GetDelta(), install_timer.GetDelta(), 213
184 push_native_timer.GetDelta(), push_dex_timer.GetDelta()) 214 logging.info(
215 'Took %s seconds (setup=%s, install=%s, libs=%s, dex=%s, finalize=%s)',
216 main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(),
217 push_native_timer.GetDelta(), push_dex_timer.GetDelta(),
218 finalize_timer.GetDelta())
185 219
186 220
187 if __name__ == '__main__': 221 if __name__ == '__main__':
188 sys.exit(main()) 222 sys.exit(main())
189 223
OLDNEW
« build/android/devil/android/device_utils.py ('K') | « build/android/devil/android/device_utils.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698