blob: 67c801341e48df752c3a249833afa11f85dc3e1b [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
Ryan Cui3c183c22013-04-29 18:04:11 -070043from chromite.scripts import lddtree
Ryan Cui3045c5d2012-07-13 18:00:33 -070044
45
Ryan Cuia56a71e2012-10-18 18:40:35 -070046_USAGE = "deploy_chrome [--]\n\n %s" % __doc__
47
Ryan Cui3045c5d2012-07-13 18:00:33 -070048KERNEL_A_PARTITION = 2
49KERNEL_B_PARTITION = 4
50
51KILL_PROC_MAX_WAIT = 10
52POST_KILL_WAIT = 2
53
Ryan Cuie535b172012-10-19 18:25:03 -070054MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080055LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070056
David James2cb34002013-03-01 18:42:40 -080057_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070058_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
David James2cb34002013-03-01 18:42:40 -080059
Thiago Goncales12793312013-05-23 11:26:17 -070060_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
61_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Ryan Cui3045c5d2012-07-13 18:00:33 -070062
Ryan Cui3045c5d2012-07-13 18:00:33 -070063def _UrlBaseName(url):
64 """Return the last component of the URL."""
65 return url.rstrip('/').rpartition('/')[-1]
66
67
David James88e6f032013-03-02 08:13:20 -080068class DeployFailure(results_lib.StepFailure):
69 """Raised whenever the deploy fails."""
70
71
Ryan Cui7193a7e2013-04-26 14:15:19 -070072DeviceInfo = collections.namedtuple(
73 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
74
75
Ryan Cui3045c5d2012-07-13 18:00:33 -070076class DeployChrome(object):
77 """Wraps the core deployment functionality."""
Ryan Cuia56a71e2012-10-18 18:40:35 -070078 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070079 """Initialize the class.
80
81 Arguments:
82 options: Optparse result structure.
83 tempdir: Scratch space for the class. Caller has responsibility to clean
84 it up.
Ryan Cuie535b172012-10-19 18:25:03 -070085 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070086 self.tempdir = tempdir
87 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070088 self.staging_dir = staging_dir
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070089 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080090 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -070091
Ryan Cui7193a7e2013-04-26 14:15:19 -070092 def _GetRemoteMountFree(self, remote_dir):
93 result = self.host.RemoteSh('df -k %s' % remote_dir)
94 line = result.output.splitlines()[1]
95 return int(line.split()[3])
96
97 def _GetRemoteDirSize(self, remote_dir):
98 result = self.host.RemoteSh('du -ks %s' % remote_dir)
99 return int(result.output.split()[0])
100
101 def _GetStagingDirSize(self):
102 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
103 redirect_stdout=True)
104 return int(result.output.split()[0])
105
Ryan Cui3045c5d2012-07-13 18:00:33 -0700106 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800107 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
108 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700109 return result.returncode == 0
110
111 def _DisableRootfsVerification(self):
112 if not self.options.force:
113 logging.error('Detected that the device has rootfs verification enabled.')
114 logging.info('This script can automatically remove the rootfs '
115 'verification, which requires that it reboot the device.')
116 logging.info('Make sure the device is in developer mode!')
117 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700118 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800119 # Since we stopped Chrome earlier, it's good form to start it up again.
120 if self.options.startui:
121 logging.info('Starting Chrome...')
122 self.host.RemoteSh('start ui')
123 raise DeployFailure('Need rootfs verification to be disabled. '
124 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700125
126 logging.info('Removing rootfs verification from %s', self.options.to)
127 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
128 # Use --force to bypass the checks.
129 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
130 '--remove_rootfs_verification --force')
131 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
132 self.host.RemoteSh(cmd % partition, error_code_ok=True)
133
134 # A reboot in developer mode takes a while (and has delays), so the user
135 # will have time to read and act on the USB boot instructions below.
136 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
137 self.host.RemoteReboot()
138
David James88e6f032013-03-02 08:13:20 -0800139 # Now that the machine has been rebooted, we need to kill Chrome again.
140 self._KillProcsIfNeeded()
141
142 # Make sure the rootfs is writable now.
143 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700144
145 def _CheckUiJobStarted(self):
146 # status output is in the format:
147 # <job_name> <status> ['process' <pid>].
148 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800149 try:
150 result = self.host.RemoteSh('status ui')
151 except cros_build_lib.RunCommandError as e:
152 if 'Unknown job' in e.result.error:
153 return False
154 else:
155 raise e
156
Ryan Cui3045c5d2012-07-13 18:00:33 -0700157 return result.output.split()[1].split('/')[0] == 'start'
158
159 def _KillProcsIfNeeded(self):
160 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800161 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700162 self.host.RemoteSh('stop ui')
163
164 # Developers sometimes run session_manager manually, in which case we'll
165 # need to help shut the chrome processes down.
166 try:
167 with cros_build_lib.SubCommandTimeout(KILL_PROC_MAX_WAIT):
168 while self._ChromeFileInUse():
169 logging.warning('The chrome binary on the device is in use.')
170 logging.warning('Killing chrome and session_manager processes...\n')
171
172 self.host.RemoteSh("pkill 'chrome|session_manager'",
173 error_code_ok=True)
174 # Wait for processes to actually terminate
175 time.sleep(POST_KILL_WAIT)
176 logging.info('Rechecking the chrome binary...')
177 except cros_build_lib.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800178 msg = ('Could not kill processes after %s seconds. Please exit any '
179 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
180 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700181
David James88e6f032013-03-02 08:13:20 -0800182 def _MountRootfsAsWritable(self, error_code_ok=True):
183 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700184
David James88e6f032013-03-02 08:13:20 -0800185 If the command fails, and error_code_ok is True, then this function sets
186 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700187
David James88e6f032013-03-02 08:13:20 -0800188 Arguments:
189 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
190 """
191 result = self.host.RemoteSh(MOUNT_RW_COMMAND, error_code_ok=error_code_ok)
192 if result.returncode:
193 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700194
Ryan Cui7193a7e2013-04-26 14:15:19 -0700195 def _GetDeviceInfo(self):
196 steps = [
197 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
198 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
199 ]
200 return_values = parallel.RunParallelSteps(steps, return_values=True)
201 return DeviceInfo(*return_values)
202
203 def _CheckDeviceFreeSpace(self, device_info):
204 """See if target device has enough space for Chrome.
205
206 Arguments:
207 device_info: A DeviceInfo named tuple.
208 """
209 effective_free = device_info.target_dir_size + device_info.target_fs_free
210 staging_size = self._GetStagingDirSize()
211 if effective_free < staging_size:
212 raise DeployFailure(
213 'Not enough free space on the device. Required: %s MB, '
214 'actual: %s MB.' % (staging_size/1024, effective_free/1024))
215 if device_info.target_fs_free < (100 * 1024):
216 logging.warning('The device has less than 100MB free. deploy_chrome may '
217 'hang during the transfer.')
218
Ryan Cui3045c5d2012-07-13 18:00:33 -0700219 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800220 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700221 # Show the output (status) for this command.
Pawel Osciak577773a2013-03-05 10:52:12 -0800222 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
223 self.options.target_dir,
David Jamesa6e08892013-03-01 13:34:11 -0800224 inplace=True, debug_level=logging.INFO,
225 verbose=self.options.verbose)
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800226 if self.options.startui:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800227 logging.info('Starting Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700228 self.host.RemoteSh('start ui')
229
David James88e6f032013-03-02 08:13:20 -0800230 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700231 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800232 logging.info('Testing connection to the device...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700233 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800234 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700235 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800236 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700237
David James88e6f032013-03-02 08:13:20 -0800238 def _PrepareStagingDir(self):
239 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir)
240
Thiago Goncales12793312013-05-23 11:26:17 -0700241 def _MountTarget(self):
242 logging.info('Mounting Chrome...')
243
244 # Create directory if does not exist
245 self.host.RemoteSh('mkdir -p --mode 0775 %s' % (self.options.mount_dir,))
246 self.host.RemoteSh(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
247 self.options.mount_dir))
248 # Chrome needs partition to have exec and suid flags set
249 self.host.RemoteSh(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
250
David James88e6f032013-03-02 08:13:20 -0800251 def Perform(self):
252 # If requested, just do the staging step.
253 if self.options.staging_only:
254 self._PrepareStagingDir()
255 return 0
256
257 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800258 # stop printing output at that point, and halt any running steps.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700259 steps = [self._GetDeviceInfo, self._PrepareStagingDir,
260 self._CheckConnection, self._KillProcsIfNeeded,
261 self._MountRootfsAsWritable]
262 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
263 return_values=True)
264 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800265
266 # If we failed to mark the rootfs as writable, try disabling rootfs
267 # verification.
268 if self._rootfs_is_still_readonly.is_set():
269 self._DisableRootfsVerification()
270
Thiago Goncales12793312013-05-23 11:26:17 -0700271 if self.options.mount_dir is not None:
272 self._MountTarget()
273
David James88e6f032013-03-02 08:13:20 -0800274 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700275 self._Deploy()
276
277
Ryan Cuia56a71e2012-10-18 18:40:35 -0700278def ValidateGypDefines(_option, _opt, value):
279 """Convert GYP_DEFINES-formatted string to dictionary."""
280 return chrome_util.ProcessGypDefines(value)
281
282
283class CustomOption(commandline.Option):
284 """Subclass Option class to implement path evaluation."""
285 TYPES = commandline.Option.TYPES + ('gyp_defines',)
286 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
287 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
288
289
Ryan Cuie535b172012-10-19 18:25:03 -0700290def _CreateParser():
291 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800292 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
293 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700294
Ryan Cuia56a71e2012-10-18 18:40:35 -0700295 # TODO(rcui): Have this use the UI-V2 format of having source and target
296 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700297 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800298 help='Skip all prompts (i.e., for disabling of rootfs '
299 'verification). This may result in the target '
300 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800301 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
302 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800303 help="The board the Chrome build is targeted for. When in "
304 "a 'cros chrome-sdk' shell, defaults to the SDK "
305 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700306 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800307 help='The directory with Chrome build artifacts to deploy '
308 'from. Typically of format <chrome_root>/out/Debug. '
309 'When this option is used, the GYP_DEFINES '
310 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800311 parser.add_option('--target-dir', type='path',
312 help='Target directory on device to deploy Chrome into.',
Thiago Goncales12793312013-05-23 11:26:17 -0700313 default=None)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700314 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800315 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800316 parser.add_option('--nostartui', action='store_false', dest='startui',
317 default=True,
318 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800319 parser.add_option('--nostrip', action='store_false', dest='dostrip',
320 default=True,
321 help="Don't strip binaries during deployment. Warning: "
322 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700323 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800324 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700325 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800326 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700327 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800328 help='Show more debug output.')
Thiago Goncales12793312013-05-23 11:26:17 -0700329 parser.add_option('--mount-dir', type='path', default=None,
330 help='Deploy Chrome in target directory and bind it'
331 'to directory specified by this flag.')
332 parser.add_option('--mount', action='store_true', default=False,
333 help='Deploy Chrome to default target directory and bind it'
334 'to default mount directory.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700335
336 group = optparse.OptionGroup(parser, 'Advanced Options')
337 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800338 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800339 group.add_option('--sloppy', action='store_true', default=False,
340 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700341 group.add_option('--staging-flags', default=None, type='gyp_defines',
342 help='Extra flags to control staging. Valid flags are - %s'
343 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800344 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800345 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800346 'variable and --staging-flags, if set. Enforce that '
347 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700348 group.add_option('--strip-flags', default=None,
349 help="Flags to call the 'strip' binutil tool with. "
350 "Overrides the default arguments.")
Ryan Cuief91e702013-02-04 12:06:36 -0800351
Ryan Cuia56a71e2012-10-18 18:40:35 -0700352 parser.add_option_group(group)
353
Ryan Cuif890a3e2013-03-07 18:57:06 -0800354 # GYP_DEFINES that Chrome was built with. Influences which files are staged
355 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
356 # enviroment variable.
357 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
358 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700359 # Path of an empty directory to stage chrome artifacts to. Defaults to a
360 # temporary directory that is removed when the script finishes. If the path
361 # is specified, then it will not be removed.
362 parser.add_option('--staging-dir', type='path', default=None,
363 help=optparse.SUPPRESS_HELP)
364 # Only prepare the staging directory, and skip deploying to the device.
365 parser.add_option('--staging-only', action='store_true', default=False,
366 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700367 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
368 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
369 # fetching the SDK toolchain.
370 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700371 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700372
Ryan Cuie535b172012-10-19 18:25:03 -0700373
374def _ParseCommandLine(argv):
375 """Parse args, and run environment-independent checks."""
376 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700377 (options, args) = parser.parse_args(argv)
378
Ryan Cuia56a71e2012-10-18 18:40:35 -0700379 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
380 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800381 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700382 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
383 parser.error('Cannot specify both --build_dir and '
384 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800385 if options.build_dir and not options.board:
386 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700387 if options.gs_path and options.local_pkg_path:
388 parser.error('Cannot specify both --gs-path and --local-pkg-path')
389 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700390 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800391 if (options.strict or options.staging_flags) and not options.build_dir:
392 parser.error('--strict and --staging-flags require --build-dir to be '
393 'set.')
394 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800395 parser.error('--staging-flags requires --strict to be set.')
396 if options.sloppy and options.strict:
397 parser.error('Cannot specify both --strict and --sloppy.')
Thiago Goncales12793312013-05-23 11:26:17 -0700398
399 if options.mount or options.mount_dir:
400 if not options.target_dir:
401 options.target_dir = _CHROME_DIR_MOUNT
402 else:
403 if not options.target_dir:
404 options.target_dir = _CHROME_DIR
405
406 if options.mount and not options.mount_dir:
407 options.mount_dir = _CHROME_DIR
408
Ryan Cui3045c5d2012-07-13 18:00:33 -0700409 return options, args
410
411
Ryan Cuie535b172012-10-19 18:25:03 -0700412def _PostParseCheck(options, _args):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700413 """Perform some usage validation (after we've parsed the arguments
414
415 Args:
416 options/args: The options/args object returned by optparse
417 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700418 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
419 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
420
Ryan Cuib623e7b2013-03-14 12:54:11 -0700421 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700422 gyp_env = os.getenv('GYP_DEFINES', None)
423 if gyp_env is not None:
424 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700425 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700426 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700427
428 if options.strict and not options.gyp_defines:
429 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800430 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700431
Ryan Cui3c183c22013-04-29 18:04:11 -0700432 if options.build_dir:
433 chrome_path = os.path.join(options.build_dir, 'chrome')
434 if os.path.isfile(chrome_path):
435 deps = lddtree.ParseELF(chrome_path)
436 if 'libbase.so' in deps['libs']:
437 cros_build_lib.Warning(
438 'Detected a component build of Chrome. component build is '
439 'not working properly for Chrome OS. See crbug.com/196317. '
440 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700441
Ryan Cuia56a71e2012-10-18 18:40:35 -0700442
Ryan Cui504db722013-01-22 11:48:01 -0800443def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700444 """Get the chrome prebuilt tarball from GS.
445
446 Returns: Path to the fetched chrome tarball.
447 """
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800448 gs_ctx = gs.GSContext.Cached(cache_dir, init_boto=True)
449 files = gs_ctx.LS(gs_path).output.splitlines()
450 files = [found for found in files if
451 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
452 if not files:
453 raise Exception('No chrome package found at %s' % gs_path)
454 elif len(files) > 1:
455 # - Users should provide us with a direct link to either a stripped or
456 # unstripped chrome package.
457 # - In the case of being provided with an archive directory, where both
458 # stripped and unstripped chrome available, use the stripped chrome
459 # package.
460 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
461 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
462 files = [f for f in files if not 'unstripped' in f]
463 assert len(files) == 1
464 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800465
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800466 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800467 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800468 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
469 chrome_path = os.path.join(tempdir, filename)
470 assert os.path.exists(chrome_path)
471 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700472
473
Ryan Cuif890a3e2013-03-07 18:57:06 -0800474@contextlib.contextmanager
475def _StripBinContext(options):
476 if not options.dostrip:
477 yield None
478 elif options.strip_bin:
479 yield options.strip_bin
480 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800481 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800482 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
483 with sdk.Prepare(components=components) as ctx:
484 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
485 constants.CHROME_ENV_FILE)
486 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
487 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
488 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800489 yield strip_bin
490
491
492def _PrepareStagingDir(options, tempdir, staging_dir):
493 """Place the necessary files in the staging directory.
494
495 The staging directory is the directory used to rsync the build artifacts over
496 to the device. Only the necessary Chrome build artifacts are put into the
497 staging directory.
498 """
Ryan Cui5866be02013-03-18 14:12:00 -0700499 osutils.SafeMakedirs(staging_dir)
500 os.chmod(staging_dir, 0755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800501 if options.build_dir:
502 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700503 strip_flags = (None if options.strip_flags is None else
504 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800505 chrome_util.StageChromeFromBuildDir(
506 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800507 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700508 staging_flags=options.staging_flags,
509 strip_flags=strip_flags)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700510 else:
511 pkg_path = options.local_pkg_path
512 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800513 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
514 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700515
516 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800517 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700518 # Extract only the ./opt/google/chrome contents, directly into the staging
519 # dir, collapsing the directory hierarchy.
520 cros_build_lib.DebugRunCommand(
521 ['tar', '--strip-components', '4', '--extract',
522 '--preserve-permissions', '--file', pkg_path, '.%s' % _CHROME_DIR],
523 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700524
Ryan Cui71aa8de2013-04-19 16:12:55 -0700525
Ryan Cui3045c5d2012-07-13 18:00:33 -0700526def main(argv):
527 options, args = _ParseCommandLine(argv)
528 _PostParseCheck(options, args)
529
530 # Set cros_build_lib debug level to hide RunCommand spew.
531 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700532 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700533 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800534 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700535
Ryan Cui71aa8de2013-04-19 16:12:55 -0700536 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700537 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
538 if cmd_stats:
539 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700540
Ryan Cui71aa8de2013-04-19 16:12:55 -0700541 with osutils.TempDir(set_global=True) as tempdir:
542 staging_dir = options.staging_dir
543 if not staging_dir:
544 staging_dir = os.path.join(tempdir, 'chrome')
545
546 deploy = DeployChrome(options, tempdir, staging_dir)
547 try:
548 deploy.Perform()
549 except results_lib.StepFailure as ex:
550 raise SystemExit(str(ex).strip())