blob: 4336f932f26677350bd1d237c922a88f5e1f8607 [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,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400123 capture_output=True, encoding='utf-8')
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],
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400128 redirect_stdout=True, capture_output=True,
129 encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700130 return int(result.output.split()[0])
131
Ryan Cui3045c5d2012-07-13 18:00:33 -0700132 def _ChromeFileInUse(self):
Anushruth8d797672019-10-17 12:22:31 -0700133 result = self.device.RunCommand(LSOF_COMMAND_CHROME %
134 (self.options.target_dir,),
David Haddock3151d912017-10-24 03:50:32 +0000135 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700136 return result.returncode == 0
137
Justin TerAvestfac210e2017-04-13 11:39:00 -0600138 def _Reboot(self):
139 # A reboot in developer mode takes a while (and has delays), so the user
140 # will have time to read and act on the USB boot instructions below.
141 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
142 self.device.Reboot()
143
Ryan Cui3045c5d2012-07-13 18:00:33 -0700144 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.')
Ben Chan37d64e52018-10-05 15:35:02 -0700151 if not cros_build_lib.BooleanPrompt('Remove rootfs verification?', False):
Steven Bennettsca73efa2018-07-10 13:36:56 -0700152 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700153
154 logging.info('Removing rootfs verification from %s', self.options.to)
155 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
156 # Use --force to bypass the checks.
David Haddock3151d912017-10-24 03:50:32 +0000157 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
158 '--remove_rootfs_verification --force')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700159 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
David Haddock3151d912017-10-24 03:50:32 +0000160 self.device.RunCommand(cmd % partition, error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700161
Justin TerAvestfac210e2017-04-13 11:39:00 -0600162 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700163
David James88e6f032013-03-02 08:13:20 -0800164 # Now that the machine has been rebooted, we need to kill Chrome again.
165 self._KillProcsIfNeeded()
166
167 # Make sure the rootfs is writable now.
168 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700169
Steven Bennettsca73efa2018-07-10 13:36:56 -0700170 return True
171
Ryan Cui3045c5d2012-07-13 18:00:33 -0700172 def _CheckUiJobStarted(self):
173 # status output is in the format:
174 # <job_name> <status> ['process' <pid>].
175 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800176 try:
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400177 result = self.device.RunCommand('status ui', capture_output=True,
178 encoding='utf-8')
Ryan Cuif2d1a582013-02-19 14:08:13 -0800179 except cros_build_lib.RunCommandError as e:
180 if 'Unknown job' in e.result.error:
181 return False
182 else:
183 raise e
184
Ryan Cui3045c5d2012-07-13 18:00:33 -0700185 return result.output.split()[1].split('/')[0] == 'start'
186
187 def _KillProcsIfNeeded(self):
188 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800189 logging.info('Shutting down Chrome...')
David Haddock3151d912017-10-24 03:50:32 +0000190 self.device.RunCommand('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700191
192 # Developers sometimes run session_manager manually, in which case we'll
193 # need to help shut the chrome processes down.
194 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700195 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700196 while self._ChromeFileInUse():
197 logging.warning('The chrome binary on the device is in use.')
198 logging.warning('Killing chrome and session_manager processes...\n')
199
David Haddock3151d912017-10-24 03:50:32 +0000200 self.device.RunCommand("pkill 'chrome|session_manager'",
Mike Frysingere65f3752014-12-08 00:46:39 -0500201 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700202 # Wait for processes to actually terminate
203 time.sleep(POST_KILL_WAIT)
204 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800205 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800206 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700207 'running chrome processes and try again.'
208 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800209 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700210
David James88e6f032013-03-02 08:13:20 -0800211 def _MountRootfsAsWritable(self, error_code_ok=True):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800212 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700213
Steven Bennettsca73efa2018-07-10 13:36:56 -0700214 If the command fails and the root dir is not writable then this function
215 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700216
Mike Frysinger02e1e072013-11-10 22:11:34 -0500217 Args:
David James88e6f032013-03-02 08:13:20 -0800218 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
219 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800220 # TODO: Should migrate to use the remount functions in remote_access.
Robert Flack1dc7ea82014-11-26 13:50:24 -0500221 result = self.device.RunCommand(MOUNT_RW_COMMAND,
222 error_code_ok=error_code_ok,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400223 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700224 if result.returncode and not self.device.IsDirWritable('/'):
225 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700226
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800227 def _EnsureTargetDir(self):
228 """Ensures that the target directory exists on the remote device."""
229 target_dir = self.options.target_dir
230 # Any valid /opt directory should already exist so avoid the remote call.
231 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
232 return
233 self.device.RunCommand(['mkdir', '-p', '--mode', '0775', target_dir])
234
Ryan Cui7193a7e2013-04-26 14:15:19 -0700235 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800236 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700237 steps = [
238 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
239 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
240 ]
241 return_values = parallel.RunParallelSteps(steps, return_values=True)
242 return DeviceInfo(*return_values)
243
244 def _CheckDeviceFreeSpace(self, device_info):
245 """See if target device has enough space for Chrome.
246
Mike Frysinger02e1e072013-11-10 22:11:34 -0500247 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700248 device_info: A DeviceInfo named tuple.
249 """
250 effective_free = device_info.target_dir_size + device_info.target_fs_free
251 staging_size = self._GetStagingDirSize()
252 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700253 raise DeployFailure(
254 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400255 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700256 if device_info.target_fs_free < (100 * 1024):
257 logging.warning('The device has less than 100MB free. deploy_chrome may '
258 'hang during the transfer.')
259
Satoru Takabayashif2893002017-06-20 14:52:48 +0900260 def _ShouldUseCompression(self):
261 """Checks if compression should be used for rsync."""
262 if self.options.compress == 'always':
263 return True
264 elif self.options.compress == 'never':
265 return False
266 elif self.options.compress == 'auto':
267 return not self.device.HasGigabitEthernet()
268
Ryan Cui3045c5d2012-07-13 18:00:33 -0700269 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800270 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700271 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
272 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900273 if not self.device.HasRsync():
274 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700275 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500276 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500277 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
278 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700279 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900280 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700281 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500282 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700283
284 # Set the security context on the default Chrome dir if that's where it's
285 # getting deployed, and only on SELinux supported devices.
286 if (self.device.IsSELinuxAvailable() and
287 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Miah Sanchez769bb1b2019-07-26 13:02:00 -0700288 self.device.RunCommand(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800289
290 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800291 if p.mode:
292 # Set mode if necessary.
David Haddock3151d912017-10-24 03:50:32 +0000293 self.device.RunCommand('chmod %o %s/%s' % (
294 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800295
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700296 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
297 # pick up major changes (bus type, logging, etc.), but all we care about is
298 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
299 # be authorized to take ownership of its service names.
David Haddock3151d912017-10-24 03:50:32 +0000300 self.device.RunCommand(DBUS_RELOAD_COMMAND, error_code_ok=True)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600301
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800302 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800303 logging.info('Starting UI...')
David Haddock3151d912017-10-24 03:50:32 +0000304 self.device.RunCommand('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700305
David James88e6f032013-03-02 08:13:20 -0800306 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700307 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800308 logging.info('Testing connection to the device...')
David Haddock3151d912017-10-24 03:50:32 +0000309 self.device.RunCommand('true')
David James88e6f032013-03-02 08:13:20 -0800310 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700311 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800312 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700313
Steve Funge984a532013-11-25 17:09:25 -0800314 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700315 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700316 def BinaryExists(filename):
317 """Checks if the passed-in file is present in the build directory."""
318 return os.path.exists(os.path.join(self.options.build_dir, filename))
319
Daniel Erat9813f0e2014-11-12 11:00:28 -0700320 # Handle non-Chrome deployments.
321 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700322 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700323 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
324
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700325 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700326 # app_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700327 self.options.startui = False
328
David James88e6f032013-03-02 08:13:20 -0800329 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800330 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
331 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800332
Thiago Goncales12793312013-05-23 11:26:17 -0700333 def _MountTarget(self):
334 logging.info('Mounting Chrome...')
335
Anushruth8d797672019-10-17 12:22:31 -0700336 # Create directory if does not exist.
337 self.device.RunCommand(_MKDIR_P_CMD % self.options.mount_dir)
338 try:
339 # Umount the existing mount on mount_dir if present first.
340 self.device.RunCommand(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
341 {'dir': self.options.mount_dir})
342 except cros_build_lib.RunCommandError as e:
343 logging.error('Failed to umount %s', self.options.mount_dir)
344 # If there is a failure, check if some processs is using the mount_dir.
345 result = self.device.RunCommand(LSOF_COMMAND % (self.options.mount_dir,),
346 check=False, capture_output=True,
347 encoding='utf-8')
348 logging.error('lsof %s -->', self.options.mount_dir)
349 logging.error(result.stdout)
350 raise e
351
David Haddock3151d912017-10-24 03:50:32 +0000352 self.device.RunCommand(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
353 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700354
Thiago Goncales12793312013-05-23 11:26:17 -0700355 # Chrome needs partition to have exec and suid flags set
David Haddock3151d912017-10-24 03:50:32 +0000356 self.device.RunCommand(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500357
358 def Cleanup(self):
359 """Clean up RemoteDevice."""
360 if not self.options.staging_only:
361 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700362
David James88e6f032013-03-02 08:13:20 -0800363 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800364 self._CheckDeployType()
365
David James88e6f032013-03-02 08:13:20 -0800366 # If requested, just do the staging step.
367 if self.options.staging_only:
368 self._PrepareStagingDir()
369 return 0
370
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800371 # Ensure that the target directory exists before running parallel steps.
372 self._EnsureTargetDir()
373
David James88e6f032013-03-02 08:13:20 -0800374 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800375 # stop printing output at that point, and halt any running steps.
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800376 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800377 steps = [self._GetDeviceInfo, self._CheckConnection,
378 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
379 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700380 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
381 return_values=True)
382 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800383
Steven Bennettsca73efa2018-07-10 13:36:56 -0700384 # If the root dir is not writable, try disabling rootfs verification.
385 # (We always do this by default so that developers can write to
386 # /etc/chriome_dev.conf and other directories in the rootfs).
387 if self._root_dir_is_still_readonly.is_set():
388 if self.options.noremove_rootfs_verification:
389 logging.warning('Skipping disable rootfs verification.')
390 elif not self._DisableRootfsVerification():
391 logging.warning('Failed to disable rootfs verification.')
392
393 # If the target dir is still not writable (i.e. the user opted out or the
394 # command failed), abort.
395 if not self.device.IsDirWritable(self.options.target_dir):
396 if self.options.startui:
397 logging.info('Restarting Chrome...')
398 self.device.RunCommand('start ui')
399 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800400
Thiago Goncales12793312013-05-23 11:26:17 -0700401 if self.options.mount_dir is not None:
402 self._MountTarget()
403
David James88e6f032013-03-02 08:13:20 -0800404 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700405 self._Deploy()
406
407
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700408def ValidateStagingFlags(value):
409 """Convert formatted string to dictionary."""
410 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700411
412
Steven Bennetts368c3e52016-09-23 13:05:21 -0700413def ValidateGnArgs(value):
414 """Convert GN_ARGS-formatted string to dictionary."""
415 return gn_helpers.FromGNArgs(value)
416
417
Ryan Cuie535b172012-10-19 18:25:03 -0700418def _CreateParser():
419 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400420 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700421
Ryan Cuia56a71e2012-10-18 18:40:35 -0700422 # TODO(rcui): Have this use the UI-V2 format of having source and target
423 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400424 parser.add_argument('--force', action='store_true', default=False,
425 help='Skip all prompts (i.e., for disabling of rootfs '
426 'verification). This may result in the target '
427 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800428 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400429 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400430 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400431 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400432 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400433 parser.add_argument('--build-dir', type='path',
434 help='The directory with Chrome build artifacts to '
435 'deploy from. Typically of format '
436 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700437 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400438 parser.add_argument('--target-dir', type='path',
439 default=None,
440 help='Target directory on device to deploy Chrome into.')
441 parser.add_argument('-g', '--gs-path', type='gs_path',
442 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500443 parser.add_argument('--private-key', type='path', default=None,
444 help='An ssh private key to use when deploying to '
445 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400446 parser.add_argument('--nostartui', action='store_false', dest='startui',
447 default=True,
448 help="Don't restart the ui daemon after deployment.")
449 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
450 default=True,
451 help="Don't strip binaries during deployment. Warning: "
452 'the resulting binaries will be very large!')
453 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
454 help='Port of the target device to connect to.')
455 parser.add_argument('-t', '--to',
456 help='The IP address of the CrOS device to deploy to.')
457 parser.add_argument('-v', '--verbose', action='store_true', default=False,
458 help='Show more debug output.')
459 parser.add_argument('--mount-dir', type='path', default=None,
460 help='Deploy Chrome in target directory and bind it '
461 'to the directory specified by this flag.'
462 'Any existing mount on this directory will be '
463 'umounted first.')
464 parser.add_argument('--mount', action='store_true', default=False,
465 help='Deploy Chrome to default target directory and bind '
466 'it to the default mount directory.'
467 'Any existing mount on this directory will be '
468 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700469 parser.add_argument('--noremove-rootfs-verification', action='store_true',
470 default=False, help='Never remove rootfs verification.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700471
Mike Frysingerc3061a62015-06-04 04:16:18 -0400472 group = parser.add_argument_group('Advanced Options')
473 group.add_argument('-l', '--local-pkg-path', type='path',
474 help='Path to local chrome prebuilt package to deploy.')
475 group.add_argument('--sloppy', action='store_true', default=False,
476 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700477 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400478 help=('Extra flags to control staging. Valid flags are - '
479 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700480 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400481 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700482 help='Deprecated. Default behavior is "strict". Use '
483 '--sloppy to omit warnings for missing optional '
484 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400485 group.add_argument('--strip-flags', default=None,
486 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400487 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400488 group.add_argument('--ping', action='store_true', default=False,
489 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700490 group.add_argument('--process-timeout', type=int,
491 default=KILL_PROC_MAX_WAIT,
492 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700493
Mike Frysingerc3061a62015-06-04 04:16:18 -0400494 group = parser.add_argument_group(
495 'Metadata Overrides (Advanced)',
496 description='Provide all of these overrides in order to remove '
497 'dependencies on metadata.json existence.')
498 group.add_argument('--target-tc', action='store', default=None,
499 help='Override target toolchain name, e.g. '
500 'x86_64-cros-linux-gnu')
501 group.add_argument('--toolchain-url', action='store', default=None,
502 help='Override toolchain url format pattern, e.g. '
503 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700504
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700505 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
506 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
507 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400508 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700509
510 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
511 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
512 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
513 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
514 help=argparse.SUPPRESS)
515
Ryan Cuia56a71e2012-10-18 18:40:35 -0700516 # Path of an empty directory to stage chrome artifacts to. Defaults to a
517 # temporary directory that is removed when the script finishes. If the path
518 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400519 parser.add_argument('--staging-dir', type='path', default=None,
520 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700521 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400522 parser.add_argument('--staging-only', action='store_true', default=False,
523 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700524 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
525 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
526 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400527 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900528 parser.add_argument('--compress', action='store', default='auto',
529 choices=('always', 'never', 'auto'),
530 help='Choose the data compression behavior. Default '
531 'is set to "auto", that disables compression if '
532 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700533 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700534
Ryan Cuie535b172012-10-19 18:25:03 -0700535
536def _ParseCommandLine(argv):
537 """Parse args, and run environment-independent checks."""
538 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400539 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700540
Ryan Cuia56a71e2012-10-18 18:40:35 -0700541 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
542 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800543 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700544 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
545 parser.error('Cannot specify both --build_dir and '
546 '--gs-path/--local-pkg-patch')
Achuith Bhandarkar31a3eb02018-03-22 16:33:48 -0700547 if (not options.board and options.build_dir and options.dostrip and
548 not options.strip_bin):
549 parser.error('--board is required for stripping.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700550 if options.gs_path and options.local_pkg_path:
551 parser.error('Cannot specify both --gs-path and --local-pkg-path')
552 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700553 parser.error('Need to specify --to')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700554 if options.staging_flags and not options.build_dir:
555 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700556
Steven Bennetts46a84c32016-08-12 15:20:46 -0700557 if options.strict:
558 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700559 if options.gyp_defines:
560 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700561
562 if options.mount or options.mount_dir:
563 if not options.target_dir:
564 options.target_dir = _CHROME_DIR_MOUNT
565 else:
566 if not options.target_dir:
567 options.target_dir = _CHROME_DIR
568
569 if options.mount and not options.mount_dir:
570 options.mount_dir = _CHROME_DIR
571
Mike Frysingerc3061a62015-06-04 04:16:18 -0400572 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700573
574
Mike Frysingerc3061a62015-06-04 04:16:18 -0400575def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800576 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700577
578 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400579 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700580 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700581 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
582 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
583
Steven Bennetts368c3e52016-09-23 13:05:21 -0700584 if not options.gn_args:
585 gn_env = os.getenv('GN_ARGS')
586 if gn_env is not None:
587 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800588 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700589
Steven Bennetts60600462016-05-12 10:40:20 -0700590 if not options.staging_flags:
591 use_env = os.getenv('USE')
592 if use_env is not None:
593 options.staging_flags = ' '.join(set(use_env.split()).intersection(
594 chrome_util.STAGING_FLAGS))
595 logging.info('Staging flags taken from USE in environment: %s',
596 options.staging_flags)
597
Ryan Cuia56a71e2012-10-18 18:40:35 -0700598
Ryan Cui504db722013-01-22 11:48:01 -0800599def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700600 """Get the chrome prebuilt tarball from GS.
601
Mike Frysinger02e1e072013-11-10 22:11:34 -0500602 Returns:
603 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700604 """
David James9374aac2013-10-08 16:00:17 -0700605 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500606 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800607 files = [found for found in files if
608 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
609 if not files:
610 raise Exception('No chrome package found at %s' % gs_path)
611 elif len(files) > 1:
612 # - Users should provide us with a direct link to either a stripped or
613 # unstripped chrome package.
614 # - In the case of being provided with an archive directory, where both
615 # stripped and unstripped chrome available, use the stripped chrome
616 # package.
617 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
618 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
619 files = [f for f in files if not 'unstripped' in f]
620 assert len(files) == 1
621 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800622
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800623 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800624 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800625 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
626 chrome_path = os.path.join(tempdir, filename)
627 assert os.path.exists(chrome_path)
628 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700629
630
Ryan Cuif890a3e2013-03-07 18:57:06 -0800631@contextlib.contextmanager
632def _StripBinContext(options):
633 if not options.dostrip:
634 yield None
635 elif options.strip_bin:
636 yield options.strip_bin
637 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800638 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800639 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700640 with sdk.Prepare(components=components, target_tc=options.target_tc,
641 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800642 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
643 constants.CHROME_ENV_FILE)
644 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
645 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
646 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800647 yield strip_bin
648
649
Steve Funge984a532013-11-25 17:09:25 -0800650def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
651 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800652 """Place the necessary files in the staging directory.
653
654 The staging directory is the directory used to rsync the build artifacts over
655 to the device. Only the necessary Chrome build artifacts are put into the
656 staging directory.
657 """
Ryan Cui5866be02013-03-18 14:12:00 -0700658 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400659 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800660 if options.build_dir:
661 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700662 strip_flags = (None if options.strip_flags is None else
663 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800664 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700665 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700666 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700667 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800668 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700669 else:
670 pkg_path = options.local_pkg_path
671 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800672 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
673 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700674
675 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800676 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700677 # Extract only the ./opt/google/chrome contents, directly into the staging
678 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800679 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400680 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800681 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
682 staging_dir])
683 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
684 shutil.move(filename, staging_dir)
685 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
686 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400687 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800688 ['tar', '--strip-components', '4', '--extract',
689 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
690 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700691
Ryan Cui71aa8de2013-04-19 16:12:55 -0700692
Ryan Cui3045c5d2012-07-13 18:00:33 -0700693def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400694 options = _ParseCommandLine(argv)
695 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700696
697 # Set cros_build_lib debug level to hide RunCommand spew.
698 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700699 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700700 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800701 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700702
Aviv Keshet01a82e92017-03-02 17:39:59 -0800703 with osutils.TempDir(set_global=True) as tempdir:
704 staging_dir = options.staging_dir
705 if not staging_dir:
706 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700707
Aviv Keshet01a82e92017-03-02 17:39:59 -0800708 deploy = DeployChrome(options, tempdir, staging_dir)
709 try:
710 deploy.Perform()
711 except failures_lib.StepFailure as ex:
712 raise SystemExit(str(ex).strip())
713 deploy.Cleanup()