blob: ce768f3cc524b2126329b6f9dddc50e192171208 [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
Ryan Cui7193a7e2013-04-26 14:15:19 -070019import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080020import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070021import functools
Steve Funge984a532013-11-25 17:09:25 -080022import glob
Ryan Cui3045c5d2012-07-13 18:00:33 -070023import logging
David James88e6f032013-03-02 08:13:20 -080024import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070025import os
Ryan Cuia56a71e2012-10-18 18:40:35 -070026import optparse
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070027import shlex
Steve Funge984a532013-11-25 17:09:25 -080028import shutil
Ryan Cui3045c5d2012-07-13 18:00:33 -070029import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070030
Ryan Cuia56a71e2012-10-18 18:40:35 -070031
Don Garrett88b8d782014-05-13 17:30:55 -070032from chromite.cbuildbot import constants
David James14e97772014-06-04 18:44:49 -070033from chromite.cbuildbot import failures_lib
Ryan Cui686ec052013-02-12 16:39:41 -080034from chromite.cros.commands import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070035from chromite.lib import chrome_util
Ryan Cui3045c5d2012-07-13 18:00:33 -070036from chromite.lib import cros_build_lib
Ryan Cuie535b172012-10-19 18:25:03 -070037from chromite.lib import commandline
Ryan Cui777ff422012-12-07 13:12:54 -080038from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070039from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080040from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070041from chromite.lib import remote_access as remote
Ryan Cui71aa8de2013-04-19 16:12:55 -070042from chromite.lib import stats
David James3432acd2013-11-27 10:02:18 -080043from chromite.lib import timeout_util
Ryan Cui3c183c22013-04-29 18:04:11 -070044from chromite.scripts import lddtree
Ryan Cui3045c5d2012-07-13 18:00:33 -070045
46
Ryan Cuia56a71e2012-10-18 18:40:35 -070047_USAGE = "deploy_chrome [--]\n\n %s" % __doc__
48
Ryan Cui3045c5d2012-07-13 18:00:33 -070049KERNEL_A_PARTITION = 2
50KERNEL_B_PARTITION = 4
51
52KILL_PROC_MAX_WAIT = 10
53POST_KILL_WAIT = 2
54
Ryan Cuie535b172012-10-19 18:25:03 -070055MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080056LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070057
Steve Funge984a532013-11-25 17:09:25 -080058_ANDROID_DIR = '/system/chrome'
59_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
60
David James2cb34002013-03-01 18:42:40 -080061_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070062_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080063
Thiago Goncales12793312013-05-23 11:26:17 -070064_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
65_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070066
Steve Funge984a532013-11-25 17:09:25 -080067DF_COMMAND = 'df -k %s'
Steve Funge984a532013-11-25 17:09:25 -080068
Ryan Cui3045c5d2012-07-13 18:00:33 -070069def _UrlBaseName(url):
70 """Return the last component of the URL."""
71 return url.rstrip('/').rpartition('/')[-1]
72
73
Yu-Ju Hongc54d3342014-05-14 12:42:06 -070074class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -080075 """Raised whenever the deploy fails."""
76
77
Ryan Cui7193a7e2013-04-26 14:15:19 -070078DeviceInfo = collections.namedtuple(
79 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
80
81
Ryan Cui3045c5d2012-07-13 18:00:33 -070082class DeployChrome(object):
83 """Wraps the core deployment functionality."""
Ryan Cuia56a71e2012-10-18 18:40:35 -070084 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070085 """Initialize the class.
86
Mike Frysinger02e1e072013-11-10 22:11:34 -050087 Args:
Ryan Cuie535b172012-10-19 18:25:03 -070088 options: Optparse result structure.
89 tempdir: Scratch space for the class. Caller has responsibility to clean
90 it up.
Steve Funge984a532013-11-25 17:09:25 -080091 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -070092 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070093 self.tempdir = tempdir
94 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070095 self.staging_dir = staging_dir
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070096 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080097 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -070098
Daniel Erat3fd6c7a2014-05-02 14:28:31 -070099 self.copy_paths = chrome_util.GetCopyPaths('chrome')
Steve Funge984a532013-11-25 17:09:25 -0800100 self.chrome_dir = _CHROME_DIR
101
Ryan Cui7193a7e2013-04-26 14:15:19 -0700102 def _GetRemoteMountFree(self, remote_dir):
Daniel Erat1ae46382014-08-14 10:23:39 -0700103 result = self.host.RemoteSh(DF_COMMAND % remote_dir, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700104 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800105 value = line.split()[3]
106 multipliers = {
107 'G': 1024 * 1024 * 1024,
108 'M': 1024 * 1024,
109 'K': 1024,
110 }
111 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700112
113 def _GetRemoteDirSize(self, remote_dir):
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800114 result = self.host.RemoteSh('du -ks %s' % remote_dir, capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700115 return int(result.output.split()[0])
116
117 def _GetStagingDirSize(self):
118 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800119 redirect_stdout=True,
120 capture_output=True)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700121 return int(result.output.split()[0])
122
Ryan Cui3045c5d2012-07-13 18:00:33 -0700123 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800124 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800125 error_code_ok=True, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700126 return result.returncode == 0
127
128 def _DisableRootfsVerification(self):
129 if not self.options.force:
130 logging.error('Detected that the device has rootfs verification enabled.')
131 logging.info('This script can automatically remove the rootfs '
132 'verification, which requires that it reboot the device.')
133 logging.info('Make sure the device is in developer mode!')
134 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700135 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800136 # Since we stopped Chrome earlier, it's good form to start it up again.
137 if self.options.startui:
138 logging.info('Starting Chrome...')
139 self.host.RemoteSh('start ui')
140 raise DeployFailure('Need rootfs verification to be disabled. '
141 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700142
143 logging.info('Removing rootfs verification from %s', self.options.to)
144 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
145 # Use --force to bypass the checks.
146 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
147 '--remove_rootfs_verification --force')
148 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
149 self.host.RemoteSh(cmd % partition, error_code_ok=True)
150
151 # A reboot in developer mode takes a while (and has delays), so the user
152 # will have time to read and act on the USB boot instructions below.
153 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
154 self.host.RemoteReboot()
155
David James88e6f032013-03-02 08:13:20 -0800156 # Now that the machine has been rebooted, we need to kill Chrome again.
157 self._KillProcsIfNeeded()
158
159 # Make sure the rootfs is writable now.
160 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700161
162 def _CheckUiJobStarted(self):
163 # status output is in the format:
164 # <job_name> <status> ['process' <pid>].
165 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800166 try:
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800167 result = self.host.RemoteSh('status ui', capture_output=True)
Ryan Cuif2d1a582013-02-19 14:08:13 -0800168 except cros_build_lib.RunCommandError as e:
169 if 'Unknown job' in e.result.error:
170 return False
171 else:
172 raise e
173
Ryan Cui3045c5d2012-07-13 18:00:33 -0700174 return result.output.split()[1].split('/')[0] == 'start'
175
176 def _KillProcsIfNeeded(self):
177 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800178 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700179 self.host.RemoteSh('stop ui')
180
181 # Developers sometimes run session_manager manually, in which case we'll
182 # need to help shut the chrome processes down.
183 try:
David James3432acd2013-11-27 10:02:18 -0800184 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700185 while self._ChromeFileInUse():
186 logging.warning('The chrome binary on the device is in use.')
187 logging.warning('Killing chrome and session_manager processes...\n')
188
189 self.host.RemoteSh("pkill 'chrome|session_manager'",
190 error_code_ok=True)
191 # Wait for processes to actually terminate
192 time.sleep(POST_KILL_WAIT)
193 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800194 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800195 msg = ('Could not kill processes after %s seconds. Please exit any '
196 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
197 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700198
David James88e6f032013-03-02 08:13:20 -0800199 def _MountRootfsAsWritable(self, error_code_ok=True):
200 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700201
David James88e6f032013-03-02 08:13:20 -0800202 If the command fails, and error_code_ok is True, then this function sets
203 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700204
Mike Frysinger02e1e072013-11-10 22:11:34 -0500205 Args:
David James88e6f032013-03-02 08:13:20 -0800206 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
207 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800208 # TODO: Should migrate to use the remount functions in remote_access.
Daniel Erat1ae46382014-08-14 10:23:39 -0700209 result = self.host.RemoteSh(MOUNT_RW_COMMAND,
Yu-Ju Hong3add4432014-01-30 11:46:15 -0800210 error_code_ok=error_code_ok,
211 capture_output=True)
David James88e6f032013-03-02 08:13:20 -0800212 if result.returncode:
213 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700214
Ryan Cui7193a7e2013-04-26 14:15:19 -0700215 def _GetDeviceInfo(self):
216 steps = [
217 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
218 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
219 ]
220 return_values = parallel.RunParallelSteps(steps, return_values=True)
221 return DeviceInfo(*return_values)
222
223 def _CheckDeviceFreeSpace(self, device_info):
224 """See if target device has enough space for Chrome.
225
Mike Frysinger02e1e072013-11-10 22:11:34 -0500226 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700227 device_info: A DeviceInfo named tuple.
228 """
229 effective_free = device_info.target_dir_size + device_info.target_fs_free
230 staging_size = self._GetStagingDirSize()
231 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700232 raise DeployFailure(
233 'Not enough free space on the device. Required: %s MiB, '
234 'actual: %s MiB.' % (staging_size / 1024, effective_free / 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700235 if device_info.target_fs_free < (100 * 1024):
236 logging.warning('The device has less than 100MB free. deploy_chrome may '
237 'hang during the transfer.')
238
Ryan Cui3045c5d2012-07-13 18:00:33 -0700239 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800240 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700241 # Show the output (status) for this command.
Steve Funge984a532013-11-25 17:09:25 -0800242 dest_path = _CHROME_DIR
Daniel Erat1ae46382014-08-14 10:23:39 -0700243 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
244 self.options.target_dir,
245 inplace=True, debug_level=logging.INFO,
246 verbose=self.options.verbose)
Steve Funge984a532013-11-25 17:09:25 -0800247
248 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800249 if p.mode:
250 # Set mode if necessary.
251 self.host.RemoteSh('chmod %o %s/%s' % (p.mode, dest_path,
252 p.src if not p.dest else p.dest))
253
254
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800255 if self.options.startui:
Steve Funge984a532013-11-25 17:09:25 -0800256 logging.info('Starting UI...')
Daniel Erat1ae46382014-08-14 10:23:39 -0700257 self.host.RemoteSh('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700258
David James88e6f032013-03-02 08:13:20 -0800259 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700260 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800261 logging.info('Testing connection to the device...')
Daniel Erat1ae46382014-08-14 10:23:39 -0700262 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800263 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700264 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800265 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700266
Steve Funge984a532013-11-25 17:09:25 -0800267 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700268 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700269 def BinaryExists(filename):
270 """Checks if the passed-in file is present in the build directory."""
271 return os.path.exists(os.path.join(self.options.build_dir, filename))
272
Daniel Erat1ae46382014-08-14 10:23:39 -0700273 if BinaryExists('app_shell') and not BinaryExists('chrome'):
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700274 # app_shell deployment.
275 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
276 # TODO(derat): Update _Deploy() and remove this after figuring out how
277 # app_shell should be executed.
278 self.options.startui = False
279
David James88e6f032013-03-02 08:13:20 -0800280 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800281 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
282 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800283
Thiago Goncales12793312013-05-23 11:26:17 -0700284 def _MountTarget(self):
285 logging.info('Mounting Chrome...')
286
287 # Create directory if does not exist
288 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
289 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
290 self.options.mount_dir))
291 # Chrome needs partition to have exec and suid flags set
292 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
293
David James88e6f032013-03-02 08:13:20 -0800294 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800295 self._CheckDeployType()
296
David James88e6f032013-03-02 08:13:20 -0800297 # If requested, just do the staging step.
298 if self.options.staging_only:
299 self._PrepareStagingDir()
300 return 0
301
302 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800303 # stop printing output at that point, and halt any running steps.
Steve Funge984a532013-11-25 17:09:25 -0800304 steps = [self._GetDeviceInfo, self._CheckConnection,
305 self._KillProcsIfNeeded, self._MountRootfsAsWritable,
306 self._PrepareStagingDir]
Ryan Cui7193a7e2013-04-26 14:15:19 -0700307 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
308 return_values=True)
309 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800310
311 # If we failed to mark the rootfs as writable, try disabling rootfs
312 # verification.
313 if self._rootfs_is_still_readonly.is_set():
314 self._DisableRootfsVerification()
315
Thiago Goncales12793312013-05-23 11:26:17 -0700316 if self.options.mount_dir is not None:
317 self._MountTarget()
318
David James88e6f032013-03-02 08:13:20 -0800319 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700320 self._Deploy()
321
322
Ryan Cuia56a71e2012-10-18 18:40:35 -0700323def ValidateGypDefines(_option, _opt, value):
324 """Convert GYP_DEFINES-formatted string to dictionary."""
325 return chrome_util.ProcessGypDefines(value)
326
327
328class CustomOption(commandline.Option):
329 """Subclass Option class to implement path evaluation."""
330 TYPES = commandline.Option.TYPES + ('gyp_defines',)
331 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
332 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
333
334
Ryan Cuie535b172012-10-19 18:25:03 -0700335def _CreateParser():
336 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800337 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
338 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700339
Ryan Cuia56a71e2012-10-18 18:40:35 -0700340 # TODO(rcui): Have this use the UI-V2 format of having source and target
341 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700342 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800343 help='Skip all prompts (i.e., for disabling of rootfs '
344 'verification). This may result in the target '
345 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800346 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
347 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800348 help="The board the Chrome build is targeted for. When in "
349 "a 'cros chrome-sdk' shell, defaults to the SDK "
350 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700351 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800352 help='The directory with Chrome build artifacts to deploy '
353 'from. Typically of format <chrome_root>/out/Debug. '
354 'When this option is used, the GYP_DEFINES '
355 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800356 parser.add_option('--target-dir', type='path',
357 help='Target directory on device to deploy Chrome into.',
Thiago Goncales12793312013-05-23 11:26:17 -0700358 default=None)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700359 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800360 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800361 parser.add_option('--nostartui', action='store_false', dest='startui',
362 default=True,
363 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800364 parser.add_option('--nostrip', action='store_false', dest='dostrip',
365 default=True,
366 help="Don't strip binaries during deployment. Warning: "
367 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700368 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800369 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700370 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800371 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700372 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800373 help='Show more debug output.')
Thiago Goncales12793312013-05-23 11:26:17 -0700374 parser.add_option('--mount-dir', type='path', default=None,
375 help='Deploy Chrome in target directory and bind it'
376 'to directory specified by this flag.')
377 parser.add_option('--mount', action='store_true', default=False,
378 help='Deploy Chrome to default target directory and bind it'
379 'to default mount directory.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700380
381 group = optparse.OptionGroup(parser, 'Advanced Options')
382 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800383 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800384 group.add_option('--sloppy', action='store_true', default=False,
385 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700386 group.add_option('--staging-flags', default=None, type='gyp_defines',
387 help='Extra flags to control staging. Valid flags are - %s'
388 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800389 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800390 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800391 'variable and --staging-flags, if set. Enforce that '
392 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700393 group.add_option('--strip-flags', default=None,
394 help="Flags to call the 'strip' binutil tool with. "
395 "Overrides the default arguments.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700396 parser.add_option_group(group)
397
Aviv Keshet1c986f32014-04-24 13:20:49 -0700398 group = optparse.OptionGroup(parser, 'Metadata Overrides (Advanced)',
399 description='Provide all of these overrides '
400 'in order to remove dependencies on '
401 'metadata.json existence.')
402 group.add_option('--target-tc', action='store', default=None,
403 help='Override target toolchain name, e.g. '
404 'x86_64-cros-linux-gnu')
405 group.add_option('--toolchain-url', action='store', default=None,
406 help='Override toolchain url format pattern, e.g. '
407 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
408 parser.add_option_group(group)
409
410
Ryan Cuif890a3e2013-03-07 18:57:06 -0800411 # GYP_DEFINES that Chrome was built with. Influences which files are staged
412 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
413 # enviroment variable.
414 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
415 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700416 # Path of an empty directory to stage chrome artifacts to. Defaults to a
417 # temporary directory that is removed when the script finishes. If the path
418 # is specified, then it will not be removed.
419 parser.add_option('--staging-dir', type='path', default=None,
420 help=optparse.SUPPRESS_HELP)
421 # Only prepare the staging directory, and skip deploying to the device.
422 parser.add_option('--staging-only', action='store_true', default=False,
423 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700424 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
425 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
426 # fetching the SDK toolchain.
427 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700428 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700429
Ryan Cuie535b172012-10-19 18:25:03 -0700430
431def _ParseCommandLine(argv):
432 """Parse args, and run environment-independent checks."""
433 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700434 (options, args) = parser.parse_args(argv)
435
Ryan Cuia56a71e2012-10-18 18:40:35 -0700436 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
437 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800438 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700439 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
440 parser.error('Cannot specify both --build_dir and '
441 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800442 if options.build_dir and not options.board:
443 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700444 if options.gs_path and options.local_pkg_path:
445 parser.error('Cannot specify both --gs-path and --local-pkg-path')
446 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700447 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800448 if (options.strict or options.staging_flags) and not options.build_dir:
449 parser.error('--strict and --staging-flags require --build-dir to be '
450 'set.')
451 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800452 parser.error('--staging-flags requires --strict to be set.')
453 if options.sloppy and options.strict:
454 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700455
456 if options.mount or options.mount_dir:
457 if not options.target_dir:
458 options.target_dir = _CHROME_DIR_MOUNT
459 else:
460 if not options.target_dir:
461 options.target_dir = _CHROME_DIR
462
463 if options.mount and not options.mount_dir:
464 options.mount_dir = _CHROME_DIR
465
Ryan Cui3045c5d2012-07-13 18:00:33 -0700466 return options, args
467
468
Ryan Cuie535b172012-10-19 18:25:03 -0700469def _PostParseCheck(options, _args):
Steve Funge984a532013-11-25 17:09:25 -0800470 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700471
472 Args:
Steve Funge984a532013-11-25 17:09:25 -0800473 options: The options object returned by optparse.
474 _args: The args object returned by optparse.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700475 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700476 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
477 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
478
Ryan Cuib623e7b2013-03-14 12:54:11 -0700479 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700480 gyp_env = os.getenv('GYP_DEFINES', None)
481 if gyp_env is not None:
482 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700483 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700484 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700485
486 if options.strict and not options.gyp_defines:
487 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800488 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700489
Ryan Cui3c183c22013-04-29 18:04:11 -0700490 if options.build_dir:
491 chrome_path = os.path.join(options.build_dir, 'chrome')
492 if os.path.isfile(chrome_path):
493 deps = lddtree.ParseELF(chrome_path)
494 if 'libbase.so' in deps['libs']:
495 cros_build_lib.Warning(
496 'Detected a component build of Chrome. component build is '
497 'not working properly for Chrome OS. See crbug.com/196317. '
498 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700499
Ryan Cuia56a71e2012-10-18 18:40:35 -0700500
Ryan Cui504db722013-01-22 11:48:01 -0800501def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700502 """Get the chrome prebuilt tarball from GS.
503
Mike Frysinger02e1e072013-11-10 22:11:34 -0500504 Returns:
505 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700506 """
David James9374aac2013-10-08 16:00:17 -0700507 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500508 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800509 files = [found for found in files if
510 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
511 if not files:
512 raise Exception('No chrome package found at %s' % gs_path)
513 elif len(files) > 1:
514 # - Users should provide us with a direct link to either a stripped or
515 # unstripped chrome package.
516 # - In the case of being provided with an archive directory, where both
517 # stripped and unstripped chrome available, use the stripped chrome
518 # package.
519 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
520 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
521 files = [f for f in files if not 'unstripped' in f]
522 assert len(files) == 1
523 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800524
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800525 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800526 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800527 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
528 chrome_path = os.path.join(tempdir, filename)
529 assert os.path.exists(chrome_path)
530 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700531
532
Ryan Cuif890a3e2013-03-07 18:57:06 -0800533@contextlib.contextmanager
534def _StripBinContext(options):
535 if not options.dostrip:
536 yield None
537 elif options.strip_bin:
538 yield options.strip_bin
539 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800540 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800541 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700542 with sdk.Prepare(components=components, target_tc=options.target_tc,
543 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800544 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
545 constants.CHROME_ENV_FILE)
546 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
547 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
548 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800549 yield strip_bin
550
551
Steve Funge984a532013-11-25 17:09:25 -0800552def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
553 chrome_dir=_CHROME_DIR):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800554 """Place the necessary files in the staging directory.
555
556 The staging directory is the directory used to rsync the build artifacts over
557 to the device. Only the necessary Chrome build artifacts are put into the
558 staging directory.
559 """
Ryan Cui5866be02013-03-18 14:12:00 -0700560 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400561 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800562 if options.build_dir:
563 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700564 strip_flags = (None if options.strip_flags is None else
565 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800566 chrome_util.StageChromeFromBuildDir(
567 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800568 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700569 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800570 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700571 else:
572 pkg_path = options.local_pkg_path
573 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800574 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
575 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700576
577 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800578 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700579 # Extract only the ./opt/google/chrome contents, directly into the staging
580 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800581 if pkg_path[-4:] == '.zip':
582 cros_build_lib.DebugRunCommand(
583 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
584 staging_dir])
585 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
586 shutil.move(filename, staging_dir)
587 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
588 else:
589 cros_build_lib.DebugRunCommand(
590 ['tar', '--strip-components', '4', '--extract',
591 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
592 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700593
Ryan Cui71aa8de2013-04-19 16:12:55 -0700594
Ryan Cui3045c5d2012-07-13 18:00:33 -0700595def main(argv):
596 options, args = _ParseCommandLine(argv)
597 _PostParseCheck(options, args)
598
599 # Set cros_build_lib debug level to hide RunCommand spew.
600 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700601 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700602 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800603 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700604
Ryan Cui71aa8de2013-04-19 16:12:55 -0700605 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700606 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
607 if cmd_stats:
608 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700609
Ryan Cui71aa8de2013-04-19 16:12:55 -0700610 with osutils.TempDir(set_global=True) as tempdir:
611 staging_dir = options.staging_dir
612 if not staging_dir:
613 staging_dir = os.path.join(tempdir, 'chrome')
614
615 deploy = DeployChrome(options, tempdir, staging_dir)
616 try:
617 deploy.Perform()
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700618 except failures_lib.StepFailure as ex:
Ryan Cui71aa8de2013-04-19 16:12:55 -0700619 raise SystemExit(str(ex).strip())