blob: 7cb1323081a5f9c7b1e9d09c3866576ea1ef57b4 [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 Cuif890a3e2013-03-07 18:57:06 -080021import contextlib
Ryan Cui3045c5d2012-07-13 18:00:33 -070022import logging
David James88e6f032013-03-02 08:13:20 -080023import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070024import os
Ryan Cuia56a71e2012-10-18 18:40:35 -070025import optparse
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070026import shlex
Ryan Cui3045c5d2012-07-13 18:00:33 -070027import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070028
Ryan Cuia56a71e2012-10-18 18:40:35 -070029
David James629febb2012-11-25 13:07:34 -080030from chromite.buildbot import constants
David James88e6f032013-03-02 08:13:20 -080031from chromite.buildbot import cbuildbot_results as results_lib
Ryan Cui686ec052013-02-12 16:39:41 -080032from chromite.cros.commands import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070033from chromite.lib import chrome_util
Ryan Cui3045c5d2012-07-13 18:00:33 -070034from chromite.lib import cros_build_lib
Ryan Cuie535b172012-10-19 18:25:03 -070035from chromite.lib import commandline
Ryan Cui777ff422012-12-07 13:12:54 -080036from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070037from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080038from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070039from chromite.lib import remote_access as remote
Ryan Cui71aa8de2013-04-19 16:12:55 -070040from chromite.lib import stats
Ryan Cui3c183c22013-04-29 18:04:11 -070041from chromite.scripts import lddtree
Ryan Cui3045c5d2012-07-13 18:00:33 -070042
43
Ryan Cuia56a71e2012-10-18 18:40:35 -070044_USAGE = "deploy_chrome [--]\n\n %s" % __doc__
45
Ryan Cui3045c5d2012-07-13 18:00:33 -070046KERNEL_A_PARTITION = 2
47KERNEL_B_PARTITION = 4
48
49KILL_PROC_MAX_WAIT = 10
50POST_KILL_WAIT = 2
51
Ryan Cuie535b172012-10-19 18:25:03 -070052MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Pawel Osciak577773a2013-03-05 10:52:12 -080053LSOF_COMMAND = 'lsof %s/chrome'
Ryan Cui3045c5d2012-07-13 18:00:33 -070054
David James2cb34002013-03-01 18:42:40 -080055_CHROME_DIR = '/opt/google/chrome'
56
Ryan Cui3045c5d2012-07-13 18:00:33 -070057
Ryan Cui3045c5d2012-07-13 18:00:33 -070058def _UrlBaseName(url):
59 """Return the last component of the URL."""
60 return url.rstrip('/').rpartition('/')[-1]
61
62
David James88e6f032013-03-02 08:13:20 -080063class DeployFailure(results_lib.StepFailure):
64 """Raised whenever the deploy fails."""
65
66
Ryan Cui3045c5d2012-07-13 18:00:33 -070067class DeployChrome(object):
68 """Wraps the core deployment functionality."""
Ryan Cuia56a71e2012-10-18 18:40:35 -070069 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -070070 """Initialize the class.
71
72 Arguments:
73 options: Optparse result structure.
74 tempdir: Scratch space for the class. Caller has responsibility to clean
75 it up.
Ryan Cuie535b172012-10-19 18:25:03 -070076 """
Ryan Cui3045c5d2012-07-13 18:00:33 -070077 self.tempdir = tempdir
78 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -070079 self.staging_dir = staging_dir
Ryan Cuiafd6c5c2012-07-30 17:48:22 -070080 self.host = remote.RemoteAccess(options.to, tempdir, port=options.port)
David James88e6f032013-03-02 08:13:20 -080081 self._rootfs_is_still_readonly = multiprocessing.Event()
Ryan Cui3045c5d2012-07-13 18:00:33 -070082
Ryan Cui3045c5d2012-07-13 18:00:33 -070083 def _ChromeFileInUse(self):
Pawel Osciak577773a2013-03-05 10:52:12 -080084 result = self.host.RemoteSh(LSOF_COMMAND % (self.options.target_dir,),
85 error_code_ok=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -070086 return result.returncode == 0
87
88 def _DisableRootfsVerification(self):
89 if not self.options.force:
90 logging.error('Detected that the device has rootfs verification enabled.')
91 logging.info('This script can automatically remove the rootfs '
92 'verification, which requires that it reboot the device.')
93 logging.info('Make sure the device is in developer mode!')
94 logging.info('Skip this prompt by specifying --force.')
Brian Harring521e7242012-11-01 16:57:42 -070095 if not cros_build_lib.BooleanPrompt('Remove roots verification?', False):
David James88e6f032013-03-02 08:13:20 -080096 # Since we stopped Chrome earlier, it's good form to start it up again.
97 if self.options.startui:
98 logging.info('Starting Chrome...')
99 self.host.RemoteSh('start ui')
100 raise DeployFailure('Need rootfs verification to be disabled. '
101 'Aborting.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700102
103 logging.info('Removing rootfs verification from %s', self.options.to)
104 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
105 # Use --force to bypass the checks.
106 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
107 '--remove_rootfs_verification --force')
108 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
109 self.host.RemoteSh(cmd % partition, error_code_ok=True)
110
111 # A reboot in developer mode takes a while (and has delays), so the user
112 # will have time to read and act on the USB boot instructions below.
113 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
114 self.host.RemoteReboot()
115
David James88e6f032013-03-02 08:13:20 -0800116 # Now that the machine has been rebooted, we need to kill Chrome again.
117 self._KillProcsIfNeeded()
118
119 # Make sure the rootfs is writable now.
120 self._MountRootfsAsWritable(error_code_ok=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700121
122 def _CheckUiJobStarted(self):
123 # status output is in the format:
124 # <job_name> <status> ['process' <pid>].
125 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800126 try:
127 result = self.host.RemoteSh('status ui')
128 except cros_build_lib.RunCommandError as e:
129 if 'Unknown job' in e.result.error:
130 return False
131 else:
132 raise e
133
Ryan Cui3045c5d2012-07-13 18:00:33 -0700134 return result.output.split()[1].split('/')[0] == 'start'
135
136 def _KillProcsIfNeeded(self):
137 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800138 logging.info('Shutting down Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700139 self.host.RemoteSh('stop ui')
140
141 # Developers sometimes run session_manager manually, in which case we'll
142 # need to help shut the chrome processes down.
143 try:
144 with cros_build_lib.SubCommandTimeout(KILL_PROC_MAX_WAIT):
145 while self._ChromeFileInUse():
146 logging.warning('The chrome binary on the device is in use.')
147 logging.warning('Killing chrome and session_manager processes...\n')
148
149 self.host.RemoteSh("pkill 'chrome|session_manager'",
150 error_code_ok=True)
151 # Wait for processes to actually terminate
152 time.sleep(POST_KILL_WAIT)
153 logging.info('Rechecking the chrome binary...')
154 except cros_build_lib.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800155 msg = ('Could not kill processes after %s seconds. Please exit any '
156 'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
157 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700158
David James88e6f032013-03-02 08:13:20 -0800159 def _MountRootfsAsWritable(self, error_code_ok=True):
160 """Mount the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700161
David James88e6f032013-03-02 08:13:20 -0800162 If the command fails, and error_code_ok is True, then this function sets
163 self._rootfs_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700164
David James88e6f032013-03-02 08:13:20 -0800165 Arguments:
166 error_code_ok: See remote.RemoteAccess.RemoteSh for details.
167 """
168 result = self.host.RemoteSh(MOUNT_RW_COMMAND, error_code_ok=error_code_ok)
169 if result.returncode:
170 self._rootfs_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700171
172 def _Deploy(self):
Pawel Osciak577773a2013-03-05 10:52:12 -0800173 logging.info('Copying Chrome to %s on device...', self.options.target_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700174 # Show the output (status) for this command.
Pawel Osciak577773a2013-03-05 10:52:12 -0800175 self.host.Rsync('%s/' % os.path.abspath(self.staging_dir),
176 self.options.target_dir,
David Jamesa6e08892013-03-01 13:34:11 -0800177 inplace=True, debug_level=logging.INFO,
178 verbose=self.options.verbose)
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800179 if self.options.startui:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800180 logging.info('Starting Chrome...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700181 self.host.RemoteSh('start ui')
182
David James88e6f032013-03-02 08:13:20 -0800183 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700184 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800185 logging.info('Testing connection to the device...')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700186 self.host.RemoteSh('true')
David James88e6f032013-03-02 08:13:20 -0800187 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700188 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800189 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700190
David James88e6f032013-03-02 08:13:20 -0800191 def _PrepareStagingDir(self):
192 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir)
193
194 def Perform(self):
195 # If requested, just do the staging step.
196 if self.options.staging_only:
197 self._PrepareStagingDir()
198 return 0
199
200 # Run setup steps in parallel. If any step fails, RunParallelSteps will
David James48f6b332013-03-03 20:11:18 -0800201 # stop printing output at that point, and halt any running steps.
David James88e6f032013-03-02 08:13:20 -0800202 steps = [self._PrepareStagingDir, self._CheckConnection,
203 self._KillProcsIfNeeded, self._MountRootfsAsWritable]
David James48f6b332013-03-03 20:11:18 -0800204 parallel.RunParallelSteps(steps, halt_on_error=True)
David James88e6f032013-03-02 08:13:20 -0800205
206 # If we failed to mark the rootfs as writable, try disabling rootfs
207 # verification.
208 if self._rootfs_is_still_readonly.is_set():
209 self._DisableRootfsVerification()
210
211 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700212 self._Deploy()
213
214
Ryan Cuia56a71e2012-10-18 18:40:35 -0700215def ValidateGypDefines(_option, _opt, value):
216 """Convert GYP_DEFINES-formatted string to dictionary."""
217 return chrome_util.ProcessGypDefines(value)
218
219
220class CustomOption(commandline.Option):
221 """Subclass Option class to implement path evaluation."""
222 TYPES = commandline.Option.TYPES + ('gyp_defines',)
223 TYPE_CHECKER = commandline.Option.TYPE_CHECKER.copy()
224 TYPE_CHECKER['gyp_defines'] = ValidateGypDefines
225
226
Ryan Cuie535b172012-10-19 18:25:03 -0700227def _CreateParser():
228 """Create our custom parser."""
Ryan Cui504db722013-01-22 11:48:01 -0800229 parser = commandline.OptionParser(usage=_USAGE, option_class=CustomOption,
230 caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700231
Ryan Cuia56a71e2012-10-18 18:40:35 -0700232 # TODO(rcui): Have this use the UI-V2 format of having source and target
233 # device be specified as positional arguments.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700234 parser.add_option('--force', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800235 help='Skip all prompts (i.e., for disabling of rootfs '
236 'verification). This may result in the target '
237 'machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800238 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
239 parser.add_option('--board', default=sdk_board_env,
Ryan Cui686ec052013-02-12 16:39:41 -0800240 help="The board the Chrome build is targeted for. When in "
241 "a 'cros chrome-sdk' shell, defaults to the SDK "
242 "board.")
Ryan Cuia56a71e2012-10-18 18:40:35 -0700243 parser.add_option('--build-dir', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800244 help='The directory with Chrome build artifacts to deploy '
245 'from. Typically of format <chrome_root>/out/Debug. '
246 'When this option is used, the GYP_DEFINES '
247 'environment variable must be set.')
Pawel Osciak577773a2013-03-05 10:52:12 -0800248 parser.add_option('--target-dir', type='path',
249 help='Target directory on device to deploy Chrome into.',
250 default=_CHROME_DIR)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700251 parser.add_option('-g', '--gs-path', type='gs_path',
Ryan Cui686ec052013-02-12 16:39:41 -0800252 help='GS path that contains the chrome to deploy.')
Ryan Cui82a1f2d2013-02-22 17:34:23 -0800253 parser.add_option('--nostartui', action='store_false', dest='startui',
254 default=True,
255 help="Don't restart the ui daemon after deployment.")
Ryan Cuif890a3e2013-03-07 18:57:06 -0800256 parser.add_option('--nostrip', action='store_false', dest='dostrip',
257 default=True,
258 help="Don't strip binaries during deployment. Warning: "
259 "the resulting binaries will be very large!")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700260 parser.add_option('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
Ryan Cui686ec052013-02-12 16:39:41 -0800261 help='Port of the target device to connect to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700262 parser.add_option('-t', '--to',
Ryan Cui686ec052013-02-12 16:39:41 -0800263 help='The IP address of the CrOS device to deploy to.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700264 parser.add_option('-v', '--verbose', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800265 help='Show more debug output.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700266
267 group = optparse.OptionGroup(parser, 'Advanced Options')
268 group.add_option('-l', '--local-pkg-path', type='path',
Ryan Cui686ec052013-02-12 16:39:41 -0800269 help='Path to local chrome prebuilt package to deploy.')
David Jamesa6e08892013-03-01 13:34:11 -0800270 group.add_option('--sloppy', action='store_true', default=False,
271 help='Ignore when mandatory artifacts are missing.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700272 group.add_option('--staging-flags', default=None, type='gyp_defines',
273 help='Extra flags to control staging. Valid flags are - %s'
274 % ', '.join(chrome_util.STAGING_FLAGS))
Ryan Cuief91e702013-02-04 12:06:36 -0800275 group.add_option('--strict', action='store_true', default=False,
Ryan Cui686ec052013-02-12 16:39:41 -0800276 help='Stage artifacts based on the GYP_DEFINES environment '
David Jamesa6e08892013-03-01 13:34:11 -0800277 'variable and --staging-flags, if set. Enforce that '
278 'all optional artifacts are deployed.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700279 group.add_option('--strip-flags', default=None,
280 help="Flags to call the 'strip' binutil tool with. "
281 "Overrides the default arguments.")
Ryan Cuief91e702013-02-04 12:06:36 -0800282
Ryan Cuia56a71e2012-10-18 18:40:35 -0700283 parser.add_option_group(group)
284
Ryan Cuif890a3e2013-03-07 18:57:06 -0800285 # GYP_DEFINES that Chrome was built with. Influences which files are staged
286 # when --build-dir is set. Defaults to reading from the GYP_DEFINES
287 # enviroment variable.
288 parser.add_option('--gyp-defines', default=None, type='gyp_defines',
289 help=optparse.SUPPRESS_HELP)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700290 # Path of an empty directory to stage chrome artifacts to. Defaults to a
291 # temporary directory that is removed when the script finishes. If the path
292 # is specified, then it will not be removed.
293 parser.add_option('--staging-dir', type='path', default=None,
294 help=optparse.SUPPRESS_HELP)
295 # Only prepare the staging directory, and skip deploying to the device.
296 parser.add_option('--staging-only', action='store_true', default=False,
297 help=optparse.SUPPRESS_HELP)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700298 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
299 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
300 # fetching the SDK toolchain.
301 parser.add_option('--strip-bin', default=None, help=optparse.SUPPRESS_HELP)
Ryan Cuie535b172012-10-19 18:25:03 -0700302 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700303
Ryan Cuie535b172012-10-19 18:25:03 -0700304
305def _ParseCommandLine(argv):
306 """Parse args, and run environment-independent checks."""
307 parser = _CreateParser()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700308 (options, args) = parser.parse_args(argv)
309
Ryan Cuia56a71e2012-10-18 18:40:35 -0700310 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
311 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800312 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700313 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
314 parser.error('Cannot specify both --build_dir and '
315 '--gs-path/--local-pkg-patch')
Ryan Cui686ec052013-02-12 16:39:41 -0800316 if options.build_dir and not options.board:
317 parser.error('--board is required when --build-dir is specified.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700318 if options.gs_path and options.local_pkg_path:
319 parser.error('Cannot specify both --gs-path and --local-pkg-path')
320 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700321 parser.error('Need to specify --to')
Ryan Cuief91e702013-02-04 12:06:36 -0800322 if (options.strict or options.staging_flags) and not options.build_dir:
323 parser.error('--strict and --staging-flags require --build-dir to be '
324 'set.')
325 if options.staging_flags and not options.strict:
David Jamesa6e08892013-03-01 13:34:11 -0800326 parser.error('--staging-flags requires --strict to be set.')
327 if options.sloppy and options.strict:
328 parser.error('Cannot specify both --strict and --sloppy.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700329 return options, args
330
331
Ryan Cuie535b172012-10-19 18:25:03 -0700332def _PostParseCheck(options, _args):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700333 """Perform some usage validation (after we've parsed the arguments
334
335 Args:
336 options/args: The options/args object returned by optparse
337 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700338 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
339 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
340
Ryan Cuib623e7b2013-03-14 12:54:11 -0700341 if not options.gyp_defines:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700342 gyp_env = os.getenv('GYP_DEFINES', None)
343 if gyp_env is not None:
344 options.gyp_defines = chrome_util.ProcessGypDefines(gyp_env)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700345 logging.debug('GYP_DEFINES taken from environment: %s',
Ryan Cuia56a71e2012-10-18 18:40:35 -0700346 options.gyp_defines)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700347
348 if options.strict and not options.gyp_defines:
349 cros_build_lib.Die('When --strict is set, the GYP_DEFINES environment '
Ryan Cui686ec052013-02-12 16:39:41 -0800350 'variable must be set.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700351
Ryan Cui3c183c22013-04-29 18:04:11 -0700352 if options.build_dir:
353 chrome_path = os.path.join(options.build_dir, 'chrome')
354 if os.path.isfile(chrome_path):
355 deps = lddtree.ParseELF(chrome_path)
356 if 'libbase.so' in deps['libs']:
357 cros_build_lib.Warning(
358 'Detected a component build of Chrome. component build is '
359 'not working properly for Chrome OS. See crbug.com/196317. '
360 'Use at your own risk!')
Ryan Cuib623e7b2013-03-14 12:54:11 -0700361
Ryan Cuia56a71e2012-10-18 18:40:35 -0700362
Ryan Cui504db722013-01-22 11:48:01 -0800363def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700364 """Get the chrome prebuilt tarball from GS.
365
366 Returns: Path to the fetched chrome tarball.
367 """
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800368 gs_ctx = gs.GSContext.Cached(cache_dir, init_boto=True)
369 files = gs_ctx.LS(gs_path).output.splitlines()
370 files = [found for found in files if
371 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
372 if not files:
373 raise Exception('No chrome package found at %s' % gs_path)
374 elif len(files) > 1:
375 # - Users should provide us with a direct link to either a stripped or
376 # unstripped chrome package.
377 # - In the case of being provided with an archive directory, where both
378 # stripped and unstripped chrome available, use the stripped chrome
379 # package.
380 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
381 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
382 files = [f for f in files if not 'unstripped' in f]
383 assert len(files) == 1
384 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800385
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800386 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800387 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800388 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
389 chrome_path = os.path.join(tempdir, filename)
390 assert os.path.exists(chrome_path)
391 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700392
393
Ryan Cuif890a3e2013-03-07 18:57:06 -0800394@contextlib.contextmanager
395def _StripBinContext(options):
396 if not options.dostrip:
397 yield None
398 elif options.strip_bin:
399 yield options.strip_bin
400 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800401 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800402 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
403 with sdk.Prepare(components=components) as ctx:
404 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
405 constants.CHROME_ENV_FILE)
406 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
407 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
408 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800409 yield strip_bin
410
411
412def _PrepareStagingDir(options, tempdir, staging_dir):
413 """Place the necessary files in the staging directory.
414
415 The staging directory is the directory used to rsync the build artifacts over
416 to the device. Only the necessary Chrome build artifacts are put into the
417 staging directory.
418 """
Ryan Cui5866be02013-03-18 14:12:00 -0700419 osutils.SafeMakedirs(staging_dir)
420 os.chmod(staging_dir, 0755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800421 if options.build_dir:
422 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700423 strip_flags = (None if options.strip_flags is None else
424 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800425 chrome_util.StageChromeFromBuildDir(
426 staging_dir, options.build_dir, strip_bin, strict=options.strict,
David Jamesa6e08892013-03-01 13:34:11 -0800427 sloppy=options.sloppy, gyp_defines=options.gyp_defines,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700428 staging_flags=options.staging_flags,
429 strip_flags=strip_flags)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700430 else:
431 pkg_path = options.local_pkg_path
432 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800433 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
434 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700435
436 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800437 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700438 # Extract only the ./opt/google/chrome contents, directly into the staging
439 # dir, collapsing the directory hierarchy.
440 cros_build_lib.DebugRunCommand(
441 ['tar', '--strip-components', '4', '--extract',
442 '--preserve-permissions', '--file', pkg_path, '.%s' % _CHROME_DIR],
443 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700444
Ryan Cui71aa8de2013-04-19 16:12:55 -0700445
Ryan Cui3045c5d2012-07-13 18:00:33 -0700446def main(argv):
447 options, args = _ParseCommandLine(argv)
448 _PostParseCheck(options, args)
449
450 # Set cros_build_lib debug level to hide RunCommand spew.
451 if options.verbose:
Ryan Cuia56a71e2012-10-18 18:40:35 -0700452 logging.getLogger().setLevel(logging.DEBUG)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700453 else:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800454 logging.getLogger().setLevel(logging.INFO)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700455
Ryan Cui71aa8de2013-04-19 16:12:55 -0700456 with stats.UploadContext() as queue:
457 cmd_stats = stats.Stats(cmd_line=argv, cmd_base='deploy_chrome')
458 queue.put([cmd_stats, stats.StatsUploader.URL, 1])
Ryan Cuia56a71e2012-10-18 18:40:35 -0700459
Ryan Cui71aa8de2013-04-19 16:12:55 -0700460 with osutils.TempDir(set_global=True) as tempdir:
461 staging_dir = options.staging_dir
462 if not staging_dir:
463 staging_dir = os.path.join(tempdir, 'chrome')
464
465 deploy = DeployChrome(options, tempdir, staging_dir)
466 try:
467 deploy.Perform()
468 except results_lib.StepFailure as ex:
469 raise SystemExit(str(ex).strip())