blob: 464515dc9e811cd8230eb849b97c226fed2a8140 [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'
58
Ryan Cui3045c5d2012-07-13 18:00:33 -070059
Ryan Cui3045c5d2012-07-13 18:00:33 -070060def _UrlBaseName(url):
61 """Return the last component of the URL."""
62 return url.rstrip('/').rpartition('/')[-1]
63
64
David James88e6f032013-03-02 08:13:20 -080065class DeployFailure(results_lib.StepFailure):
66 """Raised whenever the deploy fails."""
67
68
Ryan Cui7193a7e2013-04-26 14:15:19 -070069DeviceInfo = collections.namedtuple(
70 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
71
72
Ryan Cui3045c5d2012-07-13 18:00:33 -070073class DeployChrome(object):
74 """Wraps the core deployment functionality."""
Ryan Cuia56a71e2012-10-18 18:40:35 -070075 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070076 """Initialize the class.
77
78 Arguments:
79 options: Optparse result structure.
80 tempdir: Scratch space for the class. Caller has responsibility to clean
81 it up.
Ryan Cuie535b172012-10-19 18:25:03 -070082 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070083 self.tempdir = tempdir
84 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070085 self.staging_dir = staging_dir
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070086 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080087 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -070088
Ryan Cui7193a7e2013-04-26 14:15:19 -070089 def _GetRemoteMountFree(self, remote_dir):
90 result = self.host.RemoteSh('df -k %s' % remote_dir)
91 line = result.output.splitlines()[1]
92 return int(line.split()[3])
93
94 def _GetRemoteDirSize(self, remote_dir):
95 result = self.host.RemoteSh('du -ks %s' % remote_dir)
96 return int(result.output.split()[0])
97
98 def _GetStagingDirSize(self):
99 result = cros_build_lib.DebugRunCommand(['du', '-ks', self.staging_dir],
100 redirect_stdout=True)
101 return int(result.output.split()[0])
102
Ryan Cui3045c5d2012-07-13 18:00:33 -0700103 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800104 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
105 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700106 return result.returncode == 0
107
108 def _DisableRootfsVerification(self):
109 if not self.options.force:
110 logging.error('Detected that the device has rootfs verification enabled.')
111 logging.info('This script can automatically remove the rootfs '
112 'verification, which requires that it reboot the device.')
113 logging.info('Make sure the device is in developer mode!')
114 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -0700115 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -0800116 # Since we stopped Chrome earlier, it's good form to start it up again.
117 if self.options.startui:
118 logging.info('Starting Chrome...')
119 self.host.RemoteSh('start ui')
120 raise DeployFailure('Need rootfs verification to be disabled. '
121 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700122
123 logging.info('Removing rootfs verification from %s', self.options.to)
124 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
125 # Use --force to bypass the checks.
126 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
127 '--remove_rootfs_verification --force')
128 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
129 self.host.RemoteSh(cmd % partition, error_code_ok=True)
130
131 # A reboot in developer mode takes a while (and has delays), so the user
132 # will have time to read and act on the USB boot instructions below.
133 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
134 self.host.RemoteReboot()
135
David James88e6f032013-03-02 08:13:20 -0800136 # Now that the machine has been rebooted, we need to kill Chrome again.
137 self._KillProcsIfNeeded()
138
139 # Make sure the rootfs is writable now.
140 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700141
142 def _CheckUiJobStarted(self):
143 # status output is in the format:
144 # <job_name> <status> ['process' <pid>].
145 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800146 try:
147 result = self.host.RemoteSh('status ui')
148 except cros_build_lib.RunCommandError as e:
149 if 'Unknown job' in e.result.error:
150 return False
151 else:
152 raise e
153
Ryan Cui3045c5d2012-07-13 18:00:33 -0700154 return result.output.split()[1].split('/')[0] == 'start'
155
156 def _KillProcsIfNeeded(self):
157 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800158 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700159 self.host.RemoteSh('stop ui')
160
161 # Developers sometimes run session_manager manually, in which case we'll
162 # need to help shut the chrome processes down.
163 try:
164 with cros_build_lib.SubCommandTimeout(KILL_PROC_MAX_WAIT):
165 while self._ChromeFileInUse():
166 logging.warning('The chrome binary on the device is in use.')
167 logging.warning('Killing chrome and session_manager processes...\n')
168
169 self.host.RemoteSh("pkill 'chrome|session_manager'",
170 error_code_ok=True)
171 # Wait for processes to actually terminate
172 time.sleep(POST_KILL_WAIT)
173 logging.info('Rechecking the chrome binary...')
174 except cros_build_lib.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800175 msg = ('Could not kill processes after %s seconds. Please exit any '
176 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
177 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700178
David James88e6f032013-03-02 08:13:20 -0800179 def _MountRootfsAsWritable(self, error_code_ok=True):
180 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700181
David James88e6f032013-03-02 08:13:20 -0800182 If the command fails, and error_code_ok is True, then this function sets
183 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700184
David James88e6f032013-03-02 08:13:20 -0800185 Arguments:
186 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
187 """
188 result = self.host.RemoteSh(MOUNT_RW_COMMAND, error_code_ok=error_code_ok)
189 if result.returncode:
190 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700191
Ryan Cui7193a7e2013-04-26 14:15:19 -0700192 def _GetDeviceInfo(self):
193 steps = [
194 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
195 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
196 ]
197 return_values = parallel.RunParallelSteps(steps, return_values=True)
198 return DeviceInfo(*return_values)
199
200 def _CheckDeviceFreeSpace(self, device_info):
201 """See if target device has enough space for Chrome.
202
203 Arguments:
204 device_info: A DeviceInfo named tuple.
205 """
206 effective_free = device_info.target_dir_size + device_info.target_fs_free
207 staging_size = self._GetStagingDirSize()
208 if effective_free < staging_size:
209 raise DeployFailure(
210 'Not enough free space on the device. Required: %s MB, '
211 'actual: %s MB.' % (staging_size/1024, effective_free/1024))
212 if device_info.target_fs_free < (100 * 1024):
213 logging.warning('The device has less than 100MB free. deploy_chrome may '
214 'hang during the transfer.')
215
Ryan Cui3045c5d2012-07-13 18:00:33 -0700216 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800217 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700218 # Show the output (status) for this command.
Pawel Osciak577773a2013-03-05 10:52:12 -0800219 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
220 self.options.target_dir,
David Jamesa6e08892013-03-01 13:34:11 -0800221 inplace=True, debug_level=logging.INFO,
222 verbose=self.options.verbose)
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800223 if self.options.startui:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800224 logging.info('Starting Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700225 self.host.RemoteSh('start ui')
226
David James88e6f032013-03-02 08:13:20 -0800227 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700228 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800229 logging.info('Testing connection to the device...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700230 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800231 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700232 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800233 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700234
David James88e6f032013-03-02 08:13:20 -0800235 def _PrepareStagingDir(self):
236 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir)
237
238 def Perform(self):
239 # If requested, just do the staging step.
240 if self.options.staging_only:
241 self._PrepareStagingDir()
242 return 0
243
244 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800245 # stop printing output at that point, and halt any running steps.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700246 steps = [self._GetDeviceInfo, self._PrepareStagingDir,
247 self._CheckConnection, self._KillProcsIfNeeded,
248 self._MountRootfsAsWritable]
249 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
250 return_values=True)
251 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800252
253 # If we failed to mark the rootfs as writable, try disabling rootfs
254 # verification.
255 if self._rootfs_is_still_readonly.is_set():
256 self._DisableRootfsVerification()
257
258 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700259 self._Deploy()
260
261
Ryan Cuia56a71e2012-10-18 18:40:35 -0700262def ValidateGypDefines(_option, _opt, value):
263 """Convert GYP_DEFINES-formatted string to dictionary."""
264 return chrome_util.ProcessGypDefines(value)
265
266
267class CustomOption(commandline.Option):
268 """Subclass Option class to implement path evaluation."""
269 TYPES = commandline.Option.TYPES + ('gyp_defines',)
270 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
271 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
272
273
Ryan Cuie535b172012-10-19 18:25:03 -0700274def _CreateParser():
275 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800276 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
277 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700278
Ryan Cuia56a71e2012-10-18 18:40:35 -0700279 # TODO(rcui): Have this use the UI-V2 format of having source and target
280 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700281 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800282 help='Skip all prompts (i.e., for disabling of rootfs '
283 'verification). This may result in the target '
284 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800285 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
286 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800287 help="The board the Chrome build is targeted for. When in "
288 "a 'cros chrome-sdk' shell, defaults to the SDK "
289 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700290 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800291 help='The directory with Chrome build artifacts to deploy '
292 'from. Typically of format <chrome_root>/out/Debug. '
293 'When this option is used, the GYP_DEFINES '
294 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800295 parser.add_option('--target-dir', type='path',
296 help='Target directory on device to deploy Chrome into.',
297 default=_CHROME_DIR)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700298 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800299 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800300 parser.add_option('--nostartui', action='store_false', dest='startui',
301 default=True,
302 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800303 parser.add_option('--nostrip', action='store_false', dest='dostrip',
304 default=True,
305 help="Don't strip binaries during deployment. Warning: "
306 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700307 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800308 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700309 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800310 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700311 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800312 help='Show more debug output.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700313
314 group = optparse.OptionGroup(parser, 'Advanced Options')
315 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800316 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800317 group.add_option('--sloppy', action='store_true', default=False,
318 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700319 group.add_option('--staging-flags', default=None, type='gyp_defines',
320 help='Extra flags to control staging. Valid flags are - %s'
321 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800322 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800323 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800324 'variable and --staging-flags, if set. Enforce that '
325 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700326 group.add_option('--strip-flags', default=None,
327 help="Flags to call the 'strip' binutil tool with. "
328 "Overrides the default arguments.")
Ryan Cuief91e702013-02-04 12:06:36 -0800329
Ryan Cuia56a71e2012-10-18 18:40:35 -0700330 parser.add_option_group(group)
331
Ryan Cuif890a3e2013-03-07 18:57:06 -0800332 # GYP_DEFINES that Chrome was built with. Influences which files are staged
333 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
334 # enviroment variable.
335 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
336 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700337 # Path of an empty directory to stage chrome artifacts to. Defaults to a
338 # temporary directory that is removed when the script finishes. If the path
339 # is specified, then it will not be removed.
340 parser.add_option('--staging-dir', type='path', default=None,
341 help=optparse.SUPPRESS_HELP)
342 # Only prepare the staging directory, and skip deploying to the device.
343 parser.add_option('--staging-only', action='store_true', default=False,
344 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700345 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
346 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
347 # fetching the SDK toolchain.
348 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700349 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700350
Ryan Cuie535b172012-10-19 18:25:03 -0700351
352def _ParseCommandLine(argv):
353 """Parse args, and run environment-independent checks."""
354 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700355 (options, args) = parser.parse_args(argv)
356
Ryan Cuia56a71e2012-10-18 18:40:35 -0700357 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
358 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800359 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700360 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
361 parser.error('Cannot specify both --build_dir and '
362 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800363 if options.build_dir and not options.board:
364 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700365 if options.gs_path and options.local_pkg_path:
366 parser.error('Cannot specify both --gs-path and --local-pkg-path')
367 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700368 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800369 if (options.strict or options.staging_flags) and not options.build_dir:
370 parser.error('--strict and --staging-flags require --build-dir to be '
371 'set.')
372 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800373 parser.error('--staging-flags requires --strict to be set.')
374 if options.sloppy and options.strict:
375 parser.error('Cannot specify both --strict and --sloppy.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700376 return options, args
377
378
Ryan Cuie535b172012-10-19 18:25:03 -0700379def _PostParseCheck(options, _args):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700380 """Perform some usage validation (after we've parsed the arguments
381
382 Args:
383 options/args: The options/args object returned by optparse
384 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700385 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
386 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
387
Ryan Cuib623e7b2013-03-14 12:54:11 -0700388 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700389 gyp_env = os.getenv('GYP_DEFINES', None)
390 if gyp_env is not None:
391 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700392 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700393 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700394
395 if options.strict and not options.gyp_defines:
396 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800397 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700398
Ryan Cui3c183c22013-04-29 18:04:11 -0700399 if options.build_dir:
400 chrome_path = os.path.join(options.build_dir, 'chrome')
401 if os.path.isfile(chrome_path):
402 deps = lddtree.ParseELF(chrome_path)
403 if 'libbase.so' in deps['libs']:
404 cros_build_lib.Warning(
405 'Detected a component build of Chrome. component build is '
406 'not working properly for Chrome OS. See crbug.com/196317. '
407 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700408
Ryan Cuia56a71e2012-10-18 18:40:35 -0700409
Ryan Cui504db722013-01-22 11:48:01 -0800410def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700411 """Get the chrome prebuilt tarball from GS.
412
413 Returns: Path to the fetched chrome tarball.
414 """
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800415 gs_ctx = gs.GSContext.Cached(cache_dir, init_boto=True)
416 files = gs_ctx.LS(gs_path).output.splitlines()
417 files = [found for found in files if
418 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
419 if not files:
420 raise Exception('No chrome package found at %s' % gs_path)
421 elif len(files) > 1:
422 # - Users should provide us with a direct link to either a stripped or
423 # unstripped chrome package.
424 # - In the case of being provided with an archive directory, where both
425 # stripped and unstripped chrome available, use the stripped chrome
426 # package.
427 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
428 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
429 files = [f for f in files if not 'unstripped' in f]
430 assert len(files) == 1
431 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800432
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800433 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800434 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800435 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
436 chrome_path = os.path.join(tempdir, filename)
437 assert os.path.exists(chrome_path)
438 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700439
440
Ryan Cuif890a3e2013-03-07 18:57:06 -0800441@contextlib.contextmanager
442def _StripBinContext(options):
443 if not options.dostrip:
444 yield None
445 elif options.strip_bin:
446 yield options.strip_bin
447 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800448 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800449 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
450 with sdk.Prepare(components=components) as ctx:
451 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
452 constants.CHROME_ENV_FILE)
453 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
454 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
455 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800456 yield strip_bin
457
458
459def _PrepareStagingDir(options, tempdir, staging_dir):
460 """Place the necessary files in the staging directory.
461
462 The staging directory is the directory used to rsync the build artifacts over
463 to the device. Only the necessary Chrome build artifacts are put into the
464 staging directory.
465 """
Ryan Cui5866be02013-03-18 14:12:00 -0700466 osutils.SafeMakedirs(staging_dir)
467 os.chmod(staging_dir, 0755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800468 if options.build_dir:
469 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700470 strip_flags = (None if options.strip_flags is None else
471 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800472 chrome_util.StageChromeFromBuildDir(
473 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800474 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700475 staging_flags=options.staging_flags,
476 strip_flags=strip_flags)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700477 else:
478 pkg_path = options.local_pkg_path
479 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800480 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
481 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700482
483 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800484 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700485 # Extract only the ./opt/google/chrome contents, directly into the staging
486 # dir, collapsing the directory hierarchy.
487 cros_build_lib.DebugRunCommand(
488 ['tar', '--strip-components', '4', '--extract',
489 '--preserve-permissions', '--file', pkg_path, '.%s' % _CHROME_DIR],
490 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700491
Ryan Cui71aa8de2013-04-19 16:12:55 -0700492
Ryan Cui3045c5d2012-07-13 18:00:33 -0700493def main(argv):
494 options, args = _ParseCommandLine(argv)
495 _PostParseCheck(options, args)
496
497 # Set cros_build_lib debug level to hide RunCommand spew.
498 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700499 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700500 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800501 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700502
Ryan Cui71aa8de2013-04-19 16:12:55 -0700503 with stats.UploadContext() as queue:
Ryan Cuicbd9bb62013-04-30 11:17:02 -0700504 cmd_stats = stats.Stats.SafeInit(cmd_line=argv, cmd_base='deploy_chrome')
505 if cmd_stats:
506 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700507
Ryan Cui71aa8de2013-04-19 16:12:55 -0700508 with osutils.TempDir(set_global=True) as tempdir:
509 staging_dir = options.staging_dir
510 if not staging_dir:
511 staging_dir = os.path.join(tempdir, 'chrome')
512
513 deploy = DeployChrome(options, tempdir, staging_dir)
514 try:
515 deploy.Perform()
516 except results_lib.StepFailure as ex:
517 raise SystemExit(str(ex).strip())