blob: 354379a95fb701151dbc11198952b1cd3fb06382 [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
Don Garrett88b8d782014-05-13 17:30:55 -070031from chromite.cbuildbot import constants
David James14e97772014-06-04 18:44:49 -070032from chromite.cbuildbot 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
Ryan Cui3045c5d2012-07-13 18:00:33 -070044
45
Ryan Cui3045c5d2012-07-13 18:00:33 -070046KERNEL_A_PARTITION = 2
47KERNEL_B_PARTITION = 4
48
49KILL_PROC_MAX_WAIT = 10
50POST_KILL_WAIT = 2
51
Ryan Cuie535b172012-10-19 18:25:03 -070052MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080053LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070054
Steve Funge984a532013-11-25 17:09:25 -080055_ANDROID_DIR = '/system/chrome'
56_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
57
David James2cb34002013-03-01 18:42:40 -080058_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070059_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080060
Pawel Osciakc1bb2742014-12-29 16:32:33 +090061_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
62 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
Thiago Goncales12793312013-05-23 11:26:17 -070063_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
64_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070065
Steve Funge984a532013-11-25 17:09:25 -080066DF_COMMAND = 'df -k %s'
Steve Funge984a532013-11-25 17:09:25 -080067
Mike Frysingere65f3752014-12-08 00:46:39 -050068
Ryan Cui3045c5d2012-07-13 18:00:33 -070069def _UrlBaseName(url):
70 """Return the last component of the URL."""
71 return url.rstrip('/').rpartition('/')[-1]
72
73
Yu-Ju Hongc54d3342014-05-14 12:42:06 -070074class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -080075 """Raised whenever the deploy fails."""
76
77
Ryan Cui7193a7e2013-04-26 14:15:19 -070078DeviceInfo = collections.namedtuple(
79 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
80
81
Ryan Cui3045c5d2012-07-13 18:00:33 -070082class DeployChrome(object):
83 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -050084
Ryan Cuia56a71e2012-10-18 18:40:35 -070085 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070086 """Initialize the class.
87
Mike Frysinger02e1e072013-11-10 22:11:34 -050088 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -040089 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -070090 tempdir: Scratch space for the class. Caller has responsibility to clean
91 it up.
Steve Funge984a532013-11-25 17:09:25 -080092 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -070093 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070094 self.tempdir = tempdir
95 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070096 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -050097 if not self.options.staging_only:
Pawel Osciak141b2262014-12-21 10:27:05 +090098 self.device = remote.RemoteDevice(options.to, port=options.port,
99 ping=options.ping)
Robert Flack1dc7ea82014-11-26 13:50:24 -0500100 self._target_dir_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700101
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700102 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800103 self.chrome_dir = _CHROME_DIR
104
Ryan Cui7193a7e2013-04-26 14:15:19 -0700105 def _GetRemoteMountFree(self, remote_dir):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500106 result = self.device.RunCommand(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700107 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800108 value = line.split()[3]
109 multipliers = {
110 'G': 1024 * 1024 * 1024,
111 'M': 1024 * 1024,
112 'K': 1024,
113 }
114 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700115
116 def _GetRemoteDirSize(self, remote_dir):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500117 result = self.device.RunCommand('du -ks %s' % remote_dir,
118 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700119 return int(result.output.split()[0])
120
121 def _GetStagingDirSize(self):
122 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800123 redirect_stdout=True,
124 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700125 return int(result.output.split()[0])
126
Ryan Cui3045c5d2012-07-13 18:00:33 -0700127 def _ChromeFileInUse(self):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500128 result = self.device.RunCommand(LSOF_COMMAND % (self.options.target_dir,),
129 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700130 return result.returncode == 0
131
132 def _DisableRootfsVerification(self):
133 if not self.options.force:
134 logging.error('Detected that the device has rootfs verification enabled.')
135 logging.info('This script can automatically remove the rootfs '
136 'verification, which requires that it reboot the device.')
137 logging.info('Make sure the device is in developer mode!')
138 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700139 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800140 # Since we stopped Chrome earlier, it's good form to start it up again.
141 if self.options.startui:
142 logging.info('Starting Chrome...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500143 self.device.RunCommand('start ui')
David James88e6f032013-03-02 08:13:20 -0800144 raise DeployFailure('Need rootfs verification to be disabled. '
145 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700146
147 logging.info('Removing rootfs verification from %s', self.options.to)
148 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
149 # Use --force to bypass the checks.
150 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
151 '--remove_rootfs_verification --force')
152 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500153 self.device.RunCommand(cmd % partition, error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700154
155 # A reboot in developer mode takes a while (and has delays), so the user
156 # will have time to read and act on the USB boot instructions below.
157 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500158 self.device.Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700159
David James88e6f032013-03-02 08:13:20 -0800160 # Now that the machine has been rebooted, we need to kill Chrome again.
161 self._KillProcsIfNeeded()
162
163 # Make sure the rootfs is writable now.
164 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700165
166 def _CheckUiJobStarted(self):
167 # status output is in the format:
168 # <job_name> <status> ['process' <pid>].
169 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800170 try:
Robert Flack1dc7ea82014-11-26 13:50:24 -0500171 result = self.device.RunCommand('status ui', capture_output=True)
Ryan Cuif2d1a582013-02-19 14:08:13 -0800172 except cros_build_lib.RunCommandError as e:
173 if 'Unknown job' in e.result.error:
174 return False
175 else:
176 raise e
177
Ryan Cui3045c5d2012-07-13 18:00:33 -0700178 return result.output.split()[1].split('/')[0] == 'start'
179
180 def _KillProcsIfNeeded(self):
181 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800182 logging.info('Shutting down Chrome...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500183 self.device.RunCommand('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700184
185 # Developers sometimes run session_manager manually, in which case we'll
186 # need to help shut the chrome processes down.
187 try:
David James3432acd2013-11-27 10:02:18 -0800188 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700189 while self._ChromeFileInUse():
190 logging.warning('The chrome binary on the device is in use.')
191 logging.warning('Killing chrome and session_manager processes...\n')
192
Robert Flack1dc7ea82014-11-26 13:50:24 -0500193 self.device.RunCommand("pkill 'chrome|session_manager'",
Mike Frysingere65f3752014-12-08 00:46:39 -0500194 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700195 # Wait for processes to actually terminate
196 time.sleep(POST_KILL_WAIT)
197 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800198 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800199 msg = ('Could not kill processes after %s seconds. Please exit any '
200 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
201 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700202
David James88e6f032013-03-02 08:13:20 -0800203 def _MountRootfsAsWritable(self, error_code_ok=True):
204 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700205
Robert Flack1dc7ea82014-11-26 13:50:24 -0500206 If the command fails, and error_code_ok is True, and the target dir is not
207 writable then this function sets self._target_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700208
Mike Frysinger02e1e072013-11-10 22:11:34 -0500209 Args:
David James88e6f032013-03-02 08:13:20 -0800210 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
211 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800212 # TODO: Should migrate to use the remount functions in remote_access.
Robert Flack1dc7ea82014-11-26 13:50:24 -0500213 result = self.device.RunCommand(MOUNT_RW_COMMAND,
214 error_code_ok=error_code_ok,
215 capture_output=True)
216 if (result.returncode and
Mike Frysinger74ccd572015-05-21 21:18:20 -0400217 not self.device.IsDirWritable(self.options.target_dir)):
Robert Flack1dc7ea82014-11-26 13:50:24 -0500218 self._target_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700219
Ryan Cui7193a7e2013-04-26 14:15:19 -0700220 def _GetDeviceInfo(self):
221 steps = [
222 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
223 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
224 ]
225 return_values = parallel.RunParallelSteps(steps, return_values=True)
226 return DeviceInfo(*return_values)
227
228 def _CheckDeviceFreeSpace(self, device_info):
229 """See if target device has enough space for Chrome.
230
Mike Frysinger02e1e072013-11-10 22:11:34 -0500231 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700232 device_info: A DeviceInfo named tuple.
233 """
234 effective_free = device_info.target_dir_size + device_info.target_fs_free
235 staging_size = self._GetStagingDirSize()
236 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700237 raise DeployFailure(
238 'Not enough free space on the device. Required: %s MiB, '
239 'actual: %s MiB.' % (staging_size / 1024, effective_free / 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700240 if device_info.target_fs_free < (100 * 1024):
241 logging.warning('The device has less than 100MB free. deploy_chrome may '
242 'hang during the transfer.')
243
Ryan Cui3045c5d2012-07-13 18:00:33 -0700244 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800245 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700246 # Show the output (status) for this command.
Steve Funge984a532013-11-25 17:09:25 -0800247 dest_path = _CHROME_DIR
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900248 if not self.device.HasRsync():
249 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700250 'rsync is not found on the device.\n'
251 'Run dev_install on the device to get rsync installed')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500252 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
253 self.options.target_dir,
254 inplace=True, debug_level=logging.INFO,
255 verbose=self.options.verbose)
Steve Funge984a532013-11-25 17:09:25 -0800256
257 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800258 if p.mode:
259 # Set mode if necessary.
Robert Flack1dc7ea82014-11-26 13:50:24 -0500260 self.device.RunCommand('chmod %o %s/%s' % (
261 p.mode, dest_path, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800262
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800263 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800264 logging.info('Starting UI...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500265 self.device.RunCommand('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700266
David James88e6f032013-03-02 08:13:20 -0800267 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700268 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800269 logging.info('Testing connection to the device...')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500270 self.device.RunCommand('true')
David James88e6f032013-03-02 08:13:20 -0800271 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700272 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800273 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700274
Steve Funge984a532013-11-25 17:09:25 -0800275 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700276 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700277 def BinaryExists(filename):
278 """Checks if the passed-in file is present in the build directory."""
279 return os.path.exists(os.path.join(self.options.build_dir, filename))
280
Daniel Erat9813f0e2014-11-12 11:00:28 -0700281 # Handle non-Chrome deployments.
282 if not BinaryExists('chrome'):
283 if BinaryExists('envoy_shell'):
284 self.copy_paths = chrome_util.GetCopyPaths('envoy')
285 elif BinaryExists('app_shell'):
286 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
287
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700288 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Erat9813f0e2014-11-12 11:00:28 -0700289 # {app,envoy}_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700290 self.options.startui = False
291
David James88e6f032013-03-02 08:13:20 -0800292 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800293 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
294 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800295
Thiago Goncales12793312013-05-23 11:26:17 -0700296 def _MountTarget(self):
297 logging.info('Mounting Chrome...')
298
299 # Create directory if does not exist
Robert Flack1dc7ea82014-11-26 13:50:24 -0500300 self.device.RunCommand('mkdir -p --mode 0775 %s' % (
Mike Frysingere65f3752014-12-08 00:46:39 -0500301 self.options.mount_dir,))
Pawel Osciakfb200f52014-12-21 13:42:05 +0900302 # Umount the existing mount on mount_dir if present first
303 self.device.RunCommand(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
304 {'dir': self.options.mount_dir})
Robert Flack1dc7ea82014-11-26 13:50:24 -0500305 self.device.RunCommand(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
306 self.options.mount_dir))
Thiago Goncales12793312013-05-23 11:26:17 -0700307 # Chrome needs partition to have exec and suid flags set
Robert Flack1dc7ea82014-11-26 13:50:24 -0500308 self.device.RunCommand(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
309
310 def Cleanup(self):
311 """Clean up RemoteDevice."""
312 if not self.options.staging_only:
313 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700314
David James88e6f032013-03-02 08:13:20 -0800315 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800316 self._CheckDeployType()
317
David James88e6f032013-03-02 08:13:20 -0800318 # If requested, just do the staging step.
319 if self.options.staging_only:
320 self._PrepareStagingDir()
321 return 0
322
323 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800324 # stop printing output at that point, and halt any running steps.
Steve Funge984a532013-11-25 17:09:25 -0800325 steps = [self._GetDeviceInfo, self._CheckConnection,
326 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
327 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700328 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
329 return_values=True)
330 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800331
Robert Flack1dc7ea82014-11-26 13:50:24 -0500332 # If we're trying to deploy to a dir which is not writable and we failed
333 # to mark the rootfs as writable, try disabling rootfs verification.
334 if self._target_dir_is_still_readonly.is_set():
David James88e6f032013-03-02 08:13:20 -0800335 self._DisableRootfsVerification()
336
Thiago Goncales12793312013-05-23 11:26:17 -0700337 if self.options.mount_dir is not None:
338 self._MountTarget()
339
David James88e6f032013-03-02 08:13:20 -0800340 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700341 self._Deploy()
342
343
Mike Frysingerc3061a62015-06-04 04:16:18 -0400344def ValidateGypDefines(value):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700345 """Convert GYP_DEFINES-formatted string to dictionary."""
346 return chrome_util.ProcessGypDefines(value)
347
348
Ryan Cuie535b172012-10-19 18:25:03 -0700349def _CreateParser():
350 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400351 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700352
Ryan Cuia56a71e2012-10-18 18:40:35 -0700353 # TODO(rcui): Have this use the UI-V2 format of having source and target
354 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400355 parser.add_argument('--force', action='store_true', default=False,
356 help='Skip all prompts (i.e., for disabling of rootfs '
357 'verification). This may result in the target '
358 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800359 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400360 parser.add_argument('--board', default=sdk_board_env,
361 help="The board the Chrome build is targeted for. When "
362 "in a 'cros chrome-sdk' shell, defaults to the SDK "
363 "board.")
364 parser.add_argument('--build-dir', type='path',
365 help='The directory with Chrome build artifacts to '
366 'deploy from. Typically of format '
367 '<chrome_root>/out/Debug. When this option is used, '
368 'the GYP_DEFINES environment variable must be set.')
369 parser.add_argument('--target-dir', type='path',
370 default=None,
371 help='Target directory on device to deploy Chrome into.')
372 parser.add_argument('-g', '--gs-path', type='gs_path',
373 help='GS path that contains the chrome to deploy.')
374 parser.add_argument('--nostartui', action='store_false', dest='startui',
375 default=True,
376 help="Don't restart the ui daemon after deployment.")
377 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
378 default=True,
379 help="Don't strip binaries during deployment. Warning: "
380 'the resulting binaries will be very large!')
381 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
382 help='Port of the target device to connect to.')
383 parser.add_argument('-t', '--to',
384 help='The IP address of the CrOS device to deploy to.')
385 parser.add_argument('-v', '--verbose', action='store_true', default=False,
386 help='Show more debug output.')
387 parser.add_argument('--mount-dir', type='path', default=None,
388 help='Deploy Chrome in target directory and bind it '
389 'to the directory specified by this flag.'
390 'Any existing mount on this directory will be '
391 'umounted first.')
392 parser.add_argument('--mount', action='store_true', default=False,
393 help='Deploy Chrome to default target directory and bind '
394 'it to the default mount directory.'
395 'Any existing mount on this directory will be '
396 'umounted first.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700397
Mike Frysingerc3061a62015-06-04 04:16:18 -0400398 group = parser.add_argument_group('Advanced Options')
399 group.add_argument('-l', '--local-pkg-path', type='path',
400 help='Path to local chrome prebuilt package to deploy.')
401 group.add_argument('--sloppy', action='store_true', default=False,
402 help='Ignore when mandatory artifacts are missing.')
403 group.add_argument('--staging-flags', default=None, type=ValidateGypDefines,
404 help=('Extra flags to control staging. Valid flags are - '
405 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
406 group.add_argument('--strict', action='store_true', default=False,
407 help='Stage artifacts based on the GYP_DEFINES '
408 'environment variable and --staging-flags, if set. '
409 'Enforce that all optional artifacts are deployed.')
410 group.add_argument('--strip-flags', default=None,
411 help="Flags to call the 'strip' binutil tool with. "
412 "Overrides the default arguments.")
413 group.add_argument('--ping', action='store_true', default=False,
414 help='Ping the device before connection attempt.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700415
Mike Frysingerc3061a62015-06-04 04:16:18 -0400416 group = parser.add_argument_group(
417 'Metadata Overrides (Advanced)',
418 description='Provide all of these overrides in order to remove '
419 'dependencies on metadata.json existence.')
420 group.add_argument('--target-tc', action='store', default=None,
421 help='Override target toolchain name, e.g. '
422 'x86_64-cros-linux-gnu')
423 group.add_argument('--toolchain-url', action='store', default=None,
424 help='Override toolchain url format pattern, e.g. '
425 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700426
Ryan Cuif890a3e2013-03-07 18:57:06 -0800427 # GYP_DEFINES that Chrome was built with. Influences which files are staged
428 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
429 # enviroment variable.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400430 parser.add_argument('--gyp-defines', default=None, type=ValidateGypDefines,
431 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700432 # Path of an empty directory to stage chrome artifacts to. Defaults to a
433 # temporary directory that is removed when the script finishes. If the path
434 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400435 parser.add_argument('--staging-dir', type='path', default=None,
436 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700437 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400438 parser.add_argument('--staging-only', action='store_true', default=False,
439 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700440 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
441 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
442 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400443 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Ryan Cuie535b172012-10-19 18:25:03 -0700444 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700445
Ryan Cuie535b172012-10-19 18:25:03 -0700446
447def _ParseCommandLine(argv):
448 """Parse args, and run environment-independent checks."""
449 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400450 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700451
Ryan Cuia56a71e2012-10-18 18:40:35 -0700452 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
453 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800454 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700455 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
456 parser.error('Cannot specify both --build_dir and '
457 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800458 if options.build_dir and not options.board:
459 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700460 if options.gs_path and options.local_pkg_path:
461 parser.error('Cannot specify both --gs-path and --local-pkg-path')
462 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700463 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800464 if (options.strict or options.staging_flags) and not options.build_dir:
465 parser.error('--strict and --staging-flags require --build-dir to be '
466 'set.')
467 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800468 parser.error('--staging-flags requires --strict to be set.')
469 if options.sloppy and options.strict:
470 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700471
472 if options.mount or options.mount_dir:
473 if not options.target_dir:
474 options.target_dir = _CHROME_DIR_MOUNT
475 else:
476 if not options.target_dir:
477 options.target_dir = _CHROME_DIR
478
479 if options.mount and not options.mount_dir:
480 options.mount_dir = _CHROME_DIR
481
Mike Frysingerc3061a62015-06-04 04:16:18 -0400482 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700483
484
Mike Frysingerc3061a62015-06-04 04:16:18 -0400485def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800486 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700487
488 Args:
Steve Funge984a532013-11-25 17:09:25 -0800489 options: The options object returned by optparse.
490 _args: The args object returned by optparse.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700491 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700492 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
493 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
494
Ryan Cuib623e7b2013-03-14 12:54:11 -0700495 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700496 gyp_env = os.getenv('GYP_DEFINES', None)
497 if gyp_env is not None:
498 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700499 logging.debug('GYP_DEFINES taken from environment: %s',
Mike Frysingere65f3752014-12-08 00:46:39 -0500500 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700501
502 if options.strict and not options.gyp_defines:
503 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Mike Frysingere65f3752014-12-08 00:46:39 -0500504 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700505
506
Ryan Cui504db722013-01-22 11:48:01 -0800507def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700508 """Get the chrome prebuilt tarball from GS.
509
Mike Frysinger02e1e072013-11-10 22:11:34 -0500510 Returns:
511 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700512 """
David James9374aac2013-10-08 16:00:17 -0700513 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500514 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800515 files = [found for found in files if
516 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
517 if not files:
518 raise Exception('No chrome package found at %s' % gs_path)
519 elif len(files) > 1:
520 # - Users should provide us with a direct link to either a stripped or
521 # unstripped chrome package.
522 # - In the case of being provided with an archive directory, where both
523 # stripped and unstripped chrome available, use the stripped chrome
524 # package.
525 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
526 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
527 files = [f for f in files if not 'unstripped' in f]
528 assert len(files) == 1
529 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800530
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800531 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800532 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800533 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
534 chrome_path = os.path.join(tempdir, filename)
535 assert os.path.exists(chrome_path)
536 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700537
538
Ryan Cuif890a3e2013-03-07 18:57:06 -0800539@contextlib.contextmanager
540def _StripBinContext(options):
541 if not options.dostrip:
542 yield None
543 elif options.strip_bin:
544 yield options.strip_bin
545 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800546 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800547 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700548 with sdk.Prepare(components=components, target_tc=options.target_tc,
549 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800550 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
551 constants.CHROME_ENV_FILE)
552 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
553 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
554 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800555 yield strip_bin
556
557
Steve Funge984a532013-11-25 17:09:25 -0800558def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
559 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800560 """Place the necessary files in the staging directory.
561
562 The staging directory is the directory used to rsync the build artifacts over
563 to the device. Only the necessary Chrome build artifacts are put into the
564 staging directory.
565 """
Ryan Cui5866be02013-03-18 14:12:00 -0700566 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400567 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800568 if options.build_dir:
569 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700570 strip_flags = (None if options.strip_flags is None else
571 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800572 chrome_util.StageChromeFromBuildDir(
573 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800574 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700575 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800576 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700577 else:
578 pkg_path = options.local_pkg_path
579 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800580 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
581 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700582
583 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800584 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700585 # Extract only the ./opt/google/chrome contents, directly into the staging
586 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800587 if pkg_path[-4:] == '.zip':
588 cros_build_lib.DebugRunCommand(
589 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
590 staging_dir])
591 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
592 shutil.move(filename, staging_dir)
593 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
594 else:
595 cros_build_lib.DebugRunCommand(
596 ['tar', '--strip-components', '4', '--extract',
597 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
598 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700599
Ryan Cui71aa8de2013-04-19 16:12:55 -0700600
Ryan Cui3045c5d2012-07-13 18:00:33 -0700601def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400602 options = _ParseCommandLine(argv)
603 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700604
605 # Set cros_build_lib debug level to hide RunCommand spew.
606 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700607 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700608 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800609 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700610
Ryan Cui71aa8de2013-04-19 16:12:55 -0700611 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700612 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
613 if cmd_stats:
614 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700615
Ryan Cui71aa8de2013-04-19 16:12:55 -0700616 with osutils.TempDir(set_global=True) as tempdir:
617 staging_dir = options.staging_dir
618 if not staging_dir:
619 staging_dir = os.path.join(tempdir, 'chrome')
620
621 deploy = DeployChrome(options, tempdir, staging_dir)
622 try:
623 deploy.Perform()
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700624 except failures_lib.StepFailure as ex:
Ryan Cui71aa8de2013-04-19 16:12:55 -0700625 raise SystemExit(str(ex).strip())
Robert Flack1dc7ea82014-11-26 13:50:24 -0500626 deploy.Cleanup()