2 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
7 """Script that deploys a Chrome build to a device.
9 The script supports deploying Chrome from these sources:
11 1. A local build output directory, such as chromium/src/out/[Debug|Release].
12 2. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
13 3. A Chrome tarball existing locally.
15 The script copies the necessary contents of the source location (tarball or
16 build directory) and rsyncs the contents of the staging directory onto your
25 import multiprocessing
35 from chromite.buildbot import constants
36 from chromite.buildbot import cbuildbot_results as results_lib
37 from chromite.cros.commands import cros_chrome_sdk
38 from chromite.lib import chrome_util
39 from chromite.lib import cros_build_lib
40 from chromite.lib import commandline
41 from chromite.lib import gs
42 from chromite.lib import osutils
43 from chromite.lib import parallel
44 from chromite.lib import remote_access as remote
45 from chromite.lib import stats
46 from chromite.lib import timeout_util
47 from chromite.scripts import lddtree
50 _USAGE = "deploy_chrome [--]\n\n %s" % __doc__
52 KERNEL_A_PARTITION = 2
53 KERNEL_B_PARTITION = 4
55 KILL_PROC_MAX_WAIT = 10
58 MOUNT_RW_COMMAND = 'mount -o remount,rw /'
59 LSOF_COMMAND = 'lsof %s/chrome'
61 MOUNT_RW_COMMAND_ANDROID = 'mount -o remount,rw /system'
63 _ANDROID_DIR = '/system/chrome'
64 _ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
66 _CHROME_DIR = '/opt/google/chrome'
67 _CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
69 _BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
70 _SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
72 DF_COMMAND = 'df -k %s'
73 DF_COMMAND_ANDROID = 'df %s'
75 def _UrlBaseName(url):
76 """Return the last component of the URL."""
77 return url.rstrip('/').rpartition('/')[-1]
80 class DeployFailure(results_lib.StepFailure):
81 """Raised whenever the deploy fails."""
84 DeviceInfo = collections.namedtuple(
85 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
88 class DeployChrome(object):
89 """Wraps the core deployment functionality."""
90 def __init__(self, options, tempdir, staging_dir):
91 """Initialize the class.
94 options: Optparse result structure.
95 tempdir: Scratch space for the class. Caller has responsibility to clean
97 staging_dir: Directory to stage the files to.
99 self.tempdir = tempdir
100 self.options = options
101 self.staging_dir = staging_dir
102 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
103 self._rootfs_is_still_readonly = multiprocessing.Event()
105 # Used to track whether deploying content_shell or chrome to a device.
106 self.content_shell = False
107 self.copy_paths = chrome_util.GetCopyPaths(False)
108 self.chrome_dir = _CHROME_DIR
110 def _GetRemoteMountFree(self, remote_dir):
111 result = self.host.RemoteSh((DF_COMMAND if not self.content_shell
112 else DF_COMMAND_ANDROID) % remote_dir,
114 line = result.output.splitlines()[1]
115 value = line.split()[3]
117 'G': 1024 * 1024 * 1024,
121 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
123 def _GetRemoteDirSize(self, remote_dir):
124 if self.content_shell:
125 # Content Shell devices currently do not contain the du binary.
126 logging.warning('Remote host does not contain du; cannot get remote '
127 'directory size to properly calculate available free '
130 result = self.host.RemoteSh('du -ks %s' % remote_dir, capture_output=True)
131 return int(result.output.split()[0])
133 def _GetStagingDirSize(self):
134 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
135 redirect_stdout=True,
137 return int(result.output.split()[0])
139 def _ChromeFileInUse(self):
140 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
141 error_code_ok=True, capture_output=True)
142 return result.returncode == 0
144 def _DisableRootfsVerification(self):
145 if not self.options.force:
146 logging.error('Detected that the device has rootfs verification enabled.')
147 logging.info('This script can automatically remove the rootfs '
148 'verification, which requires that it reboot the device.')
149 logging.info('Make sure the device is in developer mode!')
150 logging.info('Skip this prompt by specifying --force.')
151 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
152 # Since we stopped Chrome earlier, it's good form to start it up again.
153 if self.options.startui:
154 logging.info('Starting Chrome...')
155 self.host.RemoteSh('start ui')
156 raise DeployFailure('Need rootfs verification to be disabled. '
159 logging.info('Removing rootfs verification from %s', self.options.to)
160 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
161 # Use --force to bypass the checks.
162 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
163 '--remove_rootfs_verification --force')
164 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
165 self.host.RemoteSh(cmd % partition, error_code_ok=True)
167 # A reboot in developer mode takes a while (and has delays), so the user
168 # will have time to read and act on the USB boot instructions below.
169 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
170 self.host.RemoteReboot()
172 # Now that the machine has been rebooted, we need to kill Chrome again.
173 self._KillProcsIfNeeded()
175 # Make sure the rootfs is writable now.
176 self._MountRootfsAsWritable(error_code_ok=False)
178 def _CheckUiJobStarted(self):
179 # status output is in the format:
180 # <job_name> <status> ['process' <pid>].
181 # <status> is in the format <goal>/<state>.
183 result = self.host.RemoteSh('status ui', capture_output=True)
184 except cros_build_lib.RunCommandError as e:
185 if 'Unknown job' in e.result.error:
190 return result.output.split()[1].split('/')[0] == 'start'
192 def _KillProcsIfNeeded(self):
193 if self.content_shell:
194 logging.info('Shutting down content_shell...')
195 self.host.RemoteSh('stop content_shell')
198 if self._CheckUiJobStarted():
199 logging.info('Shutting down Chrome...')
200 self.host.RemoteSh('stop ui')
202 # Developers sometimes run session_manager manually, in which case we'll
203 # need to help shut the chrome processes down.
205 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
206 while self._ChromeFileInUse():
207 logging.warning('The chrome binary on the device is in use.')
208 logging.warning('Killing chrome and session_manager processes...\n')
210 self.host.RemoteSh("pkill 'chrome|session_manager'",
212 # Wait for processes to actually terminate
213 time.sleep(POST_KILL_WAIT)
214 logging.info('Rechecking the chrome binary...')
215 except timeout_util.TimeoutError:
216 msg = ('Could not kill processes after %s seconds. Please exit any '
217 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
218 raise DeployFailure(msg)
220 def _MountRootfsAsWritable(self, error_code_ok=True):
221 """Mount the rootfs as writable.
223 If the command fails, and error_code_ok is True, then this function sets
224 self._rootfs_is_still_readonly.
227 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
229 # TODO: Should migrate to use the remount functions in remote_access.
230 result = self.host.RemoteSh(MOUNT_RW_COMMAND if not self.content_shell
231 else MOUNT_RW_COMMAND_ANDROID,
232 error_code_ok=error_code_ok,
234 if result.returncode:
235 self._rootfs_is_still_readonly.set()
237 def _GetDeviceInfo(self):
239 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
240 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
242 return_values = parallel.RunParallelSteps(steps, return_values=True)
243 return DeviceInfo(*return_values)
245 def _CheckDeviceFreeSpace(self, device_info):
246 """See if target device has enough space for Chrome.
249 device_info: A DeviceInfo named tuple.
251 effective_free = device_info.target_dir_size + device_info.target_fs_free
252 staging_size = self._GetStagingDirSize()
253 # For content shell deployments, which do not contain the du binary,
254 # do not raise DeployFailure since can't get exact free space available
256 if effective_free < staging_size:
257 if self.content_shell:
258 logging.warning('Not enough free space on the device. If overwriting '
259 'files, deployment may still succeed. Required: %s '
260 'MiB, actual: %s MiB.', staging_size / 1024,
261 effective_free / 1024)
264 'Not enough free space on the device. Required: %s MiB, '
265 'actual: %s MiB.' % (staging_size / 1024, effective_free / 1024))
266 if device_info.target_fs_free < (100 * 1024):
267 logging.warning('The device has less than 100MB free. deploy_chrome may '
268 'hang during the transfer.')
271 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
272 # Show the output (status) for this command.
273 dest_path = _CHROME_DIR
274 if self.content_shell:
276 self.host.Scp('%s/*' % os.path.abspath(self.staging_dir),
277 '%s/' % self.options.target_dir,
278 debug_level=logging.INFO,
279 verbose=self.options.verbose)
280 except cros_build_lib.RunCommandError as ex:
281 if ex.result.returncode != 1:
282 logging.error('Scp failure [%s]', ex.result.returncode)
283 raise DeployFailure(ex)
285 # TODO(stevefung): Update Dropbear SSHD on device.
286 # http://crbug.com/329656
287 logging.info('Potential conflict with DropBear SSHD return status')
289 dest_path = _ANDROID_DIR
291 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
292 self.options.target_dir,
293 inplace=True, debug_level=logging.INFO,
294 verbose=self.options.verbose)
296 for p in self.copy_paths:
298 self.host.RemoteSh('chown %s %s/%s' % (p.owner, dest_path,
299 p.src if not p.dest else p.dest))
301 # Set mode if necessary.
302 self.host.RemoteSh('chmod %o %s/%s' % (p.mode, dest_path,
303 p.src if not p.dest else p.dest))
306 if self.options.startui:
307 logging.info('Starting UI...')
308 if self.content_shell:
309 self.host.RemoteSh('start content_shell')
311 self.host.RemoteSh('start ui')
313 def _CheckConnection(self):
315 logging.info('Testing connection to the device...')
316 if self.content_shell:
317 # true command over ssh returns error code 255, so as workaround
318 # use `sleep 0` as no-op.
319 self.host.RemoteSh('sleep 0')
321 self.host.RemoteSh('true')
322 except cros_build_lib.RunCommandError as ex:
323 logging.error('Error connecting to the test device.')
324 raise DeployFailure(ex)
326 def _CheckDeployType(self):
327 if self.options.build_dir:
328 if os.path.exists(os.path.join(self.options.build_dir, 'system.unand')):
329 # Unand Content shell deployment.
330 self.content_shell = True
331 self.options.build_dir = os.path.join(self.options.build_dir,
332 'system.unand/chrome/')
333 self.options.dostrip = False
334 self.options.target_dir = _ANDROID_DIR
335 self.copy_paths = chrome_util.GetCopyPaths(True)
336 elif os.path.exists(os.path.join(self.options.build_dir,
337 'content_shell')) and not os.path.exists(
338 os.path.join(self.options.build_dir, 'chrome')):
339 # Content shell deployment
340 self.content_shell = True
341 self.copy_paths = chrome_util.GetCopyPaths(True)
342 elif self.options.local_pkg_path or self.options.gs_path:
343 # Package deployment.
344 pkg_path = self.options.local_pkg_path
345 if self.options.gs_path:
346 pkg_path = _FetchChromePackage(self.options.cache_dir, self.tempdir,
347 self.options.gs_path)
350 logging.info('Checking %s for content_shell...', pkg_path)
351 if pkg_path[-4:] == '.zip':
352 zip_pkg = zipfile.ZipFile(pkg_path)
353 if any('eureka_shell' in name for name in zip_pkg.namelist()):
354 self.content_shell = True
357 tar = tarfile.open(pkg_path)
358 if any('eureka_shell' in member.name for member in tar.getmembers()):
359 self.content_shell = True
362 if self.content_shell:
363 self.options.dostrip = False
364 self.options.target_dir = _ANDROID_DIR
365 self.copy_paths = chrome_util.GetCopyPaths(True)
366 self.chrome_dir = _ANDROID_DIR
368 def _PrepareStagingDir(self):
369 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
370 self.copy_paths, self.chrome_dir)
372 def _MountTarget(self):
373 logging.info('Mounting Chrome...')
375 # Create directory if does not exist
376 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
377 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
378 self.options.mount_dir))
379 # Chrome needs partition to have exec and suid flags set
380 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
383 self._CheckDeployType()
385 # If requested, just do the staging step.
386 if self.options.staging_only:
387 self._PrepareStagingDir()
390 # Run setup steps in parallel. If any step fails, RunParallelSteps will
391 # stop printing output at that point, and halt any running steps.
392 steps = [self._GetDeviceInfo, self._CheckConnection,
393 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
394 self._PrepareStagingDir]
395 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
397 self._CheckDeviceFreeSpace(ret[0])
399 # If we failed to mark the rootfs as writable, try disabling rootfs
401 if self._rootfs_is_still_readonly.is_set():
402 self._DisableRootfsVerification()
404 if self.options.mount_dir is not None:
407 # Actually deploy Chrome to the device.
411 def ValidateGypDefines(_option, _opt, value):
412 """Convert GYP_DEFINES-formatted string to dictionary."""
413 return chrome_util.ProcessGypDefines(value)
416 class CustomOption(commandline.Option):
417 """Subclass Option class to implement path evaluation."""
418 TYPES = commandline.Option.TYPES + ('gyp_defines',)
419 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
420 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
424 """Create our custom parser."""
425 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
428 # TODO(rcui): Have this use the UI-V2 format of having source and target
429 # device be specified as positional arguments.
430 parser.add_option('--force', action='store_true', default=False,
431 help='Skip all prompts (i.e., for disabling of rootfs '
432 'verification). This may result in the target '
433 'machine being rebooted.')
434 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
435 parser.add_option('--board', default=sdk_board_env,
436 help="The board the Chrome build is targeted for. When in "
437 "a 'cros chrome-sdk' shell, defaults to the SDK "
439 parser.add_option('--build-dir', type='path',
440 help='The directory with Chrome build artifacts to deploy '
441 'from. Typically of format <chrome_root>/out/Debug. '
442 'When this option is used, the GYP_DEFINES '
443 'environment variable must be set.')
444 parser.add_option('--target-dir', type='path',
445 help='Target directory on device to deploy Chrome into.',
447 parser.add_option('-g', '--gs-path', type='gs_path',
448 help='GS path that contains the chrome to deploy.')
449 parser.add_option('--nostartui', action='store_false', dest='startui',
451 help="Don't restart the ui daemon after deployment.")
452 parser.add_option('--nostrip', action='store_false', dest='dostrip',
454 help="Don't strip binaries during deployment. Warning: "
455 "the resulting binaries will be very large!")
456 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
457 help='Port of the target device to connect to.')
458 parser.add_option('-t', '--to',
459 help='The IP address of the CrOS device to deploy to.')
460 parser.add_option('-v', '--verbose', action='store_true', default=False,
461 help='Show more debug output.')
462 parser.add_option('--mount-dir', type='path', default=None,
463 help='Deploy Chrome in target directory and bind it'
464 'to directory specified by this flag.')
465 parser.add_option('--mount', action='store_true', default=False,
466 help='Deploy Chrome to default target directory and bind it'
467 'to default mount directory.')
469 group = optparse.OptionGroup(parser, 'Advanced Options')
470 group.add_option('-l', '--local-pkg-path', type='path',
471 help='Path to local chrome prebuilt package to deploy.')
472 group.add_option('--sloppy', action='store_true', default=False,
473 help='Ignore when mandatory artifacts are missing.')
474 group.add_option('--staging-flags', default=None, type='gyp_defines',
475 help='Extra flags to control staging. Valid flags are - %s'
476 % ', '.join(chrome_util.STAGING_FLAGS))
477 group.add_option('--strict', action='store_true', default=False,
478 help='Stage artifacts based on the GYP_DEFINES environment '
479 'variable and --staging-flags, if set. Enforce that '
480 'all optional artifacts are deployed.')
481 group.add_option('--strip-flags', default=None,
482 help="Flags to call the 'strip' binutil tool with. "
483 "Overrides the default arguments.")
485 parser.add_option_group(group)
487 # GYP_DEFINES that Chrome was built with. Influences which files are staged
488 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
489 # enviroment variable.
490 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
491 help=optparse.SUPPRESS_HELP)
492 # Path of an empty directory to stage chrome artifacts to. Defaults to a
493 # temporary directory that is removed when the script finishes. If the path
494 # is specified, then it will not be removed.
495 parser.add_option('--staging-dir', type='path', default=None,
496 help=optparse.SUPPRESS_HELP)
497 # Only prepare the staging directory, and skip deploying to the device.
498 parser.add_option('--staging-only', action='store_true', default=False,
499 help=optparse.SUPPRESS_HELP)
500 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
501 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
502 # fetching the SDK toolchain.
503 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
507 def _ParseCommandLine(argv):
508 """Parse args, and run environment-independent checks."""
509 parser = _CreateParser()
510 (options, args) = parser.parse_args(argv)
512 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
513 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
515 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
516 parser.error('Cannot specify both --build_dir and '
517 '--gs-path/--local-pkg-patch')
518 if options.build_dir and not options.board:
519 parser.error('--board is required when --build-dir is specified.')
520 if options.gs_path and options.local_pkg_path:
521 parser.error('Cannot specify both --gs-path and --local-pkg-path')
522 if not (options.staging_only or options.to):
523 parser.error('Need to specify --to')
524 if (options.strict or options.staging_flags) and not options.build_dir:
525 parser.error('--strict and --staging-flags require --build-dir to be '
527 if options.staging_flags and not options.strict:
528 parser.error('--staging-flags requires --strict to be set.')
529 if options.sloppy and options.strict:
530 parser.error('Cannot specify both --strict and --sloppy.')
532 if options.mount or options.mount_dir:
533 if not options.target_dir:
534 options.target_dir = _CHROME_DIR_MOUNT
536 if not options.target_dir:
537 options.target_dir = _CHROME_DIR
539 if options.mount and not options.mount_dir:
540 options.mount_dir = _CHROME_DIR
545 def _PostParseCheck(options, _args):
546 """Perform some usage validation (after we've parsed the arguments).
549 options: The options object returned by optparse.
550 _args: The args object returned by optparse.
552 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
553 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
555 if not options.gyp_defines:
556 gyp_env = os.getenv('GYP_DEFINES', None)
557 if gyp_env is not None:
558 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
559 logging.debug('GYP_DEFINES taken from environment: %s',
562 if options.strict and not options.gyp_defines:
563 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
564 'variable must be set.')
566 if options.build_dir:
567 chrome_path = os.path.join(options.build_dir, 'chrome')
568 if os.path.isfile(chrome_path):
569 deps = lddtree.ParseELF(chrome_path)
570 if 'libbase.so' in deps['libs']:
571 cros_build_lib.Warning(
572 'Detected a component build of Chrome. component build is '
573 'not working properly for Chrome OS. See crbug.com/196317. '
574 'Use at your own risk!')
577 def _FetchChromePackage(cache_dir, tempdir, gs_path):
578 """Get the chrome prebuilt tarball from GS.
581 Path to the fetched chrome tarball.
583 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
584 files = gs_ctx.LS(gs_path)
585 files = [found for found in files if
586 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
588 raise Exception('No chrome package found at %s' % gs_path)
590 # - Users should provide us with a direct link to either a stripped or
591 # unstripped chrome package.
592 # - In the case of being provided with an archive directory, where both
593 # stripped and unstripped chrome available, use the stripped chrome
595 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
596 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
597 files = [f for f in files if not 'unstripped' in f]
598 assert len(files) == 1
599 logging.warning('Multiple chrome packages found. Using %s', files[0])
601 filename = _UrlBaseName(files[0])
602 logging.info('Fetching %s...', filename)
603 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
604 chrome_path = os.path.join(tempdir, filename)
605 assert os.path.exists(chrome_path)
609 @contextlib.contextmanager
610 def _StripBinContext(options):
611 if not options.dostrip:
613 elif options.strip_bin:
614 yield options.strip_bin
616 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
617 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
618 with sdk.Prepare(components=components) as ctx:
619 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
620 constants.CHROME_ENV_FILE)
621 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
622 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
623 'bin', os.path.basename(strip_bin))
627 def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
628 chrome_dir=_CHROME_DIR):
629 """Place the necessary files in the staging directory.
631 The staging directory is the directory used to rsync the build artifacts over
632 to the device. Only the necessary Chrome build artifacts are put into the
635 osutils.SafeMakedirs(staging_dir)
636 os.chmod(staging_dir, 0o755)
637 if options.build_dir:
638 with _StripBinContext(options) as strip_bin:
639 strip_flags = (None if options.strip_flags is None else
640 shlex.split(options.strip_flags))
641 chrome_util.StageChromeFromBuildDir(
642 staging_dir, options.build_dir, strip_bin, strict=options.strict,
643 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
644 staging_flags=options.staging_flags,
645 strip_flags=strip_flags, copy_paths=copy_paths)
647 pkg_path = options.local_pkg_path
649 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
653 logging.info('Extracting %s...', pkg_path)
654 # Extract only the ./opt/google/chrome contents, directly into the staging
655 # dir, collapsing the directory hierarchy.
656 if pkg_path[-4:] == '.zip':
657 cros_build_lib.DebugRunCommand(
658 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
660 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
661 shutil.move(filename, staging_dir)
662 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
664 cros_build_lib.DebugRunCommand(
665 ['tar', '--strip-components', '4', '--extract',
666 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
671 options, args = _ParseCommandLine(argv)
672 _PostParseCheck(options, args)
674 # Set cros_build_lib debug level to hide RunCommand spew.
676 logging.getLogger().setLevel(logging.DEBUG)
678 logging.getLogger().setLevel(logging.INFO)
680 with stats.UploadContext() as queue:
681 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
683 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
685 with osutils.TempDir(set_global=True) as tempdir:
686 staging_dir = options.staging_dir
688 staging_dir = os.path.join(tempdir, 'chrome')
690 deploy = DeployChrome(options, tempdir, staging_dir)
693 except results_lib.StepFailure as ex:
694 raise SystemExit(str(ex).strip())