blob: 004514546ae28874d5ffb7eeaa5c752bdb7e96f8 [file] [log] [blame]
Ryan Cui3045c5d2012-07-13 18:00:33 -07001#!/usr/bin/python
2# 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
Ryan Cuia56a71e2012-10-18 18:40:35 -07006
Steve Funge984a532013-11-25 17:09:25 -08007"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07008
9The script supports deploying Chrome from these sources:
10
111. A local build output directory, such as chromium/src/out/[Debug|Release].
122. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
133. A Chrome tarball existing locally.
14
15The script copies the necessary contents of the source location (tarball or
16build directory) and rsyncs the contents of the staging directory onto your
17device's rootfs.
18"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070019
Ryan Cui7193a7e2013-04-26 14:15:19 -070020import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080021import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070022import functools
Steve Funge984a532013-11-25 17:09:25 -080023import glob
Ryan Cui3045c5d2012-07-13 18:00:33 -070024import logging
David James88e6f032013-03-02 08:13:20 -080025import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070026import os
Ryan Cuia56a71e2012-10-18 18:40:35 -070027import optparse
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070028import shlex
Steve Funge984a532013-11-25 17:09:25 -080029import shutil
Ryan Cui3045c5d2012-07-13 18:00:33 -070030import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070031
Ryan Cuia56a71e2012-10-18 18:40:35 -070032
Don Garrett88b8d782014-05-13 17:30:55 -070033from chromite.cbuildbot import constants
David James14e97772014-06-04 18:44:49 -070034from chromite.cbuildbot import failures_lib
Ryan Cui686ec052013-02-12 16:39:41 -080035from chromite.cros.commands import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070036from chromite.lib import chrome_util
Ryan Cui3045c5d2012-07-13 18:00:33 -070037from chromite.lib import cros_build_lib
Ryan Cuie535b172012-10-19 18:25:03 -070038from chromite.lib import commandline
Ryan Cui777ff422012-12-07 13:12:54 -080039from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070040from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080041from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070042from chromite.lib import remote_access as remote
Ryan Cui71aa8de2013-04-19 16:12:55 -070043from chromite.lib import stats
David James3432acd2013-11-27 10:02:18 -080044from chromite.lib import timeout_util
Ryan Cui3c183c22013-04-29 18:04:11 -070045from chromite.scripts import lddtree
Ryan Cui3045c5d2012-07-13 18:00:33 -070046
47
Ryan Cuia56a71e2012-10-18 18:40:35 -070048_USAGE = "deploy_chrome [--]\n\n %s" % __doc__
49
Ryan Cui3045c5d2012-07-13 18:00:33 -070050KERNEL_A_PARTITION = 2
51KERNEL_B_PARTITION = 4
52
53KILL_PROC_MAX_WAIT = 10
54POST_KILL_WAIT = 2
55
Ryan Cuie535b172012-10-19 18:25:03 -070056MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080057LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070058
Steve Funge984a532013-11-25 17:09:25 -080059_ANDROID_DIR = '/system/chrome'
60_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
61
David James2cb34002013-03-01 18:42:40 -080062_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070063_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080064
Thiago Goncales12793312013-05-23 11:26:17 -070065_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
66_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070067
Steve Funge984a532013-11-25 17:09:25 -080068DF_COMMAND = 'df -k %s'
Steve Funge984a532013-11-25 17:09:25 -080069
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."""
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:
Ryan Cuie535b172012-10-19 18:25:03 -070089 options: Optparse result structure.
90 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
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070097 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080098 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -070099
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700100 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800101 self.chrome_dir = _CHROME_DIR
102
Ryan Cui7193a7e2013-04-26 14:15:19 -0700103 def _GetRemoteMountFree(self, remote_dir):
Daniel Erat1ae46382014-08-14 10:23:39 -0700104 result = self.host.RemoteSh(DF_COMMAND % remote_dir, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700105 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800106 value = line.split()[3]
107 multipliers = {
108 'G': 1024 * 1024 * 1024,
109 'M': 1024 * 1024,
110 'K': 1024,
111 }
112 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700113
114 def _GetRemoteDirSize(self, remote_dir):
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800115 result = self.host.RemoteSh('du -ks %s' % remote_dir, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700116 return int(result.output.split()[0])
117
118 def _GetStagingDirSize(self):
119 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800120 redirect_stdout=True,
121 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700122 return int(result.output.split()[0])
123
Ryan Cui3045c5d2012-07-13 18:00:33 -0700124 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800125 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800126 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700127 return result.returncode == 0
128
129 def _DisableRootfsVerification(self):
130 if not self.options.force:
131 logging.error('Detected that the device has rootfs verification enabled.')
132 logging.info('This script can automatically remove the rootfs '
133 'verification, which requires that it reboot the device.')
134 logging.info('Make sure the device is in developer mode!')
135 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700136 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800137 # Since we stopped Chrome earlier, it's good form to start it up again.
138 if self.options.startui:
139 logging.info('Starting Chrome...')
140 self.host.RemoteSh('start ui')
141 raise DeployFailure('Need rootfs verification to be disabled. '
142 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700143
144 logging.info('Removing rootfs verification from %s', self.options.to)
145 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
146 # Use --force to bypass the checks.
147 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
148 '--remove_rootfs_verification --force')
149 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
150 self.host.RemoteSh(cmd % partition, error_code_ok=True)
151
152 # A reboot in developer mode takes a while (and has delays), so the user
153 # will have time to read and act on the USB boot instructions below.
154 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
155 self.host.RemoteReboot()
156
David James88e6f032013-03-02 08:13:20 -0800157 # Now that the machine has been rebooted, we need to kill Chrome again.
158 self._KillProcsIfNeeded()
159
160 # Make sure the rootfs is writable now.
161 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700162
163 def _CheckUiJobStarted(self):
164 # status output is in the format:
165 # <job_name> <status> ['process' <pid>].
166 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800167 try:
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800168 result = self.host.RemoteSh('status ui', capture_output=True)
Ryan Cuif2d1a582013-02-19 14:08:13 -0800169 except cros_build_lib.RunCommandError as e:
170 if 'Unknown job' in e.result.error:
171 return False
172 else:
173 raise e
174
Ryan Cui3045c5d2012-07-13 18:00:33 -0700175 return result.output.split()[1].split('/')[0] == 'start'
176
177 def _KillProcsIfNeeded(self):
178 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800179 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700180 self.host.RemoteSh('stop ui')
181
182 # Developers sometimes run session_manager manually, in which case we'll
183 # need to help shut the chrome processes down.
184 try:
David James3432acd2013-11-27 10:02:18 -0800185 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700186 while self._ChromeFileInUse():
187 logging.warning('The chrome binary on the device is in use.')
188 logging.warning('Killing chrome and session_manager processes...\n')
189
190 self.host.RemoteSh("pkill 'chrome|session_manager'",
191 error_code_ok=True)
192 # Wait for processes to actually terminate
193 time.sleep(POST_KILL_WAIT)
194 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800195 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800196 msg = ('Could not kill processes after %s seconds. Please exit any '
197 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
198 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700199
David James88e6f032013-03-02 08:13:20 -0800200 def _MountRootfsAsWritable(self, error_code_ok=True):
201 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700202
David James88e6f032013-03-02 08:13:20 -0800203 If the command fails, and error_code_ok is True, then this function sets
204 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700205
Mike Frysinger02e1e072013-11-10 22:11:34 -0500206 Args:
David James88e6f032013-03-02 08:13:20 -0800207 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
208 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800209 # TODO: Should migrate to use the remount functions in remote_access.
Daniel Erat1ae46382014-08-14 10:23:39 -0700210 result = self.host.RemoteSh(MOUNT_RW_COMMAND,
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800211 error_code_ok=error_code_ok,
212 capture_output=True)
David James88e6f032013-03-02 08:13:20 -0800213 if result.returncode:
214 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700215
Ryan Cui7193a7e2013-04-26 14:15:19 -0700216 def _GetDeviceInfo(self):
217 steps = [
218 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
219 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
220 ]
221 return_values = parallel.RunParallelSteps(steps, return_values=True)
222 return DeviceInfo(*return_values)
223
224 def _CheckDeviceFreeSpace(self, device_info):
225 """See if target device has enough space for Chrome.
226
Mike Frysinger02e1e072013-11-10 22:11:34 -0500227 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700228 device_info: A DeviceInfo named tuple.
229 """
230 effective_free = device_info.target_dir_size + device_info.target_fs_free
231 staging_size = self._GetStagingDirSize()
232 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700233 raise DeployFailure(
234 'Not enough free space on the device. Required: %s MiB, '
235 'actual: %s MiB.' % (staging_size / 1024, effective_free / 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700236 if device_info.target_fs_free < (100 * 1024):
237 logging.warning('The device has less than 100MB free. deploy_chrome may '
238 'hang during the transfer.')
239
Ryan Cui3045c5d2012-07-13 18:00:33 -0700240 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800241 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700242 # Show the output (status) for this command.
Steve Funge984a532013-11-25 17:09:25 -0800243 dest_path = _CHROME_DIR
Daniel Erat1ae46382014-08-14 10:23:39 -0700244 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
245 self.options.target_dir,
246 inplace=True, debug_level=logging.INFO,
247 verbose=self.options.verbose)
Steve Funge984a532013-11-25 17:09:25 -0800248
249 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800250 if p.mode:
251 # Set mode if necessary.
252 self.host.RemoteSh('chmod %o %s/%s' % (p.mode, dest_path,
253 p.src if not p.dest else p.dest))
254
255
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800256 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800257 logging.info('Starting UI...')
Daniel Erat1ae46382014-08-14 10:23:39 -0700258 self.host.RemoteSh('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700259
David James88e6f032013-03-02 08:13:20 -0800260 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700261 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800262 logging.info('Testing connection to the device...')
Daniel Erat1ae46382014-08-14 10:23:39 -0700263 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800264 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700265 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800266 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700267
Steve Funge984a532013-11-25 17:09:25 -0800268 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700269 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700270 def BinaryExists(filename):
271 """Checks if the passed-in file is present in the build directory."""
272 return os.path.exists(os.path.join(self.options.build_dir, filename))
273
Daniel Erat1ae46382014-08-14 10:23:39 -0700274 if BinaryExists('app_shell') and not BinaryExists('chrome'):
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700275 # app_shell deployment.
276 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
277 # TODO(derat): Update _Deploy() and remove this after figuring out how
278 # app_shell should be executed.
279 self.options.startui = False
280
David James88e6f032013-03-02 08:13:20 -0800281 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800282 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
283 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800284
Thiago Goncales12793312013-05-23 11:26:17 -0700285 def _MountTarget(self):
286 logging.info('Mounting Chrome...')
287
288 # Create directory if does not exist
289 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
290 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
291 self.options.mount_dir))
292 # Chrome needs partition to have exec and suid flags set
293 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
294
David James88e6f032013-03-02 08:13:20 -0800295 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800296 self._CheckDeployType()
297
David James88e6f032013-03-02 08:13:20 -0800298 # If requested, just do the staging step.
299 if self.options.staging_only:
300 self._PrepareStagingDir()
301 return 0
302
303 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800304 # stop printing output at that point, and halt any running steps.
Steve Funge984a532013-11-25 17:09:25 -0800305 steps = [self._GetDeviceInfo, self._CheckConnection,
306 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
307 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700308 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
309 return_values=True)
310 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800311
312 # If we failed to mark the rootfs as writable, try disabling rootfs
313 # verification.
314 if self._rootfs_is_still_readonly.is_set():
315 self._DisableRootfsVerification()
316
Thiago Goncales12793312013-05-23 11:26:17 -0700317 if self.options.mount_dir is not None:
318 self._MountTarget()
319
David James88e6f032013-03-02 08:13:20 -0800320 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700321 self._Deploy()
322
323
Ryan Cuia56a71e2012-10-18 18:40:35 -0700324def ValidateGypDefines(_option, _opt, value):
325 """Convert GYP_DEFINES-formatted string to dictionary."""
326 return chrome_util.ProcessGypDefines(value)
327
328
329class CustomOption(commandline.Option):
330 """Subclass Option class to implement path evaluation."""
331 TYPES = commandline.Option.TYPES + ('gyp_defines',)
332 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
333 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
334
335
Ryan Cuie535b172012-10-19 18:25:03 -0700336def _CreateParser():
337 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800338 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
339 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700340
Ryan Cuia56a71e2012-10-18 18:40:35 -0700341 # TODO(rcui): Have this use the UI-V2 format of having source and target
342 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700343 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800344 help='Skip all prompts (i.e., for disabling of rootfs '
345 'verification). This may result in the target '
346 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800347 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
348 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800349 help="The board the Chrome build is targeted for. When in "
350 "a 'cros chrome-sdk' shell, defaults to the SDK "
351 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700352 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800353 help='The directory with Chrome build artifacts to deploy '
354 'from. Typically of format <chrome_root>/out/Debug. '
355 'When this option is used, the GYP_DEFINES '
356 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800357 parser.add_option('--target-dir', type='path',
358 help='Target directory on device to deploy Chrome into.',
Thiago Goncales12793312013-05-23 11:26:17 -0700359 default=None)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700360 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800361 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800362 parser.add_option('--nostartui', action='store_false', dest='startui',
363 default=True,
364 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800365 parser.add_option('--nostrip', action='store_false', dest='dostrip',
366 default=True,
367 help="Don't strip binaries during deployment. Warning: "
368 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700369 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800370 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700371 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800372 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700373 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800374 help='Show more debug output.')
Thiago Goncales12793312013-05-23 11:26:17 -0700375 parser.add_option('--mount-dir', type='path', default=None,
376 help='Deploy Chrome in target directory and bind it'
377 'to directory specified by this flag.')
378 parser.add_option('--mount', action='store_true', default=False,
379 help='Deploy Chrome to default target directory and bind it'
380 'to default mount directory.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700381
382 group = optparse.OptionGroup(parser, 'Advanced Options')
383 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800384 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800385 group.add_option('--sloppy', action='store_true', default=False,
386 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700387 group.add_option('--staging-flags', default=None, type='gyp_defines',
388 help='Extra flags to control staging. Valid flags are - %s'
389 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800390 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800391 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800392 'variable and --staging-flags, if set. Enforce that '
393 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700394 group.add_option('--strip-flags', default=None,
395 help="Flags to call the 'strip' binutil tool with. "
396 "Overrides the default arguments.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700397 parser.add_option_group(group)
398
Aviv Keshet1c986f32014-04-24 13:20:49 -0700399 group = optparse.OptionGroup(parser, 'Metadata Overrides (Advanced)',
400 description='Provide all of these overrides '
401 'in order to remove dependencies on '
402 'metadata.json existence.')
403 group.add_option('--target-tc', action='store', default=None,
404 help='Override target toolchain name, e.g. '
405 'x86_64-cros-linux-gnu')
406 group.add_option('--toolchain-url', action='store', default=None,
407 help='Override toolchain url format pattern, e.g. '
408 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
409 parser.add_option_group(group)
410
411
Ryan Cuif890a3e2013-03-07 18:57:06 -0800412 # GYP_DEFINES that Chrome was built with. Influences which files are staged
413 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
414 # enviroment variable.
415 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
416 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700417 # Path of an empty directory to stage chrome artifacts to. Defaults to a
418 # temporary directory that is removed when the script finishes. If the path
419 # is specified, then it will not be removed.
420 parser.add_option('--staging-dir', type='path', default=None,
421 help=optparse.SUPPRESS_HELP)
422 # Only prepare the staging directory, and skip deploying to the device.
423 parser.add_option('--staging-only', action='store_true', default=False,
424 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700425 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
426 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
427 # fetching the SDK toolchain.
428 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700429 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700430
Ryan Cuie535b172012-10-19 18:25:03 -0700431
432def _ParseCommandLine(argv):
433 """Parse args, and run environment-independent checks."""
434 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700435 (options, args) = parser.parse_args(argv)
436
Ryan Cuia56a71e2012-10-18 18:40:35 -0700437 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
438 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800439 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700440 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
441 parser.error('Cannot specify both --build_dir and '
442 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800443 if options.build_dir and not options.board:
444 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700445 if options.gs_path and options.local_pkg_path:
446 parser.error('Cannot specify both --gs-path and --local-pkg-path')
447 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700448 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800449 if (options.strict or options.staging_flags) and not options.build_dir:
450 parser.error('--strict and --staging-flags require --build-dir to be '
451 'set.')
452 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800453 parser.error('--staging-flags requires --strict to be set.')
454 if options.sloppy and options.strict:
455 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700456
457 if options.mount or options.mount_dir:
458 if not options.target_dir:
459 options.target_dir = _CHROME_DIR_MOUNT
460 else:
461 if not options.target_dir:
462 options.target_dir = _CHROME_DIR
463
464 if options.mount and not options.mount_dir:
465 options.mount_dir = _CHROME_DIR
466
Ryan Cui3045c5d2012-07-13 18:00:33 -0700467 return options, args
468
469
Ryan Cuie535b172012-10-19 18:25:03 -0700470def _PostParseCheck(options, _args):
Steve Funge984a532013-11-25 17:09:25 -0800471 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700472
473 Args:
Steve Funge984a532013-11-25 17:09:25 -0800474 options: The options object returned by optparse.
475 _args: The args object returned by optparse.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700476 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700477 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
478 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
479
Ryan Cuib623e7b2013-03-14 12:54:11 -0700480 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700481 gyp_env = os.getenv('GYP_DEFINES', None)
482 if gyp_env is not None:
483 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700484 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700485 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700486
487 if options.strict and not options.gyp_defines:
488 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800489 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700490
Ryan Cui3c183c22013-04-29 18:04:11 -0700491 if options.build_dir:
492 chrome_path = os.path.join(options.build_dir, 'chrome')
493 if os.path.isfile(chrome_path):
494 deps = lddtree.ParseELF(chrome_path)
495 if 'libbase.so' in deps['libs']:
496 cros_build_lib.Warning(
497 'Detected a component build of Chrome. component build is '
498 'not working properly for Chrome OS. See crbug.com/196317. '
499 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700500
Ryan Cuia56a71e2012-10-18 18:40:35 -0700501
Ryan Cui504db722013-01-22 11:48:01 -0800502def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700503 """Get the chrome prebuilt tarball from GS.
504
Mike Frysinger02e1e072013-11-10 22:11:34 -0500505 Returns:
506 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700507 """
David James9374aac2013-10-08 16:00:17 -0700508 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500509 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800510 files = [found for found in files if
511 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
512 if not files:
513 raise Exception('No chrome package found at %s' % gs_path)
514 elif len(files) > 1:
515 # - Users should provide us with a direct link to either a stripped or
516 # unstripped chrome package.
517 # - In the case of being provided with an archive directory, where both
518 # stripped and unstripped chrome available, use the stripped chrome
519 # package.
520 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
521 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
522 files = [f for f in files if not 'unstripped' in f]
523 assert len(files) == 1
524 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800525
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800526 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800527 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800528 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
529 chrome_path = os.path.join(tempdir, filename)
530 assert os.path.exists(chrome_path)
531 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700532
533
Ryan Cuif890a3e2013-03-07 18:57:06 -0800534@contextlib.contextmanager
535def _StripBinContext(options):
536 if not options.dostrip:
537 yield None
538 elif options.strip_bin:
539 yield options.strip_bin
540 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800541 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800542 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700543 with sdk.Prepare(components=components, target_tc=options.target_tc,
544 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800545 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
546 constants.CHROME_ENV_FILE)
547 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
548 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
549 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800550 yield strip_bin
551
552
Steve Funge984a532013-11-25 17:09:25 -0800553def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
554 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800555 """Place the necessary files in the staging directory.
556
557 The staging directory is the directory used to rsync the build artifacts over
558 to the device. Only the necessary Chrome build artifacts are put into the
559 staging directory.
560 """
Ryan Cui5866be02013-03-18 14:12:00 -0700561 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400562 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800563 if options.build_dir:
564 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700565 strip_flags = (None if options.strip_flags is None else
566 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800567 chrome_util.StageChromeFromBuildDir(
568 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800569 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700570 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800571 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700572 else:
573 pkg_path = options.local_pkg_path
574 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800575 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
576 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700577
578 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800579 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700580 # Extract only the ./opt/google/chrome contents, directly into the staging
581 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800582 if pkg_path[-4:] == '.zip':
583 cros_build_lib.DebugRunCommand(
584 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
585 staging_dir])
586 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
587 shutil.move(filename, staging_dir)
588 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
589 else:
590 cros_build_lib.DebugRunCommand(
591 ['tar', '--strip-components', '4', '--extract',
592 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
593 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700594
Ryan Cui71aa8de2013-04-19 16:12:55 -0700595
Ryan Cui3045c5d2012-07-13 18:00:33 -0700596def main(argv):
597 options, args = _ParseCommandLine(argv)
598 _PostParseCheck(options, args)
599
600 # Set cros_build_lib debug level to hide RunCommand spew.
601 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700602 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700603 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800604 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700605
Ryan Cui71aa8de2013-04-19 16:12:55 -0700606 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700607 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
608 if cmd_stats:
609 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700610
Ryan Cui71aa8de2013-04-19 16:12:55 -0700611 with osutils.TempDir(set_global=True) as tempdir:
612 staging_dir = options.staging_dir
613 if not staging_dir:
614 staging_dir = os.path.join(tempdir, 'chrome')
615
616 deploy = DeployChrome(options, tempdir, staging_dir)
617 try:
618 deploy.Perform()
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700619 except failures_lib.StepFailure as ex:
Ryan Cui71aa8de2013-04-19 16:12:55 -0700620 raise SystemExit(str(ex).strip())