blob: 9101d70bac567e63aff4f267d36d7cda66073e26 [file] [log] [blame]
Ryan Cui3045c5d2012-07-13 18:00:33 -07001#!/usr/bin/python
2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Ryan Cuia56a71e2012-10-18 18:40:35 -07006
7"""
8Script that deploys a Chrome build to a device.
9
10The script supports deploying Chrome from these sources:
11
121. A local build output directory, such as chromium/src/out/[Debug|Release].
132. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
143. A Chrome tarball existing locally.
15
16The script copies the necessary contents of the source location (tarball or
17build directory) and rsyncs the contents of the staging directory onto your
18device's rootfs.
19"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070020
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
Ryan Cui3045c5d2012-07-13 18:00:33 -070024import logging
David James88e6f032013-03-02 08:13:20 -080025import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070026import os
Ryan Cuia56a71e2012-10-18 18:40:35 -070027import optparse
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070028import shlex
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
David James629febb2012-11-25 13:07:34 -080032from chromite.buildbot import constants
David James88e6f032013-03-02 08:13:20 -080033from chromite.buildbot import cbuildbot_results as results_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
David James2cb34002013-03-01 18:42:40 -080058_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070059_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080060
Thiago Goncales12793312013-05-23 11:26:17 -070061_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
62_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070063
Ryan Cui3045c5d2012-07-13 18:00:33 -070064def _UrlBaseName(url):
65 """Return the last component of the URL."""
66 return url.rstrip('/').rpartition('/')[-1]
67
68
David James88e6f032013-03-02 08:13:20 -080069class DeployFailure(results_lib.StepFailure):
70 """Raised whenever the deploy fails."""
71
72
Ryan Cui7193a7e2013-04-26 14:15:19 -070073DeviceInfo = collections.namedtuple(
74 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
75
76
Ryan Cui3045c5d2012-07-13 18:00:33 -070077class DeployChrome(object):
78 """Wraps the core deployment functionality."""
Ryan Cuia56a71e2012-10-18 18:40:35 -070079 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070080 """Initialize the class.
81
Mike Frysinger02e1e072013-11-10 22:11:34 -050082 Args:
Ryan Cuie535b172012-10-19 18:25:03 -070083 options: Optparse result structure.
84 tempdir: Scratch space for the class. Caller has responsibility to clean
85 it up.
Ryan Cuie535b172012-10-19 18:25:03 -070086 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070087 self.tempdir = tempdir
88 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070089 self.staging_dir = staging_dir
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070090 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080091 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -070092
Ryan Cui7193a7e2013-04-26 14:15:19 -070093 def _GetRemoteMountFree(self, remote_dir):
94 result = self.host.RemoteSh('df -k %s' % remote_dir)
95 line = result.output.splitlines()[1]
96 return int(line.split()[3])
97
98 def _GetRemoteDirSize(self, remote_dir):
99 result = self.host.RemoteSh('du -ks %s' % remote_dir)
100 return int(result.output.split()[0])
101
102 def _GetStagingDirSize(self):
103 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
104 redirect_stdout=True)
105 return int(result.output.split()[0])
106
Ryan Cui3045c5d2012-07-13 18:00:33 -0700107 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800108 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
109 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700110 return result.returncode == 0
111
112 def _DisableRootfsVerification(self):
113 if not self.options.force:
114 logging.error('Detected that the device has rootfs verification enabled.')
115 logging.info('This script can automatically remove the rootfs '
116 'verification, which requires that it reboot the device.')
117 logging.info('Make sure the device is in developer mode!')
118 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700119 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800120 # Since we stopped Chrome earlier, it's good form to start it up again.
121 if self.options.startui:
122 logging.info('Starting Chrome...')
123 self.host.RemoteSh('start ui')
124 raise DeployFailure('Need rootfs verification to be disabled. '
125 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700126
127 logging.info('Removing rootfs verification from %s', self.options.to)
128 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
129 # Use --force to bypass the checks.
130 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
131 '--remove_rootfs_verification --force')
132 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
133 self.host.RemoteSh(cmd % partition, error_code_ok=True)
134
135 # A reboot in developer mode takes a while (and has delays), so the user
136 # will have time to read and act on the USB boot instructions below.
137 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
138 self.host.RemoteReboot()
139
David James88e6f032013-03-02 08:13:20 -0800140 # Now that the machine has been rebooted, we need to kill Chrome again.
141 self._KillProcsIfNeeded()
142
143 # Make sure the rootfs is writable now.
144 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700145
146 def _CheckUiJobStarted(self):
147 # status output is in the format:
148 # <job_name> <status> ['process' <pid>].
149 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800150 try:
151 result = self.host.RemoteSh('status ui')
152 except cros_build_lib.RunCommandError as e:
153 if 'Unknown job' in e.result.error:
154 return False
155 else:
156 raise e
157
Ryan Cui3045c5d2012-07-13 18:00:33 -0700158 return result.output.split()[1].split('/')[0] == 'start'
159
160 def _KillProcsIfNeeded(self):
161 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800162 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700163 self.host.RemoteSh('stop ui')
164
165 # Developers sometimes run session_manager manually, in which case we'll
166 # need to help shut the chrome processes down.
167 try:
David James3432acd2013-11-27 10:02:18 -0800168 with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700169 while self._ChromeFileInUse():
170 logging.warning('The chrome binary on the device is in use.')
171 logging.warning('Killing chrome and session_manager processes...\n')
172
173 self.host.RemoteSh("pkill 'chrome|session_manager'",
174 error_code_ok=True)
175 # Wait for processes to actually terminate
176 time.sleep(POST_KILL_WAIT)
177 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800178 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800179 msg = ('Could not kill processes after %s seconds. Please exit any '
180 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
181 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700182
David James88e6f032013-03-02 08:13:20 -0800183 def _MountRootfsAsWritable(self, error_code_ok=True):
184 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700185
David James88e6f032013-03-02 08:13:20 -0800186 If the command fails, and error_code_ok is True, then this function sets
187 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700188
Mike Frysinger02e1e072013-11-10 22:11:34 -0500189 Args:
David James88e6f032013-03-02 08:13:20 -0800190 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
191 """
192 result = self.host.RemoteSh(MOUNT_RW_COMMAND, error_code_ok=error_code_ok)
193 if result.returncode:
194 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700195
Ryan Cui7193a7e2013-04-26 14:15:19 -0700196 def _GetDeviceInfo(self):
197 steps = [
198 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
199 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
200 ]
201 return_values = parallel.RunParallelSteps(steps, return_values=True)
202 return DeviceInfo(*return_values)
203
204 def _CheckDeviceFreeSpace(self, device_info):
205 """See if target device has enough space for Chrome.
206
Mike Frysinger02e1e072013-11-10 22:11:34 -0500207 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700208 device_info: A DeviceInfo named tuple.
209 """
210 effective_free = device_info.target_dir_size + device_info.target_fs_free
211 staging_size = self._GetStagingDirSize()
212 if effective_free < staging_size:
213 raise DeployFailure(
214 'Not enough free space on the device. Required: %s MB, '
215 'actual: %s MB.' % (staging_size/1024, effective_free/1024))
216 if device_info.target_fs_free < (100 * 1024):
217 logging.warning('The device has less than 100MB free. deploy_chrome may '
218 'hang during the transfer.')
219
Ryan Cui3045c5d2012-07-13 18:00:33 -0700220 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800221 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700222 # Show the output (status) for this command.
Pawel Osciak577773a2013-03-05 10:52:12 -0800223 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
224 self.options.target_dir,
David Jamesa6e08892013-03-01 13:34:11 -0800225 inplace=True, debug_level=logging.INFO,
226 verbose=self.options.verbose)
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800227 if self.options.startui:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800228 logging.info('Starting Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700229 self.host.RemoteSh('start ui')
230
David James88e6f032013-03-02 08:13:20 -0800231 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700232 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800233 logging.info('Testing connection to the device...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700234 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800235 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700236 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800237 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700238
David James88e6f032013-03-02 08:13:20 -0800239 def _PrepareStagingDir(self):
240 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir)
241
Thiago Goncales12793312013-05-23 11:26:17 -0700242 def _MountTarget(self):
243 logging.info('Mounting Chrome...')
244
245 # Create directory if does not exist
246 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
247 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
248 self.options.mount_dir))
249 # Chrome needs partition to have exec and suid flags set
250 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
251
David James88e6f032013-03-02 08:13:20 -0800252 def Perform(self):
253 # If requested, just do the staging step.
254 if self.options.staging_only:
255 self._PrepareStagingDir()
256 return 0
257
258 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800259 # stop printing output at that point, and halt any running steps.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700260 steps = [self._GetDeviceInfo, self._PrepareStagingDir,
261 self._CheckConnection, self._KillProcsIfNeeded,
262 self._MountRootfsAsWritable]
263 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
264 return_values=True)
265 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800266
267 # If we failed to mark the rootfs as writable, try disabling rootfs
268 # verification.
269 if self._rootfs_is_still_readonly.is_set():
270 self._DisableRootfsVerification()
271
Thiago Goncales12793312013-05-23 11:26:17 -0700272 if self.options.mount_dir is not None:
273 self._MountTarget()
274
David James88e6f032013-03-02 08:13:20 -0800275 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700276 self._Deploy()
277
278
Ryan Cuia56a71e2012-10-18 18:40:35 -0700279def ValidateGypDefines(_option, _opt, value):
280 """Convert GYP_DEFINES-formatted string to dictionary."""
281 return chrome_util.ProcessGypDefines(value)
282
283
284class CustomOption(commandline.Option):
285 """Subclass Option class to implement path evaluation."""
286 TYPES = commandline.Option.TYPES + ('gyp_defines',)
287 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
288 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
289
290
Ryan Cuie535b172012-10-19 18:25:03 -0700291def _CreateParser():
292 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800293 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
294 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700295
Ryan Cuia56a71e2012-10-18 18:40:35 -0700296 # TODO(rcui): Have this use the UI-V2 format of having source and target
297 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700298 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800299 help='Skip all prompts (i.e., for disabling of rootfs '
300 'verification). This may result in the target '
301 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800302 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
303 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800304 help="The board the Chrome build is targeted for. When in "
305 "a 'cros chrome-sdk' shell, defaults to the SDK "
306 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700307 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800308 help='The directory with Chrome build artifacts to deploy '
309 'from. Typically of format <chrome_root>/out/Debug. '
310 'When this option is used, the GYP_DEFINES '
311 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800312 parser.add_option('--target-dir', type='path',
313 help='Target directory on device to deploy Chrome into.',
Thiago Goncales12793312013-05-23 11:26:17 -0700314 default=None)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700315 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800316 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800317 parser.add_option('--nostartui', action='store_false', dest='startui',
318 default=True,
319 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800320 parser.add_option('--nostrip', action='store_false', dest='dostrip',
321 default=True,
322 help="Don't strip binaries during deployment. Warning: "
323 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700324 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800325 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700326 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800327 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700328 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800329 help='Show more debug output.')
Thiago Goncales12793312013-05-23 11:26:17 -0700330 parser.add_option('--mount-dir', type='path', default=None,
331 help='Deploy Chrome in target directory and bind it'
332 'to directory specified by this flag.')
333 parser.add_option('--mount', action='store_true', default=False,
334 help='Deploy Chrome to default target directory and bind it'
335 'to default mount directory.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700336
337 group = optparse.OptionGroup(parser, 'Advanced Options')
338 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800339 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800340 group.add_option('--sloppy', action='store_true', default=False,
341 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700342 group.add_option('--staging-flags', default=None, type='gyp_defines',
343 help='Extra flags to control staging. Valid flags are - %s'
344 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800345 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800346 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800347 'variable and --staging-flags, if set. Enforce that '
348 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700349 group.add_option('--strip-flags', default=None,
350 help="Flags to call the 'strip' binutil tool with. "
351 "Overrides the default arguments.")
Ryan Cuief91e702013-02-04 12:06:36 -0800352
Ryan Cuia56a71e2012-10-18 18:40:35 -0700353 parser.add_option_group(group)
354
Ryan Cuif890a3e2013-03-07 18:57:06 -0800355 # GYP_DEFINES that Chrome was built with. Influences which files are staged
356 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
357 # enviroment variable.
358 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
359 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700360 # Path of an empty directory to stage chrome artifacts to. Defaults to a
361 # temporary directory that is removed when the script finishes. If the path
362 # is specified, then it will not be removed.
363 parser.add_option('--staging-dir', type='path', default=None,
364 help=optparse.SUPPRESS_HELP)
365 # Only prepare the staging directory, and skip deploying to the device.
366 parser.add_option('--staging-only', action='store_true', default=False,
367 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700368 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
369 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
370 # fetching the SDK toolchain.
371 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700372 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700373
Ryan Cuie535b172012-10-19 18:25:03 -0700374
375def _ParseCommandLine(argv):
376 """Parse args, and run environment-independent checks."""
377 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700378 (options, args) = parser.parse_args(argv)
379
Ryan Cuia56a71e2012-10-18 18:40:35 -0700380 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
381 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800382 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700383 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
384 parser.error('Cannot specify both --build_dir and '
385 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800386 if options.build_dir and not options.board:
387 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700388 if options.gs_path and options.local_pkg_path:
389 parser.error('Cannot specify both --gs-path and --local-pkg-path')
390 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700391 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800392 if (options.strict or options.staging_flags) and not options.build_dir:
393 parser.error('--strict and --staging-flags require --build-dir to be '
394 'set.')
395 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800396 parser.error('--staging-flags requires --strict to be set.')
397 if options.sloppy and options.strict:
398 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700399
400 if options.mount or options.mount_dir:
401 if not options.target_dir:
402 options.target_dir = _CHROME_DIR_MOUNT
403 else:
404 if not options.target_dir:
405 options.target_dir = _CHROME_DIR
406
407 if options.mount and not options.mount_dir:
408 options.mount_dir = _CHROME_DIR
409
Ryan Cui3045c5d2012-07-13 18:00:33 -0700410 return options, args
411
412
Ryan Cuie535b172012-10-19 18:25:03 -0700413def _PostParseCheck(options, _args):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700414 """Perform some usage validation (after we've parsed the arguments
415
416 Args:
417 options/args: The options/args object returned by optparse
418 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700419 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
420 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
421
Ryan Cuib623e7b2013-03-14 12:54:11 -0700422 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700423 gyp_env = os.getenv('GYP_DEFINES', None)
424 if gyp_env is not None:
425 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700426 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700427 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700428
429 if options.strict and not options.gyp_defines:
430 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800431 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700432
Ryan Cui3c183c22013-04-29 18:04:11 -0700433 if options.build_dir:
434 chrome_path = os.path.join(options.build_dir, 'chrome')
435 if os.path.isfile(chrome_path):
436 deps = lddtree.ParseELF(chrome_path)
437 if 'libbase.so' in deps['libs']:
438 cros_build_lib.Warning(
439 'Detected a component build of Chrome. component build is '
440 'not working properly for Chrome OS. See crbug.com/196317. '
441 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700442
Ryan Cuia56a71e2012-10-18 18:40:35 -0700443
Ryan Cui504db722013-01-22 11:48:01 -0800444def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700445 """Get the chrome prebuilt tarball from GS.
446
Mike Frysinger02e1e072013-11-10 22:11:34 -0500447 Returns:
448 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700449 """
David James9374aac2013-10-08 16:00:17 -0700450 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800451 files = gs_ctx.LS(gs_path).output.splitlines()
452 files = [found for found in files if
453 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
454 if not files:
455 raise Exception('No chrome package found at %s' % gs_path)
456 elif len(files) > 1:
457 # - Users should provide us with a direct link to either a stripped or
458 # unstripped chrome package.
459 # - In the case of being provided with an archive directory, where both
460 # stripped and unstripped chrome available, use the stripped chrome
461 # package.
462 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
463 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
464 files = [f for f in files if not 'unstripped' in f]
465 assert len(files) == 1
466 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800467
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800468 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800469 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800470 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
471 chrome_path = os.path.join(tempdir, filename)
472 assert os.path.exists(chrome_path)
473 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700474
475
Ryan Cuif890a3e2013-03-07 18:57:06 -0800476@contextlib.contextmanager
477def _StripBinContext(options):
478 if not options.dostrip:
479 yield None
480 elif options.strip_bin:
481 yield options.strip_bin
482 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800483 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800484 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
485 with sdk.Prepare(components=components) as ctx:
486 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
487 constants.CHROME_ENV_FILE)
488 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
489 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
490 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800491 yield strip_bin
492
493
494def _PrepareStagingDir(options, tempdir, staging_dir):
495 """Place the necessary files in the staging directory.
496
497 The staging directory is the directory used to rsync the build artifacts over
498 to the device. Only the necessary Chrome build artifacts are put into the
499 staging directory.
500 """
Ryan Cui5866be02013-03-18 14:12:00 -0700501 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400502 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800503 if options.build_dir:
504 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700505 strip_flags = (None if options.strip_flags is None else
506 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800507 chrome_util.StageChromeFromBuildDir(
508 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800509 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700510 staging_flags=options.staging_flags,
511 strip_flags=strip_flags)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700512 else:
513 pkg_path = options.local_pkg_path
514 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800515 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
516 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700517
518 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800519 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700520 # Extract only the ./opt/google/chrome contents, directly into the staging
521 # dir, collapsing the directory hierarchy.
522 cros_build_lib.DebugRunCommand(
523 ['tar', '--strip-components', '4', '--extract',
524 '--preserve-permissions', '--file', pkg_path, '.%s' % _CHROME_DIR],
525 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700526
Ryan Cui71aa8de2013-04-19 16:12:55 -0700527
Ryan Cui3045c5d2012-07-13 18:00:33 -0700528def main(argv):
529 options, args = _ParseCommandLine(argv)
530 _PostParseCheck(options, args)
531
532 # Set cros_build_lib debug level to hide RunCommand spew.
533 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700534 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700535 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800536 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700537
Ryan Cui71aa8de2013-04-19 16:12:55 -0700538 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700539 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
540 if cmd_stats:
541 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700542
Ryan Cui71aa8de2013-04-19 16:12:55 -0700543 with osutils.TempDir(set_global=True) as tempdir:
544 staging_dir = options.staging_dir
545 if not staging_dir:
546 staging_dir = os.path.join(tempdir, 'chrome')
547
548 deploy = DeployChrome(options, tempdir, staging_dir)
549 try:
550 deploy.Perform()
551 except results_lib.StepFailure as ex:
552 raise SystemExit(str(ex).strip())