blob: 33d42aad4023e4e8bc953acc38042b6c1dbda2b2 [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
Ryan Cuia56a71e2012-10-18 18:40:35 -07005
Steve Funge984a532013-11-25 17:09:25 -08006"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07007
8The script supports deploying Chrome from these sources:
9
101. A local build output directory, such as chromium/src/out/[Debug|Release].
112. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
123. A Chrome tarball existing locally.
13
14The script copies the necessary contents of the source location (tarball or
15build directory) and rsyncs the contents of the staging directory onto your
16device's rootfs.
17"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070018
Mike Frysinger383367e2014-09-16 15:06:17 -040019from __future__ import print_function
20
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
Ryan Cui3045c5d2012-07-13 18:00:33 -070025import logging
David James88e6f032013-03-02 08:13:20 -080026import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070027import os
Ryan Cuia56a71e2012-10-18 18:40:35 -070028import optparse
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070029import shlex
Steve Funge984a532013-11-25 17:09:25 -080030import shutil
Ryan Cui3045c5d2012-07-13 18:00:33 -070031import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070032
Ryan Cuia56a71e2012-10-18 18:40:35 -070033
Don Garrett88b8d782014-05-13 17:30:55 -070034from chromite.cbuildbot import constants
David James14e97772014-06-04 18:44:49 -070035from chromite.cbuildbot import failures_lib
Ryan Cui686ec052013-02-12 16:39:41 -080036from chromite.cros.commands import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070037from chromite.lib import chrome_util
Ryan Cui3045c5d2012-07-13 18:00:33 -070038from chromite.lib import cros_build_lib
Ryan Cuie535b172012-10-19 18:25:03 -070039from chromite.lib import commandline
Ryan Cui777ff422012-12-07 13:12:54 -080040from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070041from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080042from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070043from chromite.lib import remote_access as remote
Ryan Cui71aa8de2013-04-19 16:12:55 -070044from chromite.lib import stats
David James3432acd2013-11-27 10:02:18 -080045from chromite.lib import timeout_util
Ryan Cui3c183c22013-04-29 18:04:11 -070046from chromite.scripts import lddtree
Ryan Cui3045c5d2012-07-13 18:00:33 -070047
48
Ryan Cuia56a71e2012-10-18 18:40:35 -070049_USAGE = "deploy_chrome [--]\n\n %s" % __doc__
50
Ryan Cui3045c5d2012-07-13 18:00:33 -070051KERNEL_A_PARTITION = 2
52KERNEL_B_PARTITION = 4
53
54KILL_PROC_MAX_WAIT = 10
55POST_KILL_WAIT = 2
56
Ryan Cuie535b172012-10-19 18:25:03 -070057MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080058LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070059
Steve Funge984a532013-11-25 17:09:25 -080060_ANDROID_DIR = '/system/chrome'
61_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
62
David James2cb34002013-03-01 18:42:40 -080063_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070064_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080065
Thiago Goncales12793312013-05-23 11:26:17 -070066_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
67_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070068
Steve Funge984a532013-11-25 17:09:25 -080069DF_COMMAND = 'df -k %s'
Steve Funge984a532013-11-25 17:09:25 -080070
Ryan Cui3045c5d2012-07-13 18:00:33 -070071def _UrlBaseName(url):
72 """Return the last component of the URL."""
73 return url.rstrip('/').rpartition('/')[-1]
74
75
Yu-Ju Hongc54d3342014-05-14 12:42:06 -070076class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -080077 """Raised whenever the deploy fails."""
78
79
Ryan Cui7193a7e2013-04-26 14:15:19 -070080DeviceInfo = collections.namedtuple(
81 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
82
83
Ryan Cui3045c5d2012-07-13 18:00:33 -070084class DeployChrome(object):
85 """Wraps the core deployment functionality."""
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:
Ryan Cuie535b172012-10-19 18:25:03 -070090 options: Optparse result structure.
91 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
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070098 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080099 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700100
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700101 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800102 self.chrome_dir = _CHROME_DIR
103
Ryan Cui7193a7e2013-04-26 14:15:19 -0700104 def _GetRemoteMountFree(self, remote_dir):
Daniel Erat1ae46382014-08-14 10:23:39 -0700105 result = self.host.RemoteSh(DF_COMMAND % remote_dir, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700106 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800107 value = line.split()[3]
108 multipliers = {
109 'G': 1024 * 1024 * 1024,
110 'M': 1024 * 1024,
111 'K': 1024,
112 }
113 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700114
115 def _GetRemoteDirSize(self, remote_dir):
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800116 result = self.host.RemoteSh('du -ks %s' % remote_dir, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700117 return int(result.output.split()[0])
118
119 def _GetStagingDirSize(self):
120 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800121 redirect_stdout=True,
122 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700123 return int(result.output.split()[0])
124
Ryan Cui3045c5d2012-07-13 18:00:33 -0700125 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800126 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800127 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700128 return result.returncode == 0
129
130 def _DisableRootfsVerification(self):
131 if not self.options.force:
132 logging.error('Detected that the device has rootfs verification enabled.')
133 logging.info('This script can automatically remove the rootfs '
134 'verification, which requires that it reboot the device.')
135 logging.info('Make sure the device is in developer mode!')
136 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700137 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800138 # Since we stopped Chrome earlier, it's good form to start it up again.
139 if self.options.startui:
140 logging.info('Starting Chrome...')
141 self.host.RemoteSh('start ui')
142 raise DeployFailure('Need rootfs verification to be disabled. '
143 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700144
145 logging.info('Removing rootfs verification from %s', self.options.to)
146 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
147 # Use --force to bypass the checks.
148 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
149 '--remove_rootfs_verification --force')
150 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
151 self.host.RemoteSh(cmd % partition, error_code_ok=True)
152
153 # A reboot in developer mode takes a while (and has delays), so the user
154 # will have time to read and act on the USB boot instructions below.
155 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
156 self.host.RemoteReboot()
157
David James88e6f032013-03-02 08:13:20 -0800158 # Now that the machine has been rebooted, we need to kill Chrome again.
159 self._KillProcsIfNeeded()
160
161 # Make sure the rootfs is writable now.
162 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700163
164 def _CheckUiJobStarted(self):
165 # status output is in the format:
166 # <job_name> <status> ['process' <pid>].
167 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800168 try:
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800169 result = self.host.RemoteSh('status ui', capture_output=True)
Ryan Cuif2d1a582013-02-19 14:08:13 -0800170 except cros_build_lib.RunCommandError as e:
171 if 'Unknown job' in e.result.error:
172 return False
173 else:
174 raise e
175
Ryan Cui3045c5d2012-07-13 18:00:33 -0700176 return result.output.split()[1].split('/')[0] == 'start'
177
178 def _KillProcsIfNeeded(self):
179 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800180 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700181 self.host.RemoteSh('stop ui')
182
183 # Developers sometimes run session_manager manually, in which case we'll
184 # need to help shut the chrome processes down.
185 try:
David James3432acd2013-11-27 10:02:18 -0800186 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700187 while self._ChromeFileInUse():
188 logging.warning('The chrome binary on the device is in use.')
189 logging.warning('Killing chrome and session_manager processes...\n')
190
191 self.host.RemoteSh("pkill 'chrome|session_manager'",
192 error_code_ok=True)
193 # Wait for processes to actually terminate
194 time.sleep(POST_KILL_WAIT)
195 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800196 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800197 msg = ('Could not kill processes after %s seconds. Please exit any '
198 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
199 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700200
David James88e6f032013-03-02 08:13:20 -0800201 def _MountRootfsAsWritable(self, error_code_ok=True):
202 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700203
David James88e6f032013-03-02 08:13:20 -0800204 If the command fails, and error_code_ok is True, then this function sets
205 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700206
Mike Frysinger02e1e072013-11-10 22:11:34 -0500207 Args:
David James88e6f032013-03-02 08:13:20 -0800208 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
209 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800210 # TODO: Should migrate to use the remount functions in remote_access.
Daniel Erat1ae46382014-08-14 10:23:39 -0700211 result = self.host.RemoteSh(MOUNT_RW_COMMAND,
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800212 error_code_ok=error_code_ok,
213 capture_output=True)
David James88e6f032013-03-02 08:13:20 -0800214 if result.returncode:
215 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700216
Ryan Cui7193a7e2013-04-26 14:15:19 -0700217 def _GetDeviceInfo(self):
218 steps = [
219 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
220 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
221 ]
222 return_values = parallel.RunParallelSteps(steps, return_values=True)
223 return DeviceInfo(*return_values)
224
225 def _CheckDeviceFreeSpace(self, device_info):
226 """See if target device has enough space for Chrome.
227
Mike Frysinger02e1e072013-11-10 22:11:34 -0500228 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700229 device_info: A DeviceInfo named tuple.
230 """
231 effective_free = device_info.target_dir_size + device_info.target_fs_free
232 staging_size = self._GetStagingDirSize()
233 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700234 raise DeployFailure(
235 'Not enough free space on the device. Required: %s MiB, '
236 'actual: %s MiB.' % (staging_size / 1024, effective_free / 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700237 if device_info.target_fs_free < (100 * 1024):
238 logging.warning('The device has less than 100MB free. deploy_chrome may '
239 'hang during the transfer.')
240
Ryan Cui3045c5d2012-07-13 18:00:33 -0700241 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800242 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700243 # Show the output (status) for this command.
Steve Funge984a532013-11-25 17:09:25 -0800244 dest_path = _CHROME_DIR
Daniel Erat1ae46382014-08-14 10:23:39 -0700245 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
246 self.options.target_dir,
247 inplace=True, debug_level=logging.INFO,
248 verbose=self.options.verbose)
Steve Funge984a532013-11-25 17:09:25 -0800249
250 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800251 if p.mode:
252 # Set mode if necessary.
253 self.host.RemoteSh('chmod %o %s/%s' % (p.mode, dest_path,
254 p.src if not p.dest else p.dest))
255
256
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800257 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800258 logging.info('Starting UI...')
Daniel Erat1ae46382014-08-14 10:23:39 -0700259 self.host.RemoteSh('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700260
David James88e6f032013-03-02 08:13:20 -0800261 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700262 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800263 logging.info('Testing connection to the device...')
Daniel Erat1ae46382014-08-14 10:23:39 -0700264 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800265 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700266 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800267 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700268
Steve Funge984a532013-11-25 17:09:25 -0800269 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700270 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700271 def BinaryExists(filename):
272 """Checks if the passed-in file is present in the build directory."""
273 return os.path.exists(os.path.join(self.options.build_dir, filename))
274
Daniel Erat9813f0e2014-11-12 11:00:28 -0700275 # Handle non-Chrome deployments.
276 if not BinaryExists('chrome'):
277 if BinaryExists('envoy_shell'):
278 self.copy_paths = chrome_util.GetCopyPaths('envoy')
279 elif BinaryExists('app_shell'):
280 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
281
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700282 # TODO(derat): Update _Deploy() and remove this after figuring out how
Daniel Erat9813f0e2014-11-12 11:00:28 -0700283 # {app,envoy}_shell should be executed.
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700284 self.options.startui = False
285
David James88e6f032013-03-02 08:13:20 -0800286 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800287 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
288 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800289
Thiago Goncales12793312013-05-23 11:26:17 -0700290 def _MountTarget(self):
291 logging.info('Mounting Chrome...')
292
293 # Create directory if does not exist
294 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
295 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
296 self.options.mount_dir))
297 # Chrome needs partition to have exec and suid flags set
298 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
299
David James88e6f032013-03-02 08:13:20 -0800300 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800301 self._CheckDeployType()
302
David James88e6f032013-03-02 08:13:20 -0800303 # If requested, just do the staging step.
304 if self.options.staging_only:
305 self._PrepareStagingDir()
306 return 0
307
308 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800309 # stop printing output at that point, and halt any running steps.
Steve Funge984a532013-11-25 17:09:25 -0800310 steps = [self._GetDeviceInfo, self._CheckConnection,
311 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
312 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700313 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
314 return_values=True)
315 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800316
317 # If we failed to mark the rootfs as writable, try disabling rootfs
318 # verification.
319 if self._rootfs_is_still_readonly.is_set():
320 self._DisableRootfsVerification()
321
Thiago Goncales12793312013-05-23 11:26:17 -0700322 if self.options.mount_dir is not None:
323 self._MountTarget()
324
David James88e6f032013-03-02 08:13:20 -0800325 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700326 self._Deploy()
327
328
Ryan Cuia56a71e2012-10-18 18:40:35 -0700329def ValidateGypDefines(_option, _opt, value):
330 """Convert GYP_DEFINES-formatted string to dictionary."""
331 return chrome_util.ProcessGypDefines(value)
332
333
334class CustomOption(commandline.Option):
335 """Subclass Option class to implement path evaluation."""
336 TYPES = commandline.Option.TYPES + ('gyp_defines',)
337 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
338 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
339
340
Ryan Cuie535b172012-10-19 18:25:03 -0700341def _CreateParser():
342 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800343 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
344 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700345
Ryan Cuia56a71e2012-10-18 18:40:35 -0700346 # TODO(rcui): Have this use the UI-V2 format of having source and target
347 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700348 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800349 help='Skip all prompts (i.e., for disabling of rootfs '
350 'verification). This may result in the target '
351 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800352 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
353 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800354 help="The board the Chrome build is targeted for. When in "
355 "a 'cros chrome-sdk' shell, defaults to the SDK "
356 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700357 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800358 help='The directory with Chrome build artifacts to deploy '
359 'from. Typically of format <chrome_root>/out/Debug. '
360 'When this option is used, the GYP_DEFINES '
361 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800362 parser.add_option('--target-dir', type='path',
363 help='Target directory on device to deploy Chrome into.',
Thiago Goncales12793312013-05-23 11:26:17 -0700364 default=None)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700365 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800366 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800367 parser.add_option('--nostartui', action='store_false', dest='startui',
368 default=True,
369 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800370 parser.add_option('--nostrip', action='store_false', dest='dostrip',
371 default=True,
372 help="Don't strip binaries during deployment. Warning: "
373 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700374 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800375 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700376 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800377 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700378 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800379 help='Show more debug output.')
Thiago Goncales12793312013-05-23 11:26:17 -0700380 parser.add_option('--mount-dir', type='path', default=None,
Mike Frysingercd304692014-10-02 16:33:41 -0400381 help='Deploy Chrome in target directory and bind it '
382 'to the directory specified by this flag.')
Thiago Goncales12793312013-05-23 11:26:17 -0700383 parser.add_option('--mount', action='store_true', default=False,
Mike Frysingercd304692014-10-02 16:33:41 -0400384 help='Deploy Chrome to default target directory and bind '
385 'it to the default mount directory.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700386
387 group = optparse.OptionGroup(parser, 'Advanced Options')
388 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800389 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800390 group.add_option('--sloppy', action='store_true', default=False,
391 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700392 group.add_option('--staging-flags', default=None, type='gyp_defines',
393 help='Extra flags to control staging. Valid flags are - %s'
394 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800395 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800396 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800397 'variable and --staging-flags, if set. Enforce that '
398 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700399 group.add_option('--strip-flags', default=None,
400 help="Flags to call the 'strip' binutil tool with. "
401 "Overrides the default arguments.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700402 parser.add_option_group(group)
403
Aviv Keshet1c986f32014-04-24 13:20:49 -0700404 group = optparse.OptionGroup(parser, 'Metadata Overrides (Advanced)',
405 description='Provide all of these overrides '
406 'in order to remove dependencies on '
407 'metadata.json existence.')
408 group.add_option('--target-tc', action='store', default=None,
409 help='Override target toolchain name, e.g. '
410 'x86_64-cros-linux-gnu')
411 group.add_option('--toolchain-url', action='store', default=None,
412 help='Override toolchain url format pattern, e.g. '
413 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
414 parser.add_option_group(group)
415
416
Ryan Cuif890a3e2013-03-07 18:57:06 -0800417 # GYP_DEFINES that Chrome was built with. Influences which files are staged
418 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
419 # enviroment variable.
420 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
421 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700422 # Path of an empty directory to stage chrome artifacts to. Defaults to a
423 # temporary directory that is removed when the script finishes. If the path
424 # is specified, then it will not be removed.
425 parser.add_option('--staging-dir', type='path', default=None,
426 help=optparse.SUPPRESS_HELP)
427 # Only prepare the staging directory, and skip deploying to the device.
428 parser.add_option('--staging-only', action='store_true', default=False,
429 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700430 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
431 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
432 # fetching the SDK toolchain.
433 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700434 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700435
Ryan Cuie535b172012-10-19 18:25:03 -0700436
437def _ParseCommandLine(argv):
438 """Parse args, and run environment-independent checks."""
439 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700440 (options, args) = parser.parse_args(argv)
441
Ryan Cuia56a71e2012-10-18 18:40:35 -0700442 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
443 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800444 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700445 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
446 parser.error('Cannot specify both --build_dir and '
447 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800448 if options.build_dir and not options.board:
449 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700450 if options.gs_path and options.local_pkg_path:
451 parser.error('Cannot specify both --gs-path and --local-pkg-path')
452 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700453 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800454 if (options.strict or options.staging_flags) and not options.build_dir:
455 parser.error('--strict and --staging-flags require --build-dir to be '
456 'set.')
457 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800458 parser.error('--staging-flags requires --strict to be set.')
459 if options.sloppy and options.strict:
460 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700461
462 if options.mount or options.mount_dir:
463 if not options.target_dir:
464 options.target_dir = _CHROME_DIR_MOUNT
465 else:
466 if not options.target_dir:
467 options.target_dir = _CHROME_DIR
468
469 if options.mount and not options.mount_dir:
470 options.mount_dir = _CHROME_DIR
471
Ryan Cui3045c5d2012-07-13 18:00:33 -0700472 return options, args
473
474
Ryan Cuie535b172012-10-19 18:25:03 -0700475def _PostParseCheck(options, _args):
Steve Funge984a532013-11-25 17:09:25 -0800476 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700477
478 Args:
Steve Funge984a532013-11-25 17:09:25 -0800479 options: The options object returned by optparse.
480 _args: The args object returned by optparse.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700481 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700482 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
483 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
484
Ryan Cuib623e7b2013-03-14 12:54:11 -0700485 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700486 gyp_env = os.getenv('GYP_DEFINES', None)
487 if gyp_env is not None:
488 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700489 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700490 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700491
492 if options.strict and not options.gyp_defines:
493 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800494 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700495
Ryan Cui3c183c22013-04-29 18:04:11 -0700496 if options.build_dir:
497 chrome_path = os.path.join(options.build_dir, 'chrome')
498 if os.path.isfile(chrome_path):
499 deps = lddtree.ParseELF(chrome_path)
500 if 'libbase.so' in deps['libs']:
501 cros_build_lib.Warning(
502 'Detected a component build of Chrome. component build is '
503 'not working properly for Chrome OS. See crbug.com/196317. '
504 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700505
Ryan Cuia56a71e2012-10-18 18:40:35 -0700506
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):
602 options, args = _ParseCommandLine(argv)
603 _PostParseCheck(options, args)
604
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())