blob: f82815c082d7609a3a6d209a8dbceca35f51f4a6 [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,),
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500135 check=False, 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):
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500160 self.device.RunCommand(cmd % partition, check=False)
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.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500168 self._MountRootfsAsWritable(check=True)
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 Frysingerf5a3b2d2019-12-12 14:36:17 -0500201 check=False)
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
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500211 def _MountRootfsAsWritable(self, check=False):
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:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500218 check: See remote.RemoteAccess.RemoteSh for details.
David James88e6f032013-03-02 08:13:20 -0800219 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800220 # TODO: Should migrate to use the remount functions in remote_access.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500221 result = self.device.RunCommand(MOUNT_RW_COMMAND, check=check,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400222 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700223 if result.returncode and not self.device.IsDirWritable('/'):
224 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700225
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800226 def _EnsureTargetDir(self):
227 """Ensures that the target directory exists on the remote device."""
228 target_dir = self.options.target_dir
229 # Any valid /opt directory should already exist so avoid the remote call.
230 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
231 return
232 self.device.RunCommand(['mkdir', '-p', '--mode', '0775', target_dir])
233
Ryan Cui7193a7e2013-04-26 14:15:19 -0700234 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800235 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700236 steps = [
237 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
238 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
239 ]
240 return_values = parallel.RunParallelSteps(steps, return_values=True)
241 return DeviceInfo(*return_values)
242
243 def _CheckDeviceFreeSpace(self, device_info):
244 """See if target device has enough space for Chrome.
245
Mike Frysinger02e1e072013-11-10 22:11:34 -0500246 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700247 device_info: A DeviceInfo named tuple.
248 """
249 effective_free = device_info.target_dir_size + device_info.target_fs_free
250 staging_size = self._GetStagingDirSize()
251 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700252 raise DeployFailure(
253 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400254 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700255 if device_info.target_fs_free < (100 * 1024):
256 logging.warning('The device has less than 100MB free. deploy_chrome may '
257 'hang during the transfer.')
258
Satoru Takabayashif2893002017-06-20 14:52:48 +0900259 def _ShouldUseCompression(self):
260 """Checks if compression should be used for rsync."""
261 if self.options.compress == 'always':
262 return True
263 elif self.options.compress == 'never':
264 return False
265 elif self.options.compress == 'auto':
266 return not self.device.HasGigabitEthernet()
267
Ryan Cui3045c5d2012-07-13 18:00:33 -0700268 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800269 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700270 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
271 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900272 if not self.device.HasRsync():
273 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700274 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500275 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500276 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
277 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700278 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900279 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700280 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500281 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700282
283 # Set the security context on the default Chrome dir if that's where it's
284 # getting deployed, and only on SELinux supported devices.
285 if (self.device.IsSELinuxAvailable() and
286 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Miah Sanchez769bb1b2019-07-26 13:02:00 -0700287 self.device.RunCommand(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800288
289 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800290 if p.mode:
291 # Set mode if necessary.
David Haddock3151d912017-10-24 03:50:32 +0000292 self.device.RunCommand('chmod %o %s/%s' % (
293 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800294
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700295 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
296 # pick up major changes (bus type, logging, etc.), but all we care about is
297 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
298 # be authorized to take ownership of its service names.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500299 self.device.RunCommand(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600300
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800301 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800302 logging.info('Starting UI...')
David Haddock3151d912017-10-24 03:50:32 +0000303 self.device.RunCommand('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700304
David James88e6f032013-03-02 08:13:20 -0800305 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700306 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800307 logging.info('Testing connection to the device...')
David Haddock3151d912017-10-24 03:50:32 +0000308 self.device.RunCommand('true')
David James88e6f032013-03-02 08:13:20 -0800309 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700310 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800311 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700312
Steve Funge984a532013-11-25 17:09:25 -0800313 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700314 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700315 def BinaryExists(filename):
316 """Checks if the passed-in file is present in the build directory."""
317 return os.path.exists(os.path.join(self.options.build_dir, filename))
318
Daniel Erat9813f0e2014-11-12 11:00:28 -0700319 # Handle non-Chrome deployments.
320 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700321 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700322 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
323
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700324 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700325 # app_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700326 self.options.startui = False
327
David James88e6f032013-03-02 08:13:20 -0800328 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800329 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
330 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800331
Thiago Goncales12793312013-05-23 11:26:17 -0700332 def _MountTarget(self):
333 logging.info('Mounting Chrome...')
334
Anushruth8d797672019-10-17 12:22:31 -0700335 # Create directory if does not exist.
336 self.device.RunCommand(_MKDIR_P_CMD % self.options.mount_dir)
337 try:
338 # Umount the existing mount on mount_dir if present first.
339 self.device.RunCommand(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
340 {'dir': self.options.mount_dir})
341 except cros_build_lib.RunCommandError as e:
342 logging.error('Failed to umount %s', self.options.mount_dir)
343 # If there is a failure, check if some processs is using the mount_dir.
344 result = self.device.RunCommand(LSOF_COMMAND % (self.options.mount_dir,),
345 check=False, capture_output=True,
346 encoding='utf-8')
347 logging.error('lsof %s -->', self.options.mount_dir)
348 logging.error(result.stdout)
349 raise e
350
David Haddock3151d912017-10-24 03:50:32 +0000351 self.device.RunCommand(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
352 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700353
Thiago Goncales12793312013-05-23 11:26:17 -0700354 # Chrome needs partition to have exec and suid flags set
David Haddock3151d912017-10-24 03:50:32 +0000355 self.device.RunCommand(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500356
357 def Cleanup(self):
358 """Clean up RemoteDevice."""
359 if not self.options.staging_only:
360 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700361
David James88e6f032013-03-02 08:13:20 -0800362 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800363 self._CheckDeployType()
364
David James88e6f032013-03-02 08:13:20 -0800365 # If requested, just do the staging step.
366 if self.options.staging_only:
367 self._PrepareStagingDir()
368 return 0
369
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800370 # Ensure that the target directory exists before running parallel steps.
371 self._EnsureTargetDir()
372
David James88e6f032013-03-02 08:13:20 -0800373 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800374 # stop printing output at that point, and halt any running steps.
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800375 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800376 steps = [self._GetDeviceInfo, self._CheckConnection,
377 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
378 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700379 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
380 return_values=True)
381 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800382
Steven Bennettsca73efa2018-07-10 13:36:56 -0700383 # If the root dir is not writable, try disabling rootfs verification.
384 # (We always do this by default so that developers can write to
385 # /etc/chriome_dev.conf and other directories in the rootfs).
386 if self._root_dir_is_still_readonly.is_set():
387 if self.options.noremove_rootfs_verification:
388 logging.warning('Skipping disable rootfs verification.')
389 elif not self._DisableRootfsVerification():
390 logging.warning('Failed to disable rootfs verification.')
391
392 # If the target dir is still not writable (i.e. the user opted out or the
393 # command failed), abort.
394 if not self.device.IsDirWritable(self.options.target_dir):
395 if self.options.startui:
396 logging.info('Restarting Chrome...')
397 self.device.RunCommand('start ui')
398 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800399
Thiago Goncales12793312013-05-23 11:26:17 -0700400 if self.options.mount_dir is not None:
401 self._MountTarget()
402
David James88e6f032013-03-02 08:13:20 -0800403 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700404 self._Deploy()
405
406
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700407def ValidateStagingFlags(value):
408 """Convert formatted string to dictionary."""
409 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700410
411
Steven Bennetts368c3e52016-09-23 13:05:21 -0700412def ValidateGnArgs(value):
413 """Convert GN_ARGS-formatted string to dictionary."""
414 return gn_helpers.FromGNArgs(value)
415
416
Ryan Cuie535b172012-10-19 18:25:03 -0700417def _CreateParser():
418 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400419 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700420
Ryan Cuia56a71e2012-10-18 18:40:35 -0700421 # TODO(rcui): Have this use the UI-V2 format of having source and target
422 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400423 parser.add_argument('--force', action='store_true', default=False,
424 help='Skip all prompts (i.e., for disabling of rootfs '
425 'verification). This may result in the target '
426 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800427 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400428 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400429 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400430 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400431 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400432 parser.add_argument('--build-dir', type='path',
433 help='The directory with Chrome build artifacts to '
434 'deploy from. Typically of format '
435 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700436 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400437 parser.add_argument('--target-dir', type='path',
438 default=None,
439 help='Target directory on device to deploy Chrome into.')
440 parser.add_argument('-g', '--gs-path', type='gs_path',
441 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500442 parser.add_argument('--private-key', type='path', default=None,
443 help='An ssh private key to use when deploying to '
444 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400445 parser.add_argument('--nostartui', action='store_false', dest='startui',
446 default=True,
447 help="Don't restart the ui daemon after deployment.")
448 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
449 default=True,
450 help="Don't strip binaries during deployment. Warning: "
451 'the resulting binaries will be very large!')
452 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
453 help='Port of the target device to connect to.')
454 parser.add_argument('-t', '--to',
455 help='The IP address of the CrOS device to deploy to.')
456 parser.add_argument('-v', '--verbose', action='store_true', default=False,
457 help='Show more debug output.')
458 parser.add_argument('--mount-dir', type='path', default=None,
459 help='Deploy Chrome in target directory and bind it '
460 'to the directory specified by this flag.'
461 'Any existing mount on this directory will be '
462 'umounted first.')
463 parser.add_argument('--mount', action='store_true', default=False,
464 help='Deploy Chrome to default target directory and bind '
465 'it to the default mount directory.'
466 'Any existing mount on this directory will be '
467 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700468 parser.add_argument('--noremove-rootfs-verification', action='store_true',
469 default=False, help='Never remove rootfs verification.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700470
Mike Frysingerc3061a62015-06-04 04:16:18 -0400471 group = parser.add_argument_group('Advanced Options')
472 group.add_argument('-l', '--local-pkg-path', type='path',
473 help='Path to local chrome prebuilt package to deploy.')
474 group.add_argument('--sloppy', action='store_true', default=False,
475 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700476 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400477 help=('Extra flags to control staging. Valid flags are - '
478 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700479 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400480 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700481 help='Deprecated. Default behavior is "strict". Use '
482 '--sloppy to omit warnings for missing optional '
483 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400484 group.add_argument('--strip-flags', default=None,
485 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400486 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400487 group.add_argument('--ping', action='store_true', default=False,
488 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700489 group.add_argument('--process-timeout', type=int,
490 default=KILL_PROC_MAX_WAIT,
491 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700492
Mike Frysingerc3061a62015-06-04 04:16:18 -0400493 group = parser.add_argument_group(
494 'Metadata Overrides (Advanced)',
495 description='Provide all of these overrides in order to remove '
496 'dependencies on metadata.json existence.')
497 group.add_argument('--target-tc', action='store', default=None,
498 help='Override target toolchain name, e.g. '
499 'x86_64-cros-linux-gnu')
500 group.add_argument('--toolchain-url', action='store', default=None,
501 help='Override toolchain url format pattern, e.g. '
502 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700503
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700504 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
505 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
506 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400507 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700508
509 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
510 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
511 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
512 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
513 help=argparse.SUPPRESS)
514
Ryan Cuia56a71e2012-10-18 18:40:35 -0700515 # Path of an empty directory to stage chrome artifacts to. Defaults to a
516 # temporary directory that is removed when the script finishes. If the path
517 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400518 parser.add_argument('--staging-dir', type='path', default=None,
519 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700520 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400521 parser.add_argument('--staging-only', action='store_true', default=False,
522 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700523 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
524 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
525 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400526 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900527 parser.add_argument('--compress', action='store', default='auto',
528 choices=('always', 'never', 'auto'),
529 help='Choose the data compression behavior. Default '
530 'is set to "auto", that disables compression if '
531 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700532 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700533
Ryan Cuie535b172012-10-19 18:25:03 -0700534
535def _ParseCommandLine(argv):
536 """Parse args, and run environment-independent checks."""
537 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400538 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700539
Ryan Cuia56a71e2012-10-18 18:40:35 -0700540 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
541 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800542 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700543 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
544 parser.error('Cannot specify both --build_dir and '
545 '--gs-path/--local-pkg-patch')
Achuith Bhandarkar31a3eb02018-03-22 16:33:48 -0700546 if (not options.board and options.build_dir and options.dostrip and
547 not options.strip_bin):
548 parser.error('--board is required for stripping.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700549 if options.gs_path and options.local_pkg_path:
550 parser.error('Cannot specify both --gs-path and --local-pkg-path')
551 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700552 parser.error('Need to specify --to')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700553 if options.staging_flags and not options.build_dir:
554 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700555
Steven Bennetts46a84c32016-08-12 15:20:46 -0700556 if options.strict:
557 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700558 if options.gyp_defines:
559 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700560
561 if options.mount or options.mount_dir:
562 if not options.target_dir:
563 options.target_dir = _CHROME_DIR_MOUNT
564 else:
565 if not options.target_dir:
566 options.target_dir = _CHROME_DIR
567
568 if options.mount and not options.mount_dir:
569 options.mount_dir = _CHROME_DIR
570
Mike Frysingerc3061a62015-06-04 04:16:18 -0400571 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700572
573
Mike Frysingerc3061a62015-06-04 04:16:18 -0400574def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800575 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700576
577 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400578 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700579 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700580 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
581 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
582
Steven Bennetts368c3e52016-09-23 13:05:21 -0700583 if not options.gn_args:
584 gn_env = os.getenv('GN_ARGS')
585 if gn_env is not None:
586 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800587 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700588
Steven Bennetts60600462016-05-12 10:40:20 -0700589 if not options.staging_flags:
590 use_env = os.getenv('USE')
591 if use_env is not None:
592 options.staging_flags = ' '.join(set(use_env.split()).intersection(
593 chrome_util.STAGING_FLAGS))
594 logging.info('Staging flags taken from USE in environment: %s',
595 options.staging_flags)
596
Ryan Cuia56a71e2012-10-18 18:40:35 -0700597
Ryan Cui504db722013-01-22 11:48:01 -0800598def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700599 """Get the chrome prebuilt tarball from GS.
600
Mike Frysinger02e1e072013-11-10 22:11:34 -0500601 Returns:
602 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700603 """
David James9374aac2013-10-08 16:00:17 -0700604 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500605 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800606 files = [found for found in files if
607 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
608 if not files:
609 raise Exception('No chrome package found at %s' % gs_path)
610 elif len(files) > 1:
611 # - Users should provide us with a direct link to either a stripped or
612 # unstripped chrome package.
613 # - In the case of being provided with an archive directory, where both
614 # stripped and unstripped chrome available, use the stripped chrome
615 # package.
616 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
617 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
618 files = [f for f in files if not 'unstripped' in f]
619 assert len(files) == 1
620 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800621
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800622 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800623 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800624 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
625 chrome_path = os.path.join(tempdir, filename)
626 assert os.path.exists(chrome_path)
627 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700628
629
Ryan Cuif890a3e2013-03-07 18:57:06 -0800630@contextlib.contextmanager
631def _StripBinContext(options):
632 if not options.dostrip:
633 yield None
634 elif options.strip_bin:
635 yield options.strip_bin
636 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800637 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800638 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700639 with sdk.Prepare(components=components, target_tc=options.target_tc,
640 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800641 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
642 constants.CHROME_ENV_FILE)
643 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
644 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
645 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800646 yield strip_bin
647
648
Steve Funge984a532013-11-25 17:09:25 -0800649def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
650 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800651 """Place the necessary files in the staging directory.
652
653 The staging directory is the directory used to rsync the build artifacts over
654 to the device. Only the necessary Chrome build artifacts are put into the
655 staging directory.
656 """
Ryan Cui5866be02013-03-18 14:12:00 -0700657 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400658 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800659 if options.build_dir:
660 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700661 strip_flags = (None if options.strip_flags is None else
662 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800663 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700664 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700665 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700666 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800667 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700668 else:
669 pkg_path = options.local_pkg_path
670 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800671 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
672 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700673
674 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800675 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700676 # Extract only the ./opt/google/chrome contents, directly into the staging
677 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800678 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400679 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800680 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
681 staging_dir])
682 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
683 shutil.move(filename, staging_dir)
684 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
685 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400686 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800687 ['tar', '--strip-components', '4', '--extract',
688 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
689 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700690
Ryan Cui71aa8de2013-04-19 16:12:55 -0700691
Ryan Cui3045c5d2012-07-13 18:00:33 -0700692def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400693 options = _ParseCommandLine(argv)
694 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700695
696 # Set cros_build_lib debug level to hide RunCommand spew.
697 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700698 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700699 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800700 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700701
Aviv Keshet01a82e92017-03-02 17:39:59 -0800702 with osutils.TempDir(set_global=True) as tempdir:
703 staging_dir = options.staging_dir
704 if not staging_dir:
705 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700706
Aviv Keshet01a82e92017-03-02 17:39:59 -0800707 deploy = DeployChrome(options, tempdir, staging_dir)
708 try:
709 deploy.Perform()
710 except failures_lib.StepFailure as ex:
711 raise SystemExit(str(ex).strip())
712 deploy.Cleanup()