blob: d778830e40e7fed9ec4261c71471c46244e77bb5 [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
Mike Frysinger6165cdc2020-02-21 02:38:07 -050030import sys
Ryan Cui3045c5d2012-07-13 18:00:33 -070031import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070032
Aviv Keshetb7519e12016-10-04 00:50:00 -070033from chromite.lib import constants
34from chromite.lib import failures_lib
David Pursellcfd58872015-03-19 09:15:48 -070035from chromite.cli.cros import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070036from chromite.lib import chrome_util
Ryan Cuie535b172012-10-19 18:25:03 -070037from chromite.lib import commandline
Ralph Nathan91874ca2015-03-19 13:29:41 -070038from chromite.lib import cros_build_lib
39from chromite.lib import cros_logging as logging
Ryan Cui777ff422012-12-07 13:12:54 -080040from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070041from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080042from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070043from chromite.lib import remote_access as remote
David James3432acd2013-11-27 10:02:18 -080044from chromite.lib import timeout_util
Steven Bennetts368c3e52016-09-23 13:05:21 -070045from gn_helpers import gn_helpers
Ryan Cui3045c5d2012-07-13 18:00:33 -070046
47
Mike Frysinger6165cdc2020-02-21 02:38:07 -050048assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
49
50
Ryan Cui3045c5d2012-07-13 18:00:33 -070051KERNEL_A_PARTITION = 2
52KERNEL_B_PARTITION = 4
53
54KILL_PROC_MAX_WAIT = 10
55POST_KILL_WAIT = 2
56
David Haddock3151d912017-10-24 03:50:32 +000057MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Anushruth8d797672019-10-17 12:22:31 -070058LSOF_COMMAND_CHROME = 'lsof %s/chrome'
59LSOF_COMMAND = 'lsof %s'
David Haddock3151d912017-10-24 03:50:32 +000060DBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon'
Ryan Cui3045c5d2012-07-13 18:00:33 -070061
Steve Funge984a532013-11-25 17:09:25 -080062_ANDROID_DIR = '/system/chrome'
63_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
64
David James2cb34002013-03-01 18:42:40 -080065_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070066_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080067
David Haddock3151d912017-10-24 03:50:32 +000068_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
69 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
70_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
71_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Anushruth8d797672019-10-17 12:22:31 -070072_MKDIR_P_CMD = 'mkdir -p --mode 0775 %s'
David Haddock3151d912017-10-24 03:50:32 +000073
74DF_COMMAND = 'df -k %s'
75
Mike Frysingere65f3752014-12-08 00:46:39 -050076
Ryan Cui3045c5d2012-07-13 18:00:33 -070077def _UrlBaseName(url):
78 """Return the last component of the URL."""
79 return url.rstrip('/').rpartition('/')[-1]
80
81
Yu-Ju Hongc54d3342014-05-14 12:42:06 -070082class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -080083 """Raised whenever the deploy fails."""
84
85
Ryan Cui7193a7e2013-04-26 14:15:19 -070086DeviceInfo = collections.namedtuple(
87 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
88
89
Ryan Cui3045c5d2012-07-13 18:00:33 -070090class DeployChrome(object):
91 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -050092
Ryan Cuia56a71e2012-10-18 18:40:35 -070093 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070094 """Initialize the class.
95
Mike Frysinger02e1e072013-11-10 22:11:34 -050096 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -040097 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -070098 tempdir: Scratch space for the class. Caller has responsibility to clean
99 it up.
Steve Funge984a532013-11-25 17:09:25 -0800100 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -0700101 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700102 self.tempdir = tempdir
103 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -0700104 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -0500105 if not self.options.staging_only:
Malay Keshave7a071e2020-05-21 20:07:44 +0000106 self.device = remote.RemoteDevice(options.to, port=options.port,
107 ping=options.ping,
108 private_key=options.private_key)
Steven Bennettsca73efa2018-07-10 13:36:56 -0700109 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000110
Sadrul Habib Chowdhury977aef72016-11-21 16:30:37 -0500111 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800112 self.chrome_dir = _CHROME_DIR
113
Ryan Cui7193a7e2013-04-26 14:15:19 -0700114 def _GetRemoteMountFree(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400115 result = self.device.run(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700116 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800117 value = line.split()[3]
118 multipliers = {
119 'G': 1024 * 1024 * 1024,
120 'M': 1024 * 1024,
121 'K': 1024,
122 }
123 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700124
125 def _GetRemoteDirSize(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400126 result = self.device.run('du -ks %s' % remote_dir,
127 capture_output=True, encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700128 return int(result.output.split()[0])
129
130 def _GetStagingDirSize(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400131 result = cros_build_lib.dbg_run(['du', '-ks', self.staging_dir],
Mike Frysinger0282d222019-12-17 17:15:48 -0500132 stdout=True, capture_output=True,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400133 encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700134 return int(result.output.split()[0])
135
Ryan Cui3045c5d2012-07-13 18:00:33 -0700136 def _ChromeFileInUse(self):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400137 result = self.device.run(LSOF_COMMAND_CHROME % (self.options.target_dir,),
138 check=False, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700139 return result.returncode == 0
140
Justin TerAvestfac210e2017-04-13 11:39:00 -0600141 def _Reboot(self):
142 # A reboot in developer mode takes a while (and has delays), so the user
143 # will have time to read and act on the USB boot instructions below.
144 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
145 self.device.Reboot()
146
Ryan Cui3045c5d2012-07-13 18:00:33 -0700147 def _DisableRootfsVerification(self):
148 if not self.options.force:
149 logging.error('Detected that the device has rootfs verification enabled.')
150 logging.info('This script can automatically remove the rootfs '
151 'verification, which requires that it reboot the device.')
152 logging.info('Make sure the device is in developer mode!')
153 logging.info('Skip this prompt by specifying --force.')
Ben Chan37d64e52018-10-05 15:35:02 -0700154 if not cros_build_lib.BooleanPrompt('Remove rootfs verification?', False):
Steven Bennettsca73efa2018-07-10 13:36:56 -0700155 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700156
157 logging.info('Removing rootfs verification from %s', self.options.to)
158 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
159 # Use --force to bypass the checks.
David Haddock3151d912017-10-24 03:50:32 +0000160 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
161 '--remove_rootfs_verification --force')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700162 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400163 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700164
Justin TerAvestfac210e2017-04-13 11:39:00 -0600165 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700166
David James88e6f032013-03-02 08:13:20 -0800167 # Now that the machine has been rebooted, we need to kill Chrome again.
168 self._KillProcsIfNeeded()
169
170 # Make sure the rootfs is writable now.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500171 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700172
Steven Bennettsca73efa2018-07-10 13:36:56 -0700173 return True
174
Ryan Cui3045c5d2012-07-13 18:00:33 -0700175 def _CheckUiJobStarted(self):
176 # status output is in the format:
177 # <job_name> <status> ['process' <pid>].
178 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800179 try:
Mike Frysinger3459bf52020-03-31 00:52:11 -0400180 result = self.device.run('status ui', capture_output=True,
181 encoding='utf-8')
Ryan Cuif2d1a582013-02-19 14:08:13 -0800182 except cros_build_lib.RunCommandError as e:
183 if 'Unknown job' in e.result.error:
184 return False
185 else:
186 raise e
187
Ryan Cui3045c5d2012-07-13 18:00:33 -0700188 return result.output.split()[1].split('/')[0] == 'start'
189
190 def _KillProcsIfNeeded(self):
191 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800192 logging.info('Shutting down Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400193 self.device.run('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700194
195 # Developers sometimes run session_manager manually, in which case we'll
196 # need to help shut the chrome processes down.
197 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700198 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700199 while self._ChromeFileInUse():
200 logging.warning('The chrome binary on the device is in use.')
201 logging.warning('Killing chrome and session_manager processes...\n')
202
Mike Frysinger3459bf52020-03-31 00:52:11 -0400203 self.device.run("pkill 'chrome|session_manager'", check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700204 # Wait for processes to actually terminate
205 time.sleep(POST_KILL_WAIT)
206 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800207 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800208 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700209 'running chrome processes and try again.'
210 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800211 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700212
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500213 def _MountRootfsAsWritable(self, check=False):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800214 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700215
Steven Bennettsca73efa2018-07-10 13:36:56 -0700216 If the command fails and the root dir is not writable then this function
217 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700218
Mike Frysinger02e1e072013-11-10 22:11:34 -0500219 Args:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500220 check: See remote.RemoteAccess.RemoteSh for details.
David James88e6f032013-03-02 08:13:20 -0800221 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800222 # TODO: Should migrate to use the remount functions in remote_access.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400223 result = self.device.run(MOUNT_RW_COMMAND, check=check,
224 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700225 if result.returncode and not self.device.IsDirWritable('/'):
226 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700227
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800228 def _EnsureTargetDir(self):
229 """Ensures that the target directory exists on the remote device."""
230 target_dir = self.options.target_dir
231 # Any valid /opt directory should already exist so avoid the remote call.
232 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
233 return
Mike Frysinger3459bf52020-03-31 00:52:11 -0400234 self.device.run(['mkdir', '-p', '--mode', '0775', target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800235
Ryan Cui7193a7e2013-04-26 14:15:19 -0700236 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800237 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700238 steps = [
239 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
240 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
241 ]
242 return_values = parallel.RunParallelSteps(steps, return_values=True)
243 return DeviceInfo(*return_values)
244
245 def _CheckDeviceFreeSpace(self, device_info):
246 """See if target device has enough space for Chrome.
247
Mike Frysinger02e1e072013-11-10 22:11:34 -0500248 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700249 device_info: A DeviceInfo named tuple.
250 """
251 effective_free = device_info.target_dir_size + device_info.target_fs_free
252 staging_size = self._GetStagingDirSize()
253 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700254 raise DeployFailure(
255 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400256 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700257 if device_info.target_fs_free < (100 * 1024):
258 logging.warning('The device has less than 100MB free. deploy_chrome may '
259 'hang during the transfer.')
260
Satoru Takabayashif2893002017-06-20 14:52:48 +0900261 def _ShouldUseCompression(self):
262 """Checks if compression should be used for rsync."""
263 if self.options.compress == 'always':
264 return True
265 elif self.options.compress == 'never':
266 return False
267 elif self.options.compress == 'auto':
268 return not self.device.HasGigabitEthernet()
269
Ryan Cui3045c5d2012-07-13 18:00:33 -0700270 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800271 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700272 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
273 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900274 if not self.device.HasRsync():
275 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700276 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500277 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500278 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
279 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700280 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900281 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700282 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500283 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700284
285 # Set the security context on the default Chrome dir if that's where it's
286 # getting deployed, and only on SELinux supported devices.
287 if (self.device.IsSELinuxAvailable() and
288 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400289 self.device.run(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800290
291 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800292 if p.mode:
293 # Set mode if necessary.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400294 self.device.run('chmod %o %s/%s' % (
David Haddock3151d912017-10-24 03:50:32 +0000295 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800296
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700297 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
298 # pick up major changes (bus type, logging, etc.), but all we care about is
299 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
300 # be authorized to take ownership of its service names.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400301 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600302
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800303 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800304 logging.info('Starting UI...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400305 self.device.run('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700306
David James88e6f032013-03-02 08:13:20 -0800307 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700308 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800309 logging.info('Testing connection to the device...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400310 self.device.run('true')
David James88e6f032013-03-02 08:13:20 -0800311 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700312 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800313 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700314
Steve Funge984a532013-11-25 17:09:25 -0800315 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700316 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700317 def BinaryExists(filename):
318 """Checks if the passed-in file is present in the build directory."""
319 return os.path.exists(os.path.join(self.options.build_dir, filename))
320
Daniel Erat9813f0e2014-11-12 11:00:28 -0700321 # Handle non-Chrome deployments.
322 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700323 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700324 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
325
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700326 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700327 # app_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700328 self.options.startui = False
329
David James88e6f032013-03-02 08:13:20 -0800330 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800331 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
332 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800333
Thiago Goncales12793312013-05-23 11:26:17 -0700334 def _MountTarget(self):
335 logging.info('Mounting Chrome...')
336
Anushruth8d797672019-10-17 12:22:31 -0700337 # Create directory if does not exist.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400338 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
Anushruth8d797672019-10-17 12:22:31 -0700339 try:
340 # Umount the existing mount on mount_dir if present first.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400341 self.device.run(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
342 {'dir': self.options.mount_dir})
Anushruth8d797672019-10-17 12:22:31 -0700343 except cros_build_lib.RunCommandError as e:
344 logging.error('Failed to umount %s', self.options.mount_dir)
345 # If there is a failure, check if some processs is using the mount_dir.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400346 result = self.device.run(LSOF_COMMAND % (self.options.mount_dir,),
347 check=False, capture_output=True,
348 encoding='utf-8')
Anushruth8d797672019-10-17 12:22:31 -0700349 logging.error('lsof %s -->', self.options.mount_dir)
350 logging.error(result.stdout)
351 raise e
352
Mike Frysinger3459bf52020-03-31 00:52:11 -0400353 self.device.run(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
354 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700355
Thiago Goncales12793312013-05-23 11:26:17 -0700356 # Chrome needs partition to have exec and suid flags set
Mike Frysinger3459bf52020-03-31 00:52:11 -0400357 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500358
359 def Cleanup(self):
360 """Clean up RemoteDevice."""
361 if not self.options.staging_only:
362 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700363
David James88e6f032013-03-02 08:13:20 -0800364 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800365 self._CheckDeployType()
366
David James88e6f032013-03-02 08:13:20 -0800367 # If requested, just do the staging step.
368 if self.options.staging_only:
369 self._PrepareStagingDir()
370 return 0
371
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800372 # Ensure that the target directory exists before running parallel steps.
373 self._EnsureTargetDir()
374
David James88e6f032013-03-02 08:13:20 -0800375 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800376 # stop printing output at that point, and halt any running steps.
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800377 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800378 steps = [self._GetDeviceInfo, self._CheckConnection,
379 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
380 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700381 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
382 return_values=True)
383 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800384
Steven Bennettsca73efa2018-07-10 13:36:56 -0700385 # If the root dir is not writable, try disabling rootfs verification.
386 # (We always do this by default so that developers can write to
387 # /etc/chriome_dev.conf and other directories in the rootfs).
388 if self._root_dir_is_still_readonly.is_set():
389 if self.options.noremove_rootfs_verification:
390 logging.warning('Skipping disable rootfs verification.')
391 elif not self._DisableRootfsVerification():
392 logging.warning('Failed to disable rootfs verification.')
393
394 # If the target dir is still not writable (i.e. the user opted out or the
395 # command failed), abort.
396 if not self.device.IsDirWritable(self.options.target_dir):
397 if self.options.startui:
398 logging.info('Restarting Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400399 self.device.run('start ui')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700400 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800401
Thiago Goncales12793312013-05-23 11:26:17 -0700402 if self.options.mount_dir is not None:
403 self._MountTarget()
404
David James88e6f032013-03-02 08:13:20 -0800405 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700406 self._Deploy()
407
408
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700409def ValidateStagingFlags(value):
410 """Convert formatted string to dictionary."""
411 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700412
413
Steven Bennetts368c3e52016-09-23 13:05:21 -0700414def ValidateGnArgs(value):
415 """Convert GN_ARGS-formatted string to dictionary."""
416 return gn_helpers.FromGNArgs(value)
417
418
Ryan Cuie535b172012-10-19 18:25:03 -0700419def _CreateParser():
420 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400421 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700422
Ryan Cuia56a71e2012-10-18 18:40:35 -0700423 # TODO(rcui): Have this use the UI-V2 format of having source and target
424 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400425 parser.add_argument('--force', action='store_true', default=False,
Malay Keshave7a071e2020-05-21 20:07:44 +0000426 help='Skip all prompts (i.e., for disabling of rootfs '
427 'verification). This may result in the target '
428 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800429 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400430 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400431 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400432 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400433 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400434 parser.add_argument('--build-dir', type='path',
435 help='The directory with Chrome build artifacts to '
436 'deploy from. Typically of format '
437 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700438 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400439 parser.add_argument('--target-dir', type='path',
440 default=None,
441 help='Target directory on device to deploy Chrome into.')
442 parser.add_argument('-g', '--gs-path', type='gs_path',
443 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500444 parser.add_argument('--private-key', type='path', default=None,
445 help='An ssh private key to use when deploying to '
446 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400447 parser.add_argument('--nostartui', action='store_false', dest='startui',
448 default=True,
449 help="Don't restart the ui daemon after deployment.")
450 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
451 default=True,
452 help="Don't strip binaries during deployment. Warning: "
453 'the resulting binaries will be very large!')
454 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
455 help='Port of the target device to connect to.')
456 parser.add_argument('-t', '--to',
457 help='The IP address of the CrOS device to deploy to.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400458 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')
Malay Keshave7a071e2020-05-21 20:07:44 +0000546 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
Mike Frysinger3459bf52020-03-31 00:52:11 -0400696 # Set cros_build_lib debug level to hide run spew.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700697 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()