blob: 232d63ab47aa5e0b106806d17fb16b3b60bbde83 [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
Mike Frysinger02e1e072013-11-10 22:11:34 -050081 Args:
Ryan Cuie535b172012-10-19 18:25:03 -070082 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:
David James5691aed2013-11-27 11:33:27 -0800167 with cros_build_lib.Timeout(KILL_PROC_MAX_WAIT):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700168 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
Mike Frysinger02e1e072013-11-10 22:11:34 -0500188 Args:
David James88e6f032013-03-02 08:13:20 -0800189 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
Mike Frysinger02e1e072013-11-10 22:11:34 -0500206 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700207 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
Mike Frysinger02e1e072013-11-10 22:11:34 -0500446 Returns:
447 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700448 """
David James9374aac2013-10-08 16:00:17 -0700449 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800450 files = gs_ctx.LS(gs_path).output.splitlines()
451 files = [found for found in files if
452 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
453 if not files:
454 raise Exception('No chrome package found at %s' % gs_path)
455 elif len(files) > 1:
456 # - Users should provide us with a direct link to either a stripped or
457 # unstripped chrome package.
458 # - In the case of being provided with an archive directory, where both
459 # stripped and unstripped chrome available, use the stripped chrome
460 # package.
461 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
462 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
463 files = [f for f in files if not 'unstripped' in f]
464 assert len(files) == 1
465 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800466
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800467 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800468 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800469 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
470 chrome_path = os.path.join(tempdir, filename)
471 assert os.path.exists(chrome_path)
472 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700473
474
Ryan Cuif890a3e2013-03-07 18:57:06 -0800475@contextlib.contextmanager
476def _StripBinContext(options):
477 if not options.dostrip:
478 yield None
479 elif options.strip_bin:
480 yield options.strip_bin
481 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800482 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800483 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
484 with sdk.Prepare(components=components) as ctx:
485 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
486 constants.CHROME_ENV_FILE)
487 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
488 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
489 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800490 yield strip_bin
491
492
493def _PrepareStagingDir(options, tempdir, staging_dir):
494 """Place the necessary files in the staging directory.
495
496 The staging directory is the directory used to rsync the build artifacts over
497 to the device. Only the necessary Chrome build artifacts are put into the
498 staging directory.
499 """
Ryan Cui5866be02013-03-18 14:12:00 -0700500 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400501 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800502 if options.build_dir:
503 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700504 strip_flags = (None if options.strip_flags is None else
505 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800506 chrome_util.StageChromeFromBuildDir(
507 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800508 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700509 staging_flags=options.staging_flags,
510 strip_flags=strip_flags)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700511 else:
512 pkg_path = options.local_pkg_path
513 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800514 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
515 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700516
517 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800518 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700519 # Extract only the ./opt/google/chrome contents, directly into the staging
520 # dir, collapsing the directory hierarchy.
521 cros_build_lib.DebugRunCommand(
522 ['tar', '--strip-components', '4', '--extract',
523 '--preserve-permissions', '--file', pkg_path, '.%s' % _CHROME_DIR],
524 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700525
Ryan Cui71aa8de2013-04-19 16:12:55 -0700526
Ryan Cui3045c5d2012-07-13 18:00:33 -0700527def main(argv):
528 options, args = _ParseCommandLine(argv)
529 _PostParseCheck(options, args)
530
531 # Set cros_build_lib debug level to hide RunCommand spew.
532 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700533 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700534 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800535 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700536
Ryan Cui71aa8de2013-04-19 16:12:55 -0700537 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700538 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
539 if cmd_stats:
540 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700541
Ryan Cui71aa8de2013-04-19 16:12:55 -0700542 with osutils.TempDir(set_global=True) as tempdir:
543 staging_dir = options.staging_dir
544 if not staging_dir:
545 staging_dir = os.path.join(tempdir, 'chrome')
546
547 deploy = DeployChrome(options, tempdir, staging_dir)
548 try:
549 deploy.Perform()
550 except results_lib.StepFailure as ex:
551 raise SystemExit(str(ex).strip())