blob: fbd00e7b1fb861a678fa3784c46f6b0318988ee3 [file] [log] [blame]
Ryan Cui3045c5d2012-07-13 18:00:33 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Steve Funge984a532013-11-25 17:09:25 -08005"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07006
7The script supports deploying Chrome from these sources:
8
91. A local build output directory, such as chromium/src/out/[Debug|Release].
102. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
113. A Chrome tarball existing locally.
12
13The script copies the necessary contents of the source location (tarball or
14build directory) and rsyncs the contents of the staging directory onto your
15device's rootfs.
16"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070017
Mike Frysinger383367e2014-09-16 15:06:17 -040018from __future__ import print_function
19
Mike Frysingerc3061a62015-06-04 04:16:18 -040020import argparse
Ryan Cui7193a7e2013-04-26 14:15:19 -070021import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080022import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070023import functools
Steve Funge984a532013-11-25 17:09:25 -080024import glob
David James88e6f032013-03-02 08:13:20 -080025import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070026import os
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070027import shlex
Steve Funge984a532013-11-25 17:09:25 -080028import shutil
Ryan Cui3045c5d2012-07-13 18:00:33 -070029import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070030
Aviv Keshetb7519e12016-10-04 00:50:00 -070031from chromite.lib import constants
32from chromite.lib import failures_lib
David Pursellcfd58872015-03-19 09:15:48 -070033from chromite.cli.cros import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070034from chromite.lib import chrome_util
Ryan Cuie535b172012-10-19 18:25:03 -070035from chromite.lib import commandline
Ralph Nathan91874ca2015-03-19 13:29:41 -070036from chromite.lib import cros_build_lib
37from chromite.lib import cros_logging as logging
Ryan Cui777ff422012-12-07 13:12:54 -080038from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070039from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080040from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070041from chromite.lib import remote_access as remote
Ryan Cui71aa8de2013-04-19 16:12:55 -070042from chromite.lib import stats
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
Ryan Cuie535b172012-10-19 18:25:03 -070053MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080054LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070055
Steve Funge984a532013-11-25 17:09:25 -080056_ANDROID_DIR = '/system/chrome'
57_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
58
David James2cb34002013-03-01 18:42:40 -080059_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070060_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080061
Pawel Osciakc1bb2742014-12-29 16:32:33 +090062_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
63 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
Thiago Goncales12793312013-05-23 11:26:17 -070064_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
65_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070066
Steve Funge984a532013-11-25 17:09:25 -080067DF_COMMAND = 'df -k %s'
Steve Funge984a532013-11-25 17:09:25 -080068
Mike Frysingere65f3752014-12-08 00:46:39 -050069
Ryan Cui3045c5d2012-07-13 18:00:33 -070070def _UrlBaseName(url):
71 """Return the last component of the URL."""
72 return url.rstrip('/').rpartition('/')[-1]
73
74
Yu-Ju Hongc54d3342014-05-14 12:42:06 -070075class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -080076 """Raised whenever the deploy fails."""
77
78
Ryan Cui7193a7e2013-04-26 14:15:19 -070079DeviceInfo = collections.namedtuple(
80 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
81
82
Ryan Cui3045c5d2012-07-13 18:00:33 -070083class DeployChrome(object):
84 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -050085
Ryan Cuia56a71e2012-10-18 18:40:35 -070086 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070087 """Initialize the class.
88
Mike Frysinger02e1e072013-11-10 22:11:34 -050089 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -040090 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -070091 tempdir: Scratch space for the class. Caller has responsibility to clean
92 it up.
Steve Funge984a532013-11-25 17:09:25 -080093 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -070094 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070095 self.tempdir = tempdir
96 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070097 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -050098 if not self.options.staging_only:
Pawel Osciak141b2262014-12-21 10:27:05 +090099 self.device = remote.RemoteDevice(options.to, port=options.port,
100 ping=options.ping)
Robert Flack1dc7ea82014-11-26 13:50:24 -0500101 self._target_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000102
Sadrul Habib Chowdhury977aef72016-11-21 16:30:37 -0500103 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800104 self.chrome_dir = _CHROME_DIR
105
Ryan Cui7193a7e2013-04-26 14:15:19 -0700106 def _GetRemoteMountFree(self, remote_dir):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500107 result = self.device.RunCommand(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700108 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800109 value = line.split()[3]
110 multipliers = {
111 'G': 1024 * 1024 * 1024,
112 'M': 1024 * 1024,
113 'K': 1024,
114 }
115 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700116
117 def _GetRemoteDirSize(self, remote_dir):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500118 result = self.device.RunCommand('du -ks %s' % remote_dir,
119 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700120 return int(result.output.split()[0])
121
122 def _GetStagingDirSize(self):
123 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800124 redirect_stdout=True,
125 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700126 return int(result.output.split()[0])
127
Ryan Cui3045c5d2012-07-13 18:00:33 -0700128 def _ChromeFileInUse(self):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500129 result = self.device.RunCommand(LSOF_COMMAND % (self.options.target_dir,),
130 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700131 return result.returncode == 0
132
133 def _DisableRootfsVerification(self):
134 if not self.options.force:
135 logging.error('Detected that the device has rootfs verification enabled.')
136 logging.info('This script can automatically remove the rootfs '
137 'verification, which requires that it reboot the device.')
138 logging.info('Make sure the device is in developer mode!')
139 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700140 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800141 # Since we stopped Chrome earlier, it's good form to start it up again.
142 if self.options.startui:
143 logging.info('Starting Chrome...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500144 self.device.RunCommand('start ui')
David James88e6f032013-03-02 08:13:20 -0800145 raise DeployFailure('Need rootfs verification to be disabled. '
146 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700147
148 logging.info('Removing rootfs verification from %s', self.options.to)
149 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
150 # Use --force to bypass the checks.
151 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
152 '--remove_rootfs_verification --force')
153 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500154 self.device.RunCommand(cmd % partition, error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700155
156 # A reboot in developer mode takes a while (and has delays), so the user
157 # will have time to read and act on the USB boot instructions below.
158 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500159 self.device.Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700160
David James88e6f032013-03-02 08:13:20 -0800161 # Now that the machine has been rebooted, we need to kill Chrome again.
162 self._KillProcsIfNeeded()
163
164 # Make sure the rootfs is writable now.
165 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700166
167 def _CheckUiJobStarted(self):
168 # status output is in the format:
169 # <job_name> <status> ['process' <pid>].
170 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800171 try:
Robert Flack1dc7ea82014-11-26 13:50:24 -0500172 result = self.device.RunCommand('status ui', capture_output=True)
Ryan Cuif2d1a582013-02-19 14:08:13 -0800173 except cros_build_lib.RunCommandError as e:
174 if 'Unknown job' in e.result.error:
175 return False
176 else:
177 raise e
178
Ryan Cui3045c5d2012-07-13 18:00:33 -0700179 return result.output.split()[1].split('/')[0] == 'start'
180
181 def _KillProcsIfNeeded(self):
182 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800183 logging.info('Shutting down Chrome...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500184 self.device.RunCommand('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700185
186 # Developers sometimes run session_manager manually, in which case we'll
187 # need to help shut the chrome processes down.
188 try:
David James3432acd2013-11-27 10:02:18 -0800189 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700190 while self._ChromeFileInUse():
191 logging.warning('The chrome binary on the device is in use.')
192 logging.warning('Killing chrome and session_manager processes...\n')
193
Robert Flack1dc7ea82014-11-26 13:50:24 -0500194 self.device.RunCommand("pkill 'chrome|session_manager'",
Mike Frysingere65f3752014-12-08 00:46:39 -0500195 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700196 # Wait for processes to actually terminate
197 time.sleep(POST_KILL_WAIT)
198 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800199 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800200 msg = ('Could not kill processes after %s seconds. Please exit any '
201 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
202 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700203
David James88e6f032013-03-02 08:13:20 -0800204 def _MountRootfsAsWritable(self, error_code_ok=True):
205 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700206
Robert Flack1dc7ea82014-11-26 13:50:24 -0500207 If the command fails, and error_code_ok is True, and the target dir is not
208 writable then this function sets self._target_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700209
Mike Frysinger02e1e072013-11-10 22:11:34 -0500210 Args:
David James88e6f032013-03-02 08:13:20 -0800211 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
212 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800213 # TODO: Should migrate to use the remount functions in remote_access.
Robert Flack1dc7ea82014-11-26 13:50:24 -0500214 result = self.device.RunCommand(MOUNT_RW_COMMAND,
215 error_code_ok=error_code_ok,
216 capture_output=True)
217 if (result.returncode and
Mike Frysinger74ccd572015-05-21 21:18:20 -0400218 not self.device.IsDirWritable(self.options.target_dir)):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500219 self._target_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700220
Ryan Cui7193a7e2013-04-26 14:15:19 -0700221 def _GetDeviceInfo(self):
222 steps = [
223 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
224 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
225 ]
226 return_values = parallel.RunParallelSteps(steps, return_values=True)
227 return DeviceInfo(*return_values)
228
229 def _CheckDeviceFreeSpace(self, device_info):
230 """See if target device has enough space for Chrome.
231
Mike Frysinger02e1e072013-11-10 22:11:34 -0500232 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700233 device_info: A DeviceInfo named tuple.
234 """
235 effective_free = device_info.target_dir_size + device_info.target_fs_free
236 staging_size = self._GetStagingDirSize()
237 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700238 raise DeployFailure(
239 'Not enough free space on the device. Required: %s MiB, '
240 'actual: %s MiB.' % (staging_size / 1024, effective_free / 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700241 if device_info.target_fs_free < (100 * 1024):
242 logging.warning('The device has less than 100MB free. deploy_chrome may '
243 'hang during the transfer.')
244
Ryan Cui3045c5d2012-07-13 18:00:33 -0700245 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800246 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700247 # Show the output (status) for this command.
Steve Funge984a532013-11-25 17:09:25 -0800248 dest_path = _CHROME_DIR
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900249 if not self.device.HasRsync():
250 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700251 'rsync is not found on the device.\n'
252 'Run dev_install on the device to get rsync installed')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500253 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
254 self.options.target_dir,
255 inplace=True, debug_level=logging.INFO,
256 verbose=self.options.verbose)
Steve Funge984a532013-11-25 17:09:25 -0800257
258 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800259 if p.mode:
260 # Set mode if necessary.
Robert Flack1dc7ea82014-11-26 13:50:24 -0500261 self.device.RunCommand('chmod %o %s/%s' % (
262 p.mode, dest_path, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800263
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800264 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800265 logging.info('Starting UI...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500266 self.device.RunCommand('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700267
David James88e6f032013-03-02 08:13:20 -0800268 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700269 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800270 logging.info('Testing connection to the device...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500271 self.device.RunCommand('true')
David James88e6f032013-03-02 08:13:20 -0800272 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700273 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800274 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700275
Steve Funge984a532013-11-25 17:09:25 -0800276 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700277 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700278 def BinaryExists(filename):
279 """Checks if the passed-in file is present in the build directory."""
280 return os.path.exists(os.path.join(self.options.build_dir, filename))
281
Daniel Erat9813f0e2014-11-12 11:00:28 -0700282 # Handle non-Chrome deployments.
283 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700284 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700285 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
286
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700287 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700288 # app_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700289 self.options.startui = False
290
David James88e6f032013-03-02 08:13:20 -0800291 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800292 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
293 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800294
Thiago Goncales12793312013-05-23 11:26:17 -0700295 def _MountTarget(self):
296 logging.info('Mounting Chrome...')
297
298 # Create directory if does not exist
Robert Flack1dc7ea82014-11-26 13:50:24 -0500299 self.device.RunCommand('mkdir -p --mode 0775 %s' % (
Mike Frysingere65f3752014-12-08 00:46:39 -0500300 self.options.mount_dir,))
Pawel Osciakfb200f52014-12-21 13:42:05 +0900301 # Umount the existing mount on mount_dir if present first
302 self.device.RunCommand(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
303 {'dir': self.options.mount_dir})
Robert Flack1dc7ea82014-11-26 13:50:24 -0500304 self.device.RunCommand(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
305 self.options.mount_dir))
Thiago Goncales12793312013-05-23 11:26:17 -0700306 # Chrome needs partition to have exec and suid flags set
Robert Flack1dc7ea82014-11-26 13:50:24 -0500307 self.device.RunCommand(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
308
309 def Cleanup(self):
310 """Clean up RemoteDevice."""
311 if not self.options.staging_only:
312 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700313
David James88e6f032013-03-02 08:13:20 -0800314 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800315 self._CheckDeployType()
316
David James88e6f032013-03-02 08:13:20 -0800317 # If requested, just do the staging step.
318 if self.options.staging_only:
319 self._PrepareStagingDir()
320 return 0
321
322 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800323 # stop printing output at that point, and halt any running steps.
Steve Funge984a532013-11-25 17:09:25 -0800324 steps = [self._GetDeviceInfo, self._CheckConnection,
325 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
326 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700327 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
328 return_values=True)
329 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800330
Robert Flack1dc7ea82014-11-26 13:50:24 -0500331 # If we're trying to deploy to a dir which is not writable and we failed
332 # to mark the rootfs as writable, try disabling rootfs verification.
333 if self._target_dir_is_still_readonly.is_set():
David James88e6f032013-03-02 08:13:20 -0800334 self._DisableRootfsVerification()
335
Thiago Goncales12793312013-05-23 11:26:17 -0700336 if self.options.mount_dir is not None:
337 self._MountTarget()
338
David James88e6f032013-03-02 08:13:20 -0800339 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700340 self._Deploy()
341
342
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700343def ValidateStagingFlags(value):
344 """Convert formatted string to dictionary."""
345 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700346
347
Steven Bennetts368c3e52016-09-23 13:05:21 -0700348def ValidateGnArgs(value):
349 """Convert GN_ARGS-formatted string to dictionary."""
350 return gn_helpers.FromGNArgs(value)
351
352
Ryan Cuie535b172012-10-19 18:25:03 -0700353def _CreateParser():
354 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400355 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700356
Ryan Cuia56a71e2012-10-18 18:40:35 -0700357 # TODO(rcui): Have this use the UI-V2 format of having source and target
358 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400359 parser.add_argument('--force', action='store_true', default=False,
360 help='Skip all prompts (i.e., for disabling of rootfs '
361 'verification). This may result in the target '
362 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800363 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400364 parser.add_argument('--board', default=sdk_board_env,
365 help="The board the Chrome build is targeted for. When "
366 "in a 'cros chrome-sdk' shell, defaults to the SDK "
367 "board.")
368 parser.add_argument('--build-dir', type='path',
369 help='The directory with Chrome build artifacts to '
370 'deploy from. Typically of format '
371 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700372 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400373 parser.add_argument('--target-dir', type='path',
374 default=None,
375 help='Target directory on device to deploy Chrome into.')
376 parser.add_argument('-g', '--gs-path', type='gs_path',
377 help='GS path that contains the chrome to deploy.')
378 parser.add_argument('--nostartui', action='store_false', dest='startui',
379 default=True,
380 help="Don't restart the ui daemon after deployment.")
381 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
382 default=True,
383 help="Don't strip binaries during deployment. Warning: "
384 'the resulting binaries will be very large!')
385 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
386 help='Port of the target device to connect to.')
387 parser.add_argument('-t', '--to',
388 help='The IP address of the CrOS device to deploy to.')
389 parser.add_argument('-v', '--verbose', action='store_true', default=False,
390 help='Show more debug output.')
391 parser.add_argument('--mount-dir', type='path', default=None,
392 help='Deploy Chrome in target directory and bind it '
393 'to the directory specified by this flag.'
394 'Any existing mount on this directory will be '
395 'umounted first.')
396 parser.add_argument('--mount', action='store_true', default=False,
397 help='Deploy Chrome to default target directory and bind '
398 'it to the default mount directory.'
399 'Any existing mount on this directory will be '
400 'umounted first.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700401
Mike Frysingerc3061a62015-06-04 04:16:18 -0400402 group = parser.add_argument_group('Advanced Options')
403 group.add_argument('-l', '--local-pkg-path', type='path',
404 help='Path to local chrome prebuilt package to deploy.')
405 group.add_argument('--sloppy', action='store_true', default=False,
406 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700407 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400408 help=('Extra flags to control staging. Valid flags are - '
409 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700410 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400411 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700412 help='Deprecated. Default behavior is "strict". Use '
413 '--sloppy to omit warnings for missing optional '
414 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400415 group.add_argument('--strip-flags', default=None,
416 help="Flags to call the 'strip' binutil tool with. "
417 "Overrides the default arguments.")
418 group.add_argument('--ping', action='store_true', default=False,
419 help='Ping the device before connection attempt.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700420
Mike Frysingerc3061a62015-06-04 04:16:18 -0400421 group = parser.add_argument_group(
422 'Metadata Overrides (Advanced)',
423 description='Provide all of these overrides in order to remove '
424 'dependencies on metadata.json existence.')
425 group.add_argument('--target-tc', action='store', default=None,
426 help='Override target toolchain name, e.g. '
427 'x86_64-cros-linux-gnu')
428 group.add_argument('--toolchain-url', action='store', default=None,
429 help='Override toolchain url format pattern, e.g. '
430 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700431
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700432 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
433 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
434 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400435 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700436
437 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
438 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
439 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
440 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
441 help=argparse.SUPPRESS)
442
Ryan Cuia56a71e2012-10-18 18:40:35 -0700443 # Path of an empty directory to stage chrome artifacts to. Defaults to a
444 # temporary directory that is removed when the script finishes. If the path
445 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400446 parser.add_argument('--staging-dir', type='path', default=None,
447 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700448 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400449 parser.add_argument('--staging-only', action='store_true', default=False,
450 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700451 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
452 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
453 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400454 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Ryan Cuie535b172012-10-19 18:25:03 -0700455 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700456
Ryan Cuie535b172012-10-19 18:25:03 -0700457
458def _ParseCommandLine(argv):
459 """Parse args, and run environment-independent checks."""
460 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400461 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700462
Ryan Cuia56a71e2012-10-18 18:40:35 -0700463 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
464 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800465 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700466 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
467 parser.error('Cannot specify both --build_dir and '
468 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800469 if options.build_dir and not options.board:
470 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700471 if options.gs_path and options.local_pkg_path:
472 parser.error('Cannot specify both --gs-path and --local-pkg-path')
473 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700474 parser.error('Need to specify --to')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700475 if options.staging_flags and not options.build_dir:
476 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700477
Steven Bennetts46a84c32016-08-12 15:20:46 -0700478 if options.strict:
479 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700480 if options.gyp_defines:
481 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700482
483 if options.mount or options.mount_dir:
484 if not options.target_dir:
485 options.target_dir = _CHROME_DIR_MOUNT
486 else:
487 if not options.target_dir:
488 options.target_dir = _CHROME_DIR
489
490 if options.mount and not options.mount_dir:
491 options.mount_dir = _CHROME_DIR
492
Mike Frysingerc3061a62015-06-04 04:16:18 -0400493 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700494
495
Mike Frysingerc3061a62015-06-04 04:16:18 -0400496def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800497 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700498
499 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400500 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700501 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700502 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
503 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
504
Steven Bennetts368c3e52016-09-23 13:05:21 -0700505 if not options.gn_args:
506 gn_env = os.getenv('GN_ARGS')
507 if gn_env is not None:
508 options.gn_args = gn_helpers.FromGNArgs(gn_env)
509 logging.info('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700510
Steven Bennetts60600462016-05-12 10:40:20 -0700511 if not options.staging_flags:
512 use_env = os.getenv('USE')
513 if use_env is not None:
514 options.staging_flags = ' '.join(set(use_env.split()).intersection(
515 chrome_util.STAGING_FLAGS))
516 logging.info('Staging flags taken from USE in environment: %s',
517 options.staging_flags)
518
Ryan Cuia56a71e2012-10-18 18:40:35 -0700519
Ryan Cui504db722013-01-22 11:48:01 -0800520def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700521 """Get the chrome prebuilt tarball from GS.
522
Mike Frysinger02e1e072013-11-10 22:11:34 -0500523 Returns:
524 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700525 """
David James9374aac2013-10-08 16:00:17 -0700526 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500527 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800528 files = [found for found in files if
529 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
530 if not files:
531 raise Exception('No chrome package found at %s' % gs_path)
532 elif len(files) > 1:
533 # - Users should provide us with a direct link to either a stripped or
534 # unstripped chrome package.
535 # - In the case of being provided with an archive directory, where both
536 # stripped and unstripped chrome available, use the stripped chrome
537 # package.
538 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
539 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
540 files = [f for f in files if not 'unstripped' in f]
541 assert len(files) == 1
542 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800543
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800544 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800545 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800546 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
547 chrome_path = os.path.join(tempdir, filename)
548 assert os.path.exists(chrome_path)
549 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700550
551
Ryan Cuif890a3e2013-03-07 18:57:06 -0800552@contextlib.contextmanager
553def _StripBinContext(options):
554 if not options.dostrip:
555 yield None
556 elif options.strip_bin:
557 yield options.strip_bin
558 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800559 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800560 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700561 with sdk.Prepare(components=components, target_tc=options.target_tc,
562 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800563 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
564 constants.CHROME_ENV_FILE)
565 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
566 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
567 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800568 yield strip_bin
569
570
Steve Funge984a532013-11-25 17:09:25 -0800571def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
572 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800573 """Place the necessary files in the staging directory.
574
575 The staging directory is the directory used to rsync the build artifacts over
576 to the device. Only the necessary Chrome build artifacts are put into the
577 staging directory.
578 """
Ryan Cui5866be02013-03-18 14:12:00 -0700579 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400580 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800581 if options.build_dir:
582 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700583 strip_flags = (None if options.strip_flags is None else
584 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800585 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700586 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700587 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700588 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800589 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700590 else:
591 pkg_path = options.local_pkg_path
592 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800593 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
594 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700595
596 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800597 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700598 # Extract only the ./opt/google/chrome contents, directly into the staging
599 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800600 if pkg_path[-4:] == '.zip':
601 cros_build_lib.DebugRunCommand(
602 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
603 staging_dir])
604 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
605 shutil.move(filename, staging_dir)
606 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
607 else:
608 cros_build_lib.DebugRunCommand(
609 ['tar', '--strip-components', '4', '--extract',
610 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
611 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700612
Ryan Cui71aa8de2013-04-19 16:12:55 -0700613
Ryan Cui3045c5d2012-07-13 18:00:33 -0700614def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400615 options = _ParseCommandLine(argv)
616 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700617
618 # Set cros_build_lib debug level to hide RunCommand spew.
619 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700620 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700621 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800622 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700623
Ryan Cui71aa8de2013-04-19 16:12:55 -0700624 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700625 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
626 if cmd_stats:
627 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700628
Ryan Cui71aa8de2013-04-19 16:12:55 -0700629 with osutils.TempDir(set_global=True) as tempdir:
630 staging_dir = options.staging_dir
631 if not staging_dir:
632 staging_dir = os.path.join(tempdir, 'chrome')
633
634 deploy = DeployChrome(options, tempdir, staging_dir)
635 try:
636 deploy.Perform()
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700637 except failures_lib.StepFailure as ex:
Ryan Cui71aa8de2013-04-19 16:12:55 -0700638 raise SystemExit(str(ex).strip())
Robert Flack1dc7ea82014-11-26 13:50:24 -0500639 deploy.Cleanup()