blob: 9498a8ea969a3d43fe4d99fc8d0f6b6354092775 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Ryan Cui3045c5d2012-07-13 18:00:33 -07002# 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.
5
Steve Funge984a532013-11-25 17:09:25 -08006"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07007
8The script supports deploying Chrome from these sources:
9
101. A local build output directory, such as chromium/src/out/[Debug|Release].
112. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
123. A Chrome tarball existing locally.
13
14The script copies the necessary contents of the source location (tarball or
15build directory) and rsyncs the contents of the staging directory onto your
16device's rootfs.
17"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070018
Mike Frysinger383367e2014-09-16 15:06:17 -040019from __future__ import print_function
20
Mike Frysingerc3061a62015-06-04 04:16:18 -040021import argparse
Ryan Cui7193a7e2013-04-26 14:15:19 -070022import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080023import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070024import functools
Steve Funge984a532013-11-25 17:09:25 -080025import glob
David James88e6f032013-03-02 08:13:20 -080026import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070027import os
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070028import shlex
Steve Funge984a532013-11-25 17:09:25 -080029import shutil
Ryan Cui3045c5d2012-07-13 18:00:33 -070030import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070031
Aviv Keshetb7519e12016-10-04 00:50:00 -070032from chromite.lib import constants
33from chromite.lib import failures_lib
David Pursellcfd58872015-03-19 09:15:48 -070034from chromite.cli.cros import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070035from chromite.lib import chrome_util
Ryan Cuie535b172012-10-19 18:25:03 -070036from chromite.lib import commandline
Ralph Nathan91874ca2015-03-19 13:29:41 -070037from chromite.lib import cros_build_lib
38from chromite.lib import cros_logging as logging
Ryan Cui777ff422012-12-07 13:12:54 -080039from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070040from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080041from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070042from chromite.lib import remote_access as remote
David James3432acd2013-11-27 10:02:18 -080043from chromite.lib import timeout_util
Steven Bennetts368c3e52016-09-23 13:05:21 -070044from gn_helpers import gn_helpers
Ryan Cui3045c5d2012-07-13 18:00:33 -070045
46
Ryan Cui3045c5d2012-07-13 18:00:33 -070047KERNEL_A_PARTITION = 2
48KERNEL_B_PARTITION = 4
49
50KILL_PROC_MAX_WAIT = 10
51POST_KILL_WAIT = 2
52
David Haddock3151d912017-10-24 03:50:32 +000053MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Anushruth8d797672019-10-17 12:22:31 -070054LSOF_COMMAND_CHROME = 'lsof %s/chrome'
55LSOF_COMMAND = 'lsof %s'
David Haddock3151d912017-10-24 03:50:32 +000056DBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon'
Ryan Cui3045c5d2012-07-13 18:00:33 -070057
Steve Funge984a532013-11-25 17:09:25 -080058_ANDROID_DIR = '/system/chrome'
59_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
60
David James2cb34002013-03-01 18:42:40 -080061_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070062_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080063
David Haddock3151d912017-10-24 03:50:32 +000064_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
65 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
66_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
67_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Anushruth8d797672019-10-17 12:22:31 -070068_MKDIR_P_CMD = 'mkdir -p --mode 0775 %s'
David Haddock3151d912017-10-24 03:50:32 +000069
70DF_COMMAND = 'df -k %s'
71
Mike Frysingere65f3752014-12-08 00:46:39 -050072
Ryan Cui3045c5d2012-07-13 18:00:33 -070073def _UrlBaseName(url):
74 """Return the last component of the URL."""
75 return url.rstrip('/').rpartition('/')[-1]
76
77
Yu-Ju Hongc54d3342014-05-14 12:42:06 -070078class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -080079 """Raised whenever the deploy fails."""
80
81
Ryan Cui7193a7e2013-04-26 14:15:19 -070082DeviceInfo = collections.namedtuple(
83 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
84
85
Ryan Cui3045c5d2012-07-13 18:00:33 -070086class DeployChrome(object):
87 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -050088
Ryan Cuia56a71e2012-10-18 18:40:35 -070089 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070090 """Initialize the class.
91
Mike Frysinger02e1e072013-11-10 22:11:34 -050092 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -040093 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -070094 tempdir: Scratch space for the class. Caller has responsibility to clean
95 it up.
Steve Funge984a532013-11-25 17:09:25 -080096 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -070097 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070098 self.tempdir = tempdir
99 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -0700100 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -0500101 if not self.options.staging_only:
Pawel Osciak141b2262014-12-21 10:27:05 +0900102 self.device = remote.RemoteDevice(options.to, port=options.port,
Adrian Eldera2c548a2017-11-07 19:01:29 -0500103 ping=options.ping,
104 private_key=options.private_key)
Steven Bennettsca73efa2018-07-10 13:36:56 -0700105 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000106
Sadrul Habib Chowdhury977aef72016-11-21 16:30:37 -0500107 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800108 self.chrome_dir = _CHROME_DIR
109
Ryan Cui7193a7e2013-04-26 14:15:19 -0700110 def _GetRemoteMountFree(self, remote_dir):
David Haddock3151d912017-10-24 03:50:32 +0000111 result = self.device.RunCommand(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700112 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800113 value = line.split()[3]
114 multipliers = {
115 'G': 1024 * 1024 * 1024,
116 'M': 1024 * 1024,
117 'K': 1024,
118 }
119 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700120
121 def _GetRemoteDirSize(self, remote_dir):
David Haddock3151d912017-10-24 03:50:32 +0000122 result = self.device.RunCommand('du -ks %s' % remote_dir,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500123 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700124 return int(result.output.split()[0])
125
126 def _GetStagingDirSize(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400127 result = cros_build_lib.dbg_run(['du', '-ks', self.staging_dir],
128 redirect_stdout=True, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700129 return int(result.output.split()[0])
130
Ryan Cui3045c5d2012-07-13 18:00:33 -0700131 def _ChromeFileInUse(self):
Anushruth8d797672019-10-17 12:22:31 -0700132 result = self.device.RunCommand(LSOF_COMMAND_CHROME %
133 (self.options.target_dir,),
David Haddock3151d912017-10-24 03:50:32 +0000134 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700135 return result.returncode == 0
136
Justin TerAvestfac210e2017-04-13 11:39:00 -0600137 def _Reboot(self):
138 # A reboot in developer mode takes a while (and has delays), so the user
139 # will have time to read and act on the USB boot instructions below.
140 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
141 self.device.Reboot()
142
Ryan Cui3045c5d2012-07-13 18:00:33 -0700143 def _DisableRootfsVerification(self):
144 if not self.options.force:
145 logging.error('Detected that the device has rootfs verification enabled.')
146 logging.info('This script can automatically remove the rootfs '
147 'verification, which requires that it reboot the device.')
148 logging.info('Make sure the device is in developer mode!')
149 logging.info('Skip this prompt by specifying --force.')
Ben Chan37d64e52018-10-05 15:35:02 -0700150 if not cros_build_lib.BooleanPrompt('Remove rootfs verification?', False):
Steven Bennettsca73efa2018-07-10 13:36:56 -0700151 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700152
153 logging.info('Removing rootfs verification from %s', self.options.to)
154 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
155 # Use --force to bypass the checks.
David Haddock3151d912017-10-24 03:50:32 +0000156 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
157 '--remove_rootfs_verification --force')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700158 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
David Haddock3151d912017-10-24 03:50:32 +0000159 self.device.RunCommand(cmd % partition, error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700160
Justin TerAvestfac210e2017-04-13 11:39:00 -0600161 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700162
David James88e6f032013-03-02 08:13:20 -0800163 # Now that the machine has been rebooted, we need to kill Chrome again.
164 self._KillProcsIfNeeded()
165
166 # Make sure the rootfs is writable now.
167 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700168
Steven Bennettsca73efa2018-07-10 13:36:56 -0700169 return True
170
Ryan Cui3045c5d2012-07-13 18:00:33 -0700171 def _CheckUiJobStarted(self):
172 # status output is in the format:
173 # <job_name> <status> ['process' <pid>].
174 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800175 try:
David Haddock3151d912017-10-24 03:50:32 +0000176 result = self.device.RunCommand('status ui', capture_output=True)
Ryan Cuif2d1a582013-02-19 14:08:13 -0800177 except cros_build_lib.RunCommandError as e:
178 if 'Unknown job' in e.result.error:
179 return False
180 else:
181 raise e
182
Ryan Cui3045c5d2012-07-13 18:00:33 -0700183 return result.output.split()[1].split('/')[0] == 'start'
184
185 def _KillProcsIfNeeded(self):
186 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800187 logging.info('Shutting down Chrome...')
David Haddock3151d912017-10-24 03:50:32 +0000188 self.device.RunCommand('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700189
190 # Developers sometimes run session_manager manually, in which case we'll
191 # need to help shut the chrome processes down.
192 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700193 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700194 while self._ChromeFileInUse():
195 logging.warning('The chrome binary on the device is in use.')
196 logging.warning('Killing chrome and session_manager processes...\n')
197
David Haddock3151d912017-10-24 03:50:32 +0000198 self.device.RunCommand("pkill 'chrome|session_manager'",
Mike Frysingere65f3752014-12-08 00:46:39 -0500199 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700200 # Wait for processes to actually terminate
201 time.sleep(POST_KILL_WAIT)
202 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800203 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800204 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700205 'running chrome processes and try again.'
206 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800207 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700208
David James88e6f032013-03-02 08:13:20 -0800209 def _MountRootfsAsWritable(self, error_code_ok=True):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800210 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700211
Steven Bennettsca73efa2018-07-10 13:36:56 -0700212 If the command fails and the root dir is not writable then this function
213 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700214
Mike Frysinger02e1e072013-11-10 22:11:34 -0500215 Args:
David James88e6f032013-03-02 08:13:20 -0800216 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
217 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800218 # TODO: Should migrate to use the remount functions in remote_access.
Robert Flack1dc7ea82014-11-26 13:50:24 -0500219 result = self.device.RunCommand(MOUNT_RW_COMMAND,
220 error_code_ok=error_code_ok,
221 capture_output=True)
Steven Bennettsca73efa2018-07-10 13:36:56 -0700222 if result.returncode and not self.device.IsDirWritable('/'):
223 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700224
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800225 def _EnsureTargetDir(self):
226 """Ensures that the target directory exists on the remote device."""
227 target_dir = self.options.target_dir
228 # Any valid /opt directory should already exist so avoid the remote call.
229 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
230 return
231 self.device.RunCommand(['mkdir', '-p', '--mode', '0775', target_dir])
232
Ryan Cui7193a7e2013-04-26 14:15:19 -0700233 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800234 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700235 steps = [
236 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
237 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
238 ]
239 return_values = parallel.RunParallelSteps(steps, return_values=True)
240 return DeviceInfo(*return_values)
241
242 def _CheckDeviceFreeSpace(self, device_info):
243 """See if target device has enough space for Chrome.
244
Mike Frysinger02e1e072013-11-10 22:11:34 -0500245 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700246 device_info: A DeviceInfo named tuple.
247 """
248 effective_free = device_info.target_dir_size + device_info.target_fs_free
249 staging_size = self._GetStagingDirSize()
250 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700251 raise DeployFailure(
252 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400253 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700254 if device_info.target_fs_free < (100 * 1024):
255 logging.warning('The device has less than 100MB free. deploy_chrome may '
256 'hang during the transfer.')
257
Satoru Takabayashif2893002017-06-20 14:52:48 +0900258 def _ShouldUseCompression(self):
259 """Checks if compression should be used for rsync."""
260 if self.options.compress == 'always':
261 return True
262 elif self.options.compress == 'never':
263 return False
264 elif self.options.compress == 'auto':
265 return not self.device.HasGigabitEthernet()
266
Ryan Cui3045c5d2012-07-13 18:00:33 -0700267 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800268 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700269 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
270 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900271 if not self.device.HasRsync():
272 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700273 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500274 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500275 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
276 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700277 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900278 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700279 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500280 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700281
282 # Set the security context on the default Chrome dir if that's where it's
283 # getting deployed, and only on SELinux supported devices.
284 if (self.device.IsSELinuxAvailable() and
285 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Miah Sanchez769bb1b2019-07-26 13:02:00 -0700286 self.device.RunCommand(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800287
288 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800289 if p.mode:
290 # Set mode if necessary.
David Haddock3151d912017-10-24 03:50:32 +0000291 self.device.RunCommand('chmod %o %s/%s' % (
292 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800293
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700294 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
295 # pick up major changes (bus type, logging, etc.), but all we care about is
296 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
297 # be authorized to take ownership of its service names.
David Haddock3151d912017-10-24 03:50:32 +0000298 self.device.RunCommand(DBUS_RELOAD_COMMAND, error_code_ok=True)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600299
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800300 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800301 logging.info('Starting UI...')
David Haddock3151d912017-10-24 03:50:32 +0000302 self.device.RunCommand('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700303
David James88e6f032013-03-02 08:13:20 -0800304 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700305 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800306 logging.info('Testing connection to the device...')
David Haddock3151d912017-10-24 03:50:32 +0000307 self.device.RunCommand('true')
David James88e6f032013-03-02 08:13:20 -0800308 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700309 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800310 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700311
Steve Funge984a532013-11-25 17:09:25 -0800312 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700313 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700314 def BinaryExists(filename):
315 """Checks if the passed-in file is present in the build directory."""
316 return os.path.exists(os.path.join(self.options.build_dir, filename))
317
Daniel Erat9813f0e2014-11-12 11:00:28 -0700318 # Handle non-Chrome deployments.
319 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700320 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700321 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
322
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700323 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700324 # app_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700325 self.options.startui = False
326
David James88e6f032013-03-02 08:13:20 -0800327 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800328 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
329 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800330
Thiago Goncales12793312013-05-23 11:26:17 -0700331 def _MountTarget(self):
332 logging.info('Mounting Chrome...')
333
Anushruth8d797672019-10-17 12:22:31 -0700334 # Create directory if does not exist.
335 self.device.RunCommand(_MKDIR_P_CMD % self.options.mount_dir)
336 try:
337 # Umount the existing mount on mount_dir if present first.
338 self.device.RunCommand(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
339 {'dir': self.options.mount_dir})
340 except cros_build_lib.RunCommandError as e:
341 logging.error('Failed to umount %s', self.options.mount_dir)
342 # If there is a failure, check if some processs is using the mount_dir.
343 result = self.device.RunCommand(LSOF_COMMAND % (self.options.mount_dir,),
344 check=False, capture_output=True,
345 encoding='utf-8')
346 logging.error('lsof %s -->', self.options.mount_dir)
347 logging.error(result.stdout)
348 raise e
349
David Haddock3151d912017-10-24 03:50:32 +0000350 self.device.RunCommand(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
351 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700352
Thiago Goncales12793312013-05-23 11:26:17 -0700353 # Chrome needs partition to have exec and suid flags set
David Haddock3151d912017-10-24 03:50:32 +0000354 self.device.RunCommand(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500355
356 def Cleanup(self):
357 """Clean up RemoteDevice."""
358 if not self.options.staging_only:
359 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700360
David James88e6f032013-03-02 08:13:20 -0800361 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800362 self._CheckDeployType()
363
David James88e6f032013-03-02 08:13:20 -0800364 # If requested, just do the staging step.
365 if self.options.staging_only:
366 self._PrepareStagingDir()
367 return 0
368
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800369 # Ensure that the target directory exists before running parallel steps.
370 self._EnsureTargetDir()
371
David James88e6f032013-03-02 08:13:20 -0800372 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800373 # stop printing output at that point, and halt any running steps.
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800374 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800375 steps = [self._GetDeviceInfo, self._CheckConnection,
376 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
377 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700378 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
379 return_values=True)
380 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800381
Steven Bennettsca73efa2018-07-10 13:36:56 -0700382 # If the root dir is not writable, try disabling rootfs verification.
383 # (We always do this by default so that developers can write to
384 # /etc/chriome_dev.conf and other directories in the rootfs).
385 if self._root_dir_is_still_readonly.is_set():
386 if self.options.noremove_rootfs_verification:
387 logging.warning('Skipping disable rootfs verification.')
388 elif not self._DisableRootfsVerification():
389 logging.warning('Failed to disable rootfs verification.')
390
391 # If the target dir is still not writable (i.e. the user opted out or the
392 # command failed), abort.
393 if not self.device.IsDirWritable(self.options.target_dir):
394 if self.options.startui:
395 logging.info('Restarting Chrome...')
396 self.device.RunCommand('start ui')
397 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800398
Thiago Goncales12793312013-05-23 11:26:17 -0700399 if self.options.mount_dir is not None:
400 self._MountTarget()
401
David James88e6f032013-03-02 08:13:20 -0800402 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700403 self._Deploy()
404
405
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700406def ValidateStagingFlags(value):
407 """Convert formatted string to dictionary."""
408 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700409
410
Steven Bennetts368c3e52016-09-23 13:05:21 -0700411def ValidateGnArgs(value):
412 """Convert GN_ARGS-formatted string to dictionary."""
413 return gn_helpers.FromGNArgs(value)
414
415
Ryan Cuie535b172012-10-19 18:25:03 -0700416def _CreateParser():
417 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400418 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700419
Ryan Cuia56a71e2012-10-18 18:40:35 -0700420 # TODO(rcui): Have this use the UI-V2 format of having source and target
421 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400422 parser.add_argument('--force', action='store_true', default=False,
423 help='Skip all prompts (i.e., for disabling of rootfs '
424 'verification). This may result in the target '
425 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800426 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400427 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400428 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400429 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400430 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400431 parser.add_argument('--build-dir', type='path',
432 help='The directory with Chrome build artifacts to '
433 'deploy from. Typically of format '
434 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700435 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400436 parser.add_argument('--target-dir', type='path',
437 default=None,
438 help='Target directory on device to deploy Chrome into.')
439 parser.add_argument('-g', '--gs-path', type='gs_path',
440 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500441 parser.add_argument('--private-key', type='path', default=None,
442 help='An ssh private key to use when deploying to '
443 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400444 parser.add_argument('--nostartui', action='store_false', dest='startui',
445 default=True,
446 help="Don't restart the ui daemon after deployment.")
447 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
448 default=True,
449 help="Don't strip binaries during deployment. Warning: "
450 'the resulting binaries will be very large!')
451 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
452 help='Port of the target device to connect to.')
453 parser.add_argument('-t', '--to',
454 help='The IP address of the CrOS device to deploy to.')
455 parser.add_argument('-v', '--verbose', action='store_true', default=False,
456 help='Show more debug output.')
457 parser.add_argument('--mount-dir', type='path', default=None,
458 help='Deploy Chrome in target directory and bind it '
459 'to the directory specified by this flag.'
460 'Any existing mount on this directory will be '
461 'umounted first.')
462 parser.add_argument('--mount', action='store_true', default=False,
463 help='Deploy Chrome to default target directory and bind '
464 'it to the default mount directory.'
465 'Any existing mount on this directory will be '
466 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700467 parser.add_argument('--noremove-rootfs-verification', action='store_true',
468 default=False, help='Never remove rootfs verification.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700469
Mike Frysingerc3061a62015-06-04 04:16:18 -0400470 group = parser.add_argument_group('Advanced Options')
471 group.add_argument('-l', '--local-pkg-path', type='path',
472 help='Path to local chrome prebuilt package to deploy.')
473 group.add_argument('--sloppy', action='store_true', default=False,
474 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700475 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400476 help=('Extra flags to control staging. Valid flags are - '
477 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700478 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400479 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700480 help='Deprecated. Default behavior is "strict". Use '
481 '--sloppy to omit warnings for missing optional '
482 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400483 group.add_argument('--strip-flags', default=None,
484 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400485 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400486 group.add_argument('--ping', action='store_true', default=False,
487 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700488 group.add_argument('--process-timeout', type=int,
489 default=KILL_PROC_MAX_WAIT,
490 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700491
Mike Frysingerc3061a62015-06-04 04:16:18 -0400492 group = parser.add_argument_group(
493 'Metadata Overrides (Advanced)',
494 description='Provide all of these overrides in order to remove '
495 'dependencies on metadata.json existence.')
496 group.add_argument('--target-tc', action='store', default=None,
497 help='Override target toolchain name, e.g. '
498 'x86_64-cros-linux-gnu')
499 group.add_argument('--toolchain-url', action='store', default=None,
500 help='Override toolchain url format pattern, e.g. '
501 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700502
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700503 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
504 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
505 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400506 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700507
508 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
509 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
510 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
511 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
512 help=argparse.SUPPRESS)
513
Ryan Cuia56a71e2012-10-18 18:40:35 -0700514 # Path of an empty directory to stage chrome artifacts to. Defaults to a
515 # temporary directory that is removed when the script finishes. If the path
516 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400517 parser.add_argument('--staging-dir', type='path', default=None,
518 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700519 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400520 parser.add_argument('--staging-only', action='store_true', default=False,
521 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700522 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
523 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
524 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400525 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900526 parser.add_argument('--compress', action='store', default='auto',
527 choices=('always', 'never', 'auto'),
528 help='Choose the data compression behavior. Default '
529 'is set to "auto", that disables compression if '
530 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700531 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700532
Ryan Cuie535b172012-10-19 18:25:03 -0700533
534def _ParseCommandLine(argv):
535 """Parse args, and run environment-independent checks."""
536 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400537 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700538
Ryan Cuia56a71e2012-10-18 18:40:35 -0700539 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
540 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800541 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700542 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
543 parser.error('Cannot specify both --build_dir and '
544 '--gs-path/--local-pkg-patch')
Achuith Bhandarkar31a3eb02018-03-22 16:33:48 -0700545 if (not options.board and options.build_dir and options.dostrip and
546 not options.strip_bin):
547 parser.error('--board is required for stripping.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700548 if options.gs_path and options.local_pkg_path:
549 parser.error('Cannot specify both --gs-path and --local-pkg-path')
550 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700551 parser.error('Need to specify --to')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700552 if options.staging_flags and not options.build_dir:
553 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700554
Steven Bennetts46a84c32016-08-12 15:20:46 -0700555 if options.strict:
556 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700557 if options.gyp_defines:
558 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700559
560 if options.mount or options.mount_dir:
561 if not options.target_dir:
562 options.target_dir = _CHROME_DIR_MOUNT
563 else:
564 if not options.target_dir:
565 options.target_dir = _CHROME_DIR
566
567 if options.mount and not options.mount_dir:
568 options.mount_dir = _CHROME_DIR
569
Mike Frysingerc3061a62015-06-04 04:16:18 -0400570 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700571
572
Mike Frysingerc3061a62015-06-04 04:16:18 -0400573def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800574 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700575
576 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400577 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700578 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700579 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
580 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
581
Steven Bennetts368c3e52016-09-23 13:05:21 -0700582 if not options.gn_args:
583 gn_env = os.getenv('GN_ARGS')
584 if gn_env is not None:
585 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800586 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700587
Steven Bennetts60600462016-05-12 10:40:20 -0700588 if not options.staging_flags:
589 use_env = os.getenv('USE')
590 if use_env is not None:
591 options.staging_flags = ' '.join(set(use_env.split()).intersection(
592 chrome_util.STAGING_FLAGS))
593 logging.info('Staging flags taken from USE in environment: %s',
594 options.staging_flags)
595
Ryan Cuia56a71e2012-10-18 18:40:35 -0700596
Ryan Cui504db722013-01-22 11:48:01 -0800597def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700598 """Get the chrome prebuilt tarball from GS.
599
Mike Frysinger02e1e072013-11-10 22:11:34 -0500600 Returns:
601 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700602 """
David James9374aac2013-10-08 16:00:17 -0700603 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500604 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800605 files = [found for found in files if
606 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
607 if not files:
608 raise Exception('No chrome package found at %s' % gs_path)
609 elif len(files) > 1:
610 # - Users should provide us with a direct link to either a stripped or
611 # unstripped chrome package.
612 # - In the case of being provided with an archive directory, where both
613 # stripped and unstripped chrome available, use the stripped chrome
614 # package.
615 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
616 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
617 files = [f for f in files if not 'unstripped' in f]
618 assert len(files) == 1
619 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800620
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800621 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800622 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800623 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
624 chrome_path = os.path.join(tempdir, filename)
625 assert os.path.exists(chrome_path)
626 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700627
628
Ryan Cuif890a3e2013-03-07 18:57:06 -0800629@contextlib.contextmanager
630def _StripBinContext(options):
631 if not options.dostrip:
632 yield None
633 elif options.strip_bin:
634 yield options.strip_bin
635 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800636 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800637 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700638 with sdk.Prepare(components=components, target_tc=options.target_tc,
639 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800640 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
641 constants.CHROME_ENV_FILE)
642 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
643 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
644 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800645 yield strip_bin
646
647
Steve Funge984a532013-11-25 17:09:25 -0800648def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
649 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800650 """Place the necessary files in the staging directory.
651
652 The staging directory is the directory used to rsync the build artifacts over
653 to the device. Only the necessary Chrome build artifacts are put into the
654 staging directory.
655 """
Ryan Cui5866be02013-03-18 14:12:00 -0700656 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400657 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800658 if options.build_dir:
659 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700660 strip_flags = (None if options.strip_flags is None else
661 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800662 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700663 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700664 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700665 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800666 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700667 else:
668 pkg_path = options.local_pkg_path
669 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800670 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
671 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700672
673 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800674 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700675 # Extract only the ./opt/google/chrome contents, directly into the staging
676 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800677 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400678 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800679 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
680 staging_dir])
681 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
682 shutil.move(filename, staging_dir)
683 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
684 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400685 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800686 ['tar', '--strip-components', '4', '--extract',
687 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
688 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700689
Ryan Cui71aa8de2013-04-19 16:12:55 -0700690
Ryan Cui3045c5d2012-07-13 18:00:33 -0700691def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400692 options = _ParseCommandLine(argv)
693 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700694
695 # Set cros_build_lib debug level to hide RunCommand spew.
696 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700697 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700698 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800699 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700700
Aviv Keshet01a82e92017-03-02 17:39:59 -0800701 with osutils.TempDir(set_global=True) as tempdir:
702 staging_dir = options.staging_dir
703 if not staging_dir:
704 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700705
Aviv Keshet01a82e92017-03-02 17:39:59 -0800706 deploy = DeployChrome(options, tempdir, staging_dir)
707 try:
708 deploy.Perform()
709 except failures_lib.StepFailure as ex:
710 raise SystemExit(str(ex).strip())
711 deploy.Cleanup()