blob: 7500497e0f7e02e2a5d403ae86ff097d237177e9 [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 Erat1ae46382014-08-14 10:23:39 -0700275 if BinaryExists('app_shell') and not BinaryExists('chrome'):
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700276 # app_shell deployment.
277 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
278 # TODO(derat): Update _Deploy() and remove this after figuring out how
279 # app_shell should be executed.
280 self.options.startui = False
281
David James88e6f032013-03-02 08:13:20 -0800282 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800283 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
284 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800285
Thiago Goncales12793312013-05-23 11:26:17 -0700286 def _MountTarget(self):
287 logging.info('Mounting Chrome...')
288
289 # Create directory if does not exist
290 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
291 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
292 self.options.mount_dir))
293 # Chrome needs partition to have exec and suid flags set
294 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
295
David James88e6f032013-03-02 08:13:20 -0800296 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800297 self._CheckDeployType()
298
David James88e6f032013-03-02 08:13:20 -0800299 # If requested, just do the staging step.
300 if self.options.staging_only:
301 self._PrepareStagingDir()
302 return 0
303
304 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800305 # stop printing output at that point, and halt any running steps.
Steve Funge984a532013-11-25 17:09:25 -0800306 steps = [self._GetDeviceInfo, self._CheckConnection,
307 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
308 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700309 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
310 return_values=True)
311 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800312
313 # If we failed to mark the rootfs as writable, try disabling rootfs
314 # verification.
315 if self._rootfs_is_still_readonly.is_set():
316 self._DisableRootfsVerification()
317
Thiago Goncales12793312013-05-23 11:26:17 -0700318 if self.options.mount_dir is not None:
319 self._MountTarget()
320
David James88e6f032013-03-02 08:13:20 -0800321 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700322 self._Deploy()
323
324
Ryan Cuia56a71e2012-10-18 18:40:35 -0700325def ValidateGypDefines(_option, _opt, value):
326 """Convert GYP_DEFINES-formatted string to dictionary."""
327 return chrome_util.ProcessGypDefines(value)
328
329
330class CustomOption(commandline.Option):
331 """Subclass Option class to implement path evaluation."""
332 TYPES = commandline.Option.TYPES + ('gyp_defines',)
333 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
334 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
335
336
Ryan Cuie535b172012-10-19 18:25:03 -0700337def _CreateParser():
338 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800339 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
340 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700341
Ryan Cuia56a71e2012-10-18 18:40:35 -0700342 # TODO(rcui): Have this use the UI-V2 format of having source and target
343 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700344 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800345 help='Skip all prompts (i.e., for disabling of rootfs '
346 'verification). This may result in the target '
347 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800348 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
349 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800350 help="The board the Chrome build is targeted for. When in "
351 "a 'cros chrome-sdk' shell, defaults to the SDK "
352 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700353 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800354 help='The directory with Chrome build artifacts to deploy '
355 'from. Typically of format <chrome_root>/out/Debug. '
356 'When this option is used, the GYP_DEFINES '
357 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800358 parser.add_option('--target-dir', type='path',
359 help='Target directory on device to deploy Chrome into.',
Thiago Goncales12793312013-05-23 11:26:17 -0700360 default=None)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700361 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800362 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800363 parser.add_option('--nostartui', action='store_false', dest='startui',
364 default=True,
365 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800366 parser.add_option('--nostrip', action='store_false', dest='dostrip',
367 default=True,
368 help="Don't strip binaries during deployment. Warning: "
369 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700370 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800371 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700372 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800373 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700374 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800375 help='Show more debug output.')
Thiago Goncales12793312013-05-23 11:26:17 -0700376 parser.add_option('--mount-dir', type='path', default=None,
Mike Frysingercd304692014-10-02 16:33:41 -0400377 help='Deploy Chrome in target directory and bind it '
378 'to the directory specified by this flag.')
Thiago Goncales12793312013-05-23 11:26:17 -0700379 parser.add_option('--mount', action='store_true', default=False,
Mike Frysingercd304692014-10-02 16:33:41 -0400380 help='Deploy Chrome to default target directory and bind '
381 'it to the default mount directory.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700382
383 group = optparse.OptionGroup(parser, 'Advanced Options')
384 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800385 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800386 group.add_option('--sloppy', action='store_true', default=False,
387 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700388 group.add_option('--staging-flags', default=None, type='gyp_defines',
389 help='Extra flags to control staging. Valid flags are - %s'
390 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800391 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800392 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800393 'variable and --staging-flags, if set. Enforce that '
394 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700395 group.add_option('--strip-flags', default=None,
396 help="Flags to call the 'strip' binutil tool with. "
397 "Overrides the default arguments.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700398 parser.add_option_group(group)
399
Aviv Keshet1c986f32014-04-24 13:20:49 -0700400 group = optparse.OptionGroup(parser, 'Metadata Overrides (Advanced)',
401 description='Provide all of these overrides '
402 'in order to remove dependencies on '
403 'metadata.json existence.')
404 group.add_option('--target-tc', action='store', default=None,
405 help='Override target toolchain name, e.g. '
406 'x86_64-cros-linux-gnu')
407 group.add_option('--toolchain-url', action='store', default=None,
408 help='Override toolchain url format pattern, e.g. '
409 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
410 parser.add_option_group(group)
411
412
Ryan Cuif890a3e2013-03-07 18:57:06 -0800413 # GYP_DEFINES that Chrome was built with. Influences which files are staged
414 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
415 # enviroment variable.
416 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
417 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700418 # Path of an empty directory to stage chrome artifacts to. Defaults to a
419 # temporary directory that is removed when the script finishes. If the path
420 # is specified, then it will not be removed.
421 parser.add_option('--staging-dir', type='path', default=None,
422 help=optparse.SUPPRESS_HELP)
423 # Only prepare the staging directory, and skip deploying to the device.
424 parser.add_option('--staging-only', action='store_true', default=False,
425 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700426 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
427 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
428 # fetching the SDK toolchain.
429 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700430 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700431
Ryan Cuie535b172012-10-19 18:25:03 -0700432
433def _ParseCommandLine(argv):
434 """Parse args, and run environment-independent checks."""
435 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700436 (options, args) = parser.parse_args(argv)
437
Ryan Cuia56a71e2012-10-18 18:40:35 -0700438 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
439 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800440 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700441 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
442 parser.error('Cannot specify both --build_dir and '
443 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800444 if options.build_dir and not options.board:
445 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700446 if options.gs_path and options.local_pkg_path:
447 parser.error('Cannot specify both --gs-path and --local-pkg-path')
448 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700449 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800450 if (options.strict or options.staging_flags) and not options.build_dir:
451 parser.error('--strict and --staging-flags require --build-dir to be '
452 'set.')
453 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800454 parser.error('--staging-flags requires --strict to be set.')
455 if options.sloppy and options.strict:
456 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700457
458 if options.mount or options.mount_dir:
459 if not options.target_dir:
460 options.target_dir = _CHROME_DIR_MOUNT
461 else:
462 if not options.target_dir:
463 options.target_dir = _CHROME_DIR
464
465 if options.mount and not options.mount_dir:
466 options.mount_dir = _CHROME_DIR
467
Ryan Cui3045c5d2012-07-13 18:00:33 -0700468 return options, args
469
470
Ryan Cuie535b172012-10-19 18:25:03 -0700471def _PostParseCheck(options, _args):
Steve Funge984a532013-11-25 17:09:25 -0800472 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700473
474 Args:
Steve Funge984a532013-11-25 17:09:25 -0800475 options: The options object returned by optparse.
476 _args: The args object returned by optparse.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700477 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700478 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
479 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
480
Ryan Cuib623e7b2013-03-14 12:54:11 -0700481 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700482 gyp_env = os.getenv('GYP_DEFINES', None)
483 if gyp_env is not None:
484 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700485 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700486 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700487
488 if options.strict and not options.gyp_defines:
489 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800490 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700491
Ryan Cui3c183c22013-04-29 18:04:11 -0700492 if options.build_dir:
493 chrome_path = os.path.join(options.build_dir, 'chrome')
494 if os.path.isfile(chrome_path):
495 deps = lddtree.ParseELF(chrome_path)
496 if 'libbase.so' in deps['libs']:
497 cros_build_lib.Warning(
498 'Detected a component build of Chrome. component build is '
499 'not working properly for Chrome OS. See crbug.com/196317. '
500 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700501
Ryan Cuia56a71e2012-10-18 18:40:35 -0700502
Ryan Cui504db722013-01-22 11:48:01 -0800503def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700504 """Get the chrome prebuilt tarball from GS.
505
Mike Frysinger02e1e072013-11-10 22:11:34 -0500506 Returns:
507 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700508 """
David James9374aac2013-10-08 16:00:17 -0700509 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500510 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800511 files = [found for found in files if
512 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
513 if not files:
514 raise Exception('No chrome package found at %s' % gs_path)
515 elif len(files) > 1:
516 # - Users should provide us with a direct link to either a stripped or
517 # unstripped chrome package.
518 # - In the case of being provided with an archive directory, where both
519 # stripped and unstripped chrome available, use the stripped chrome
520 # package.
521 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
522 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
523 files = [f for f in files if not 'unstripped' in f]
524 assert len(files) == 1
525 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800526
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800527 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800528 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800529 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
530 chrome_path = os.path.join(tempdir, filename)
531 assert os.path.exists(chrome_path)
532 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700533
534
Ryan Cuif890a3e2013-03-07 18:57:06 -0800535@contextlib.contextmanager
536def _StripBinContext(options):
537 if not options.dostrip:
538 yield None
539 elif options.strip_bin:
540 yield options.strip_bin
541 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800542 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800543 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700544 with sdk.Prepare(components=components, target_tc=options.target_tc,
545 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800546 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
547 constants.CHROME_ENV_FILE)
548 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
549 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
550 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800551 yield strip_bin
552
553
Steve Funge984a532013-11-25 17:09:25 -0800554def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
555 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800556 """Place the necessary files in the staging directory.
557
558 The staging directory is the directory used to rsync the build artifacts over
559 to the device. Only the necessary Chrome build artifacts are put into the
560 staging directory.
561 """
Ryan Cui5866be02013-03-18 14:12:00 -0700562 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400563 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800564 if options.build_dir:
565 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700566 strip_flags = (None if options.strip_flags is None else
567 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800568 chrome_util.StageChromeFromBuildDir(
569 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800570 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700571 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800572 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700573 else:
574 pkg_path = options.local_pkg_path
575 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800576 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
577 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700578
579 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800580 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700581 # Extract only the ./opt/google/chrome contents, directly into the staging
582 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800583 if pkg_path[-4:] == '.zip':
584 cros_build_lib.DebugRunCommand(
585 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
586 staging_dir])
587 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
588 shutil.move(filename, staging_dir)
589 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
590 else:
591 cros_build_lib.DebugRunCommand(
592 ['tar', '--strip-components', '4', '--extract',
593 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
594 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700595
Ryan Cui71aa8de2013-04-19 16:12:55 -0700596
Ryan Cui3045c5d2012-07-13 18:00:33 -0700597def main(argv):
598 options, args = _ParseCommandLine(argv)
599 _PostParseCheck(options, args)
600
601 # Set cros_build_lib debug level to hide RunCommand spew.
602 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700603 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700604 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800605 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700606
Ryan Cui71aa8de2013-04-19 16:12:55 -0700607 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700608 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
609 if cmd_stats:
610 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700611
Ryan Cui71aa8de2013-04-19 16:12:55 -0700612 with osutils.TempDir(set_global=True) as tempdir:
613 staging_dir = options.staging_dir
614 if not staging_dir:
615 staging_dir = os.path.join(tempdir, 'chrome')
616
617 deploy = DeployChrome(options, tempdir, staging_dir)
618 try:
619 deploy.Perform()
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700620 except failures_lib.StepFailure as ex:
Ryan Cui71aa8de2013-04-19 16:12:55 -0700621 raise SystemExit(str(ex).strip())