blob: 26ae98e5b8361963a285839c2c4af8e6cc22a705 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Ryan Cui3045c5d2012-07-13 18:00:33 -07002# 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
Steve Funge984a532013-11-25 17:09:25 -08006"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07007
8The script supports deploying Chrome from these sources:
9
101. A local build output directory, such as chromium/src/out/[Debug|Release].
112. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
123. A Chrome tarball existing locally.
13
14The script copies the necessary contents of the source location (tarball or
15build directory) and rsyncs the contents of the staging directory onto your
16device's rootfs.
17"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070018
Mike Frysinger383367e2014-09-16 15:06:17 -040019from __future__ import print_function
20
Mike Frysingerc3061a62015-06-04 04:16:18 -040021import argparse
Ryan Cui7193a7e2013-04-26 14:15:19 -070022import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080023import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070024import functools
Steve Funge984a532013-11-25 17:09:25 -080025import glob
David James88e6f032013-03-02 08:13:20 -080026import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070027import os
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070028import shlex
Steve Funge984a532013-11-25 17:09:25 -080029import shutil
Mike Frysinger6165cdc2020-02-21 02:38:07 -050030import sys
Ryan Cui3045c5d2012-07-13 18:00:33 -070031import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070032
Aviv Keshetb7519e12016-10-04 00:50:00 -070033from chromite.lib import constants
34from chromite.lib import failures_lib
David Pursellcfd58872015-03-19 09:15:48 -070035from chromite.cli.cros import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070036from chromite.lib import chrome_util
Ryan Cuie535b172012-10-19 18:25:03 -070037from chromite.lib import commandline
Ralph Nathan91874ca2015-03-19 13:29:41 -070038from chromite.lib import cros_build_lib
39from chromite.lib import cros_logging as logging
Ryan Cui777ff422012-12-07 13:12:54 -080040from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070041from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080042from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070043from chromite.lib import remote_access as remote
David James3432acd2013-11-27 10:02:18 -080044from chromite.lib import timeout_util
Steven Bennetts368c3e52016-09-23 13:05:21 -070045from gn_helpers import gn_helpers
Ryan Cui3045c5d2012-07-13 18:00:33 -070046
47
Mike Frysinger6165cdc2020-02-21 02:38:07 -050048assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
49
50
Ryan Cui3045c5d2012-07-13 18:00:33 -070051KERNEL_A_PARTITION = 2
52KERNEL_B_PARTITION = 4
53
54KILL_PROC_MAX_WAIT = 10
55POST_KILL_WAIT = 2
56
David Haddock3151d912017-10-24 03:50:32 +000057MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Anushruth8d797672019-10-17 12:22:31 -070058LSOF_COMMAND_CHROME = 'lsof %s/chrome'
59LSOF_COMMAND = 'lsof %s'
David Haddock3151d912017-10-24 03:50:32 +000060DBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon'
Ryan Cui3045c5d2012-07-13 18:00:33 -070061
Steve Funge984a532013-11-25 17:09:25 -080062_ANDROID_DIR = '/system/chrome'
63_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
64
David James2cb34002013-03-01 18:42:40 -080065_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070066_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
Ben Pastenee484b342020-06-30 18:29:27 -070067_CHROME_TEST_BIN_DIR = '/usr/local/libexec/chrome-binary-tests'
David James2cb34002013-03-01 18:42:40 -080068
David Haddock3151d912017-10-24 03:50:32 +000069_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
70 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
71_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
72_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Anushruth8d797672019-10-17 12:22:31 -070073_MKDIR_P_CMD = 'mkdir -p --mode 0775 %s'
Ben Pastenee484b342020-06-30 18:29:27 -070074_FIND_TEST_BIN_CMD = 'find %s -maxdepth 1 -executable -type f' % (
75 _CHROME_TEST_BIN_DIR)
David Haddock3151d912017-10-24 03:50:32 +000076
77DF_COMMAND = 'df -k %s'
78
ChromeOS Developer536f59a2020-08-10 14:39:27 -070079LACROS_DIR = '/usr/local/lacros-chrome'
Erik Chen75a2f492020-08-06 19:15:11 -070080_CONF_FILE = '/etc/chrome_dev.conf'
81_KILL_LACROS_CHROME_CMD = 'pkill -f %(lacros_dir)s/chrome'
82MODIFIED_CONF_FILE = f'modified {_CONF_FILE}'
83
84# This command checks if "--enable-features=LacrosSupport" is present in
85# /etc/chrome_dev.conf. If it is not, then it is added.
86# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
87# to modify chrome_dev.conf. Either revisit this policy or find another
88# mechanism to pass configuration to ash-chrome.
89ENABLE_LACROS_VIA_CONF_COMMAND = f"""
90 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
91 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
92 echo {MODIFIED_CONF_FILE};
93 fi
94"""
95
96# This command checks if "--lacros-chrome-path=" is present with the right value
97# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
98# and the new one is added.
99# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
100# to modify chrome_dev.conf. Either revisit this policy or find another
101# mechanism to pass configuration to ash-chrome.
102_SET_LACROS_PATH_VIA_CONF_COMMAND = """
103 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
104 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
105 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
106 echo %(modified_conf_file)s;
107 fi
108"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500109
Ryan Cui3045c5d2012-07-13 18:00:33 -0700110def _UrlBaseName(url):
111 """Return the last component of the URL."""
112 return url.rstrip('/').rpartition('/')[-1]
113
114
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700115class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -0800116 """Raised whenever the deploy fails."""
117
118
Ryan Cui7193a7e2013-04-26 14:15:19 -0700119DeviceInfo = collections.namedtuple(
120 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
121
122
Ryan Cui3045c5d2012-07-13 18:00:33 -0700123class DeployChrome(object):
124 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500125
Ryan Cuia56a71e2012-10-18 18:40:35 -0700126 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -0700127 """Initialize the class.
128
Mike Frysinger02e1e072013-11-10 22:11:34 -0500129 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -0400130 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -0700131 tempdir: Scratch space for the class. Caller has responsibility to clean
132 it up.
Steve Funge984a532013-11-25 17:09:25 -0800133 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -0700134 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700135 self.tempdir = tempdir
136 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -0700137 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -0500138 if not self.options.staging_only:
Avery Musbach3edff0e2020-03-27 13:35:53 -0700139 self.device = remote.ChromiumOSDevice(options.to, port=options.port,
140 ping=options.ping,
141 private_key=options.private_key,
142 include_dev_paths=False)
Steven Bennettsca73efa2018-07-10 13:36:56 -0700143 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000144
Erik Chen75a2f492020-08-06 19:15:11 -0700145 self._deployment_name = 'lacros' if options.lacros else 'chrome'
146 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
147
148 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
149
150 # Whether UI was stopped during setup.
151 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800152
Ryan Cui7193a7e2013-04-26 14:15:19 -0700153 def _GetRemoteMountFree(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400154 result = self.device.run(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700155 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800156 value = line.split()[3]
157 multipliers = {
158 'G': 1024 * 1024 * 1024,
159 'M': 1024 * 1024,
160 'K': 1024,
161 }
162 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700163
164 def _GetRemoteDirSize(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400165 result = self.device.run('du -ks %s' % remote_dir,
166 capture_output=True, encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700167 return int(result.output.split()[0])
168
169 def _GetStagingDirSize(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400170 result = cros_build_lib.dbg_run(['du', '-ks', self.staging_dir],
Mike Frysinger0282d222019-12-17 17:15:48 -0500171 stdout=True, capture_output=True,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400172 encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700173 return int(result.output.split()[0])
174
Ryan Cui3045c5d2012-07-13 18:00:33 -0700175 def _ChromeFileInUse(self):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400176 result = self.device.run(LSOF_COMMAND_CHROME % (self.options.target_dir,),
177 check=False, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700178 return result.returncode == 0
179
Justin TerAvestfac210e2017-04-13 11:39:00 -0600180 def _Reboot(self):
181 # A reboot in developer mode takes a while (and has delays), so the user
182 # will have time to read and act on the USB boot instructions below.
183 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
184 self.device.Reboot()
185
Ryan Cui3045c5d2012-07-13 18:00:33 -0700186 def _DisableRootfsVerification(self):
187 if not self.options.force:
188 logging.error('Detected that the device has rootfs verification enabled.')
189 logging.info('This script can automatically remove the rootfs '
190 'verification, which requires that it reboot the device.')
191 logging.info('Make sure the device is in developer mode!')
192 logging.info('Skip this prompt by specifying --force.')
Ben Chan37d64e52018-10-05 15:35:02 -0700193 if not cros_build_lib.BooleanPrompt('Remove rootfs verification?', False):
Steven Bennettsca73efa2018-07-10 13:36:56 -0700194 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700195
196 logging.info('Removing rootfs verification from %s', self.options.to)
197 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
198 # Use --force to bypass the checks.
David Haddock3151d912017-10-24 03:50:32 +0000199 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
200 '--remove_rootfs_verification --force')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700201 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400202 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700203
Justin TerAvestfac210e2017-04-13 11:39:00 -0600204 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700205
David James88e6f032013-03-02 08:13:20 -0800206 # Now that the machine has been rebooted, we need to kill Chrome again.
Erik Chen75a2f492020-08-06 19:15:11 -0700207 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800208
209 # Make sure the rootfs is writable now.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500210 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700211
Steven Bennettsca73efa2018-07-10 13:36:56 -0700212 return True
213
Ryan Cui3045c5d2012-07-13 18:00:33 -0700214 def _CheckUiJobStarted(self):
215 # status output is in the format:
216 # <job_name> <status> ['process' <pid>].
217 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800218 try:
Mike Frysinger3459bf52020-03-31 00:52:11 -0400219 result = self.device.run('status ui', capture_output=True,
220 encoding='utf-8')
Ryan Cuif2d1a582013-02-19 14:08:13 -0800221 except cros_build_lib.RunCommandError as e:
222 if 'Unknown job' in e.result.error:
223 return False
224 else:
225 raise e
226
Ryan Cui3045c5d2012-07-13 18:00:33 -0700227 return result.output.split()[1].split('/')[0] == 'start'
228
Erik Chen75a2f492020-08-06 19:15:11 -0700229 def _KillLacrosChrome(self):
230 """This method kills lacros-chrome on the device, if it's running."""
231 self.device.run(_KILL_LACROS_CHROME_CMD %
232 {'lacros_dir': self.options.target_dir}, check=False)
233
234 def _KillAshChromeIfNeeded(self):
235 """This method kills ash-chrome on the device, if it's running.
236
237 This method calls 'stop ui', and then also manually pkills both ash-chrome
238 and the session manager.
239 """
240 self._stopped_ui = True
Ryan Cui3045c5d2012-07-13 18:00:33 -0700241 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800242 logging.info('Shutting down Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400243 self.device.run('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700244
245 # Developers sometimes run session_manager manually, in which case we'll
246 # need to help shut the chrome processes down.
247 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700248 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700249 while self._ChromeFileInUse():
250 logging.warning('The chrome binary on the device is in use.')
251 logging.warning('Killing chrome and session_manager processes...\n')
252
Mike Frysinger3459bf52020-03-31 00:52:11 -0400253 self.device.run("pkill 'chrome|session_manager'", check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700254 # Wait for processes to actually terminate
255 time.sleep(POST_KILL_WAIT)
256 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800257 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800258 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700259 'running chrome processes and try again.'
260 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800261 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700262
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500263 def _MountRootfsAsWritable(self, check=False):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800264 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700265
Steven Bennettsca73efa2018-07-10 13:36:56 -0700266 If the command fails and the root dir is not writable then this function
267 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700268
Mike Frysinger02e1e072013-11-10 22:11:34 -0500269 Args:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500270 check: See remote.RemoteAccess.RemoteSh for details.
David James88e6f032013-03-02 08:13:20 -0800271 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800272 # TODO: Should migrate to use the remount functions in remote_access.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400273 result = self.device.run(MOUNT_RW_COMMAND, check=check,
274 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700275 if result.returncode and not self.device.IsDirWritable('/'):
276 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700277
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800278 def _EnsureTargetDir(self):
279 """Ensures that the target directory exists on the remote device."""
280 target_dir = self.options.target_dir
281 # Any valid /opt directory should already exist so avoid the remote call.
282 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
283 return
Mike Frysinger3459bf52020-03-31 00:52:11 -0400284 self.device.run(['mkdir', '-p', '--mode', '0775', target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800285
Ryan Cui7193a7e2013-04-26 14:15:19 -0700286 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800287 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700288 steps = [
289 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
290 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
291 ]
292 return_values = parallel.RunParallelSteps(steps, return_values=True)
293 return DeviceInfo(*return_values)
294
295 def _CheckDeviceFreeSpace(self, device_info):
296 """See if target device has enough space for Chrome.
297
Mike Frysinger02e1e072013-11-10 22:11:34 -0500298 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700299 device_info: A DeviceInfo named tuple.
300 """
301 effective_free = device_info.target_dir_size + device_info.target_fs_free
302 staging_size = self._GetStagingDirSize()
303 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700304 raise DeployFailure(
305 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400306 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700307 if device_info.target_fs_free < (100 * 1024):
308 logging.warning('The device has less than 100MB free. deploy_chrome may '
309 'hang during the transfer.')
310
Satoru Takabayashif2893002017-06-20 14:52:48 +0900311 def _ShouldUseCompression(self):
312 """Checks if compression should be used for rsync."""
313 if self.options.compress == 'always':
314 return True
315 elif self.options.compress == 'never':
316 return False
317 elif self.options.compress == 'auto':
318 return not self.device.HasGigabitEthernet()
319
Ryan Cui3045c5d2012-07-13 18:00:33 -0700320 def _Deploy(self):
Erik Chen75a2f492020-08-06 19:15:11 -0700321 logging.info('Copying %s to %s on device...', self._deployment_name,
322 self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700323 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
324 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900325 if not self.device.HasRsync():
326 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700327 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500328 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500329 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
330 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700331 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900332 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700333 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500334 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700335
336 # Set the security context on the default Chrome dir if that's where it's
337 # getting deployed, and only on SELinux supported devices.
Erik Chen75a2f492020-08-06 19:15:11 -0700338 if not self.options.lacros and self.device.IsSELinuxAvailable() and (
Ben Pastene5f03b052019-08-12 18:03:24 -0700339 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400340 self.device.run(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800341
342 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800343 if p.mode:
344 # Set mode if necessary.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400345 self.device.run('chmod %o %s/%s' % (
David Haddock3151d912017-10-24 03:50:32 +0000346 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800347
Erik Chen75a2f492020-08-06 19:15:11 -0700348 if self.options.lacros:
349 self.device.run(['chown', '-R', 'chronos:chronos',
350 self.options.target_dir])
351
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700352 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
353 # pick up major changes (bus type, logging, etc.), but all we care about is
354 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
355 # be authorized to take ownership of its service names.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400356 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600357
Erik Chen75a2f492020-08-06 19:15:11 -0700358 if self.options.startui and self._stopped_ui:
Steve Funge984a532013-11-25 17:09:25 -0800359 logging.info('Starting UI...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400360 self.device.run('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700361
Ben Pastenee484b342020-06-30 18:29:27 -0700362 def _DeployTestBinaries(self):
363 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
364
365 There could be several binaries located in the local build dir, so compare
366 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
367 over any that we also built ourselves.
368 """
369 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
370 if r.returncode != 0:
371 raise DeployFailure('Unable to ls contents of %s' % _CHROME_TEST_BIN_DIR)
372 binaries_to_copy = []
373 for f in r.output.splitlines():
374 binaries_to_copy.append(
375 chrome_util.Path(os.path.basename(f), exe=True, optional=True))
376
377 staging_dir = os.path.join(
378 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR))
379 _PrepareStagingDir(self.options, self.tempdir, staging_dir,
380 copy_paths=binaries_to_copy)
381 self.device.CopyToDevice(
382 staging_dir, os.path.dirname(_CHROME_TEST_BIN_DIR), mode='rsync')
383
David James88e6f032013-03-02 08:13:20 -0800384 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700385 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800386 logging.info('Testing connection to the device...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400387 self.device.run('true')
David James88e6f032013-03-02 08:13:20 -0800388 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700389 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800390 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700391
Avery Musbach3edff0e2020-03-27 13:35:53 -0700392 def _CheckBoard(self):
393 """Check that the Chrome build is targeted for the device board."""
394 if self.options.board == self.device.board:
395 return
396 logging.warning('Device board is %s whereas target board is %s.',
397 self.device.board, self.options.board)
398 if self.options.force:
399 return
400 if not cros_build_lib.BooleanPrompt('Continue despite board mismatch?',
401 False):
402 raise DeployFailure('Aborted.')
403
Steve Funge984a532013-11-25 17:09:25 -0800404 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700405 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700406 def BinaryExists(filename):
407 """Checks if the passed-in file is present in the build directory."""
408 return os.path.exists(os.path.join(self.options.build_dir, filename))
409
Erik Chen75a2f492020-08-06 19:15:11 -0700410 # In the future, lacros-chrome and ash-chrome will likely be named
411 # something other than 'chrome' to avoid confusion.
Daniel Erat9813f0e2014-11-12 11:00:28 -0700412 # Handle non-Chrome deployments.
413 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700414 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700415 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
416
David James88e6f032013-03-02 08:13:20 -0800417 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800418 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
419 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800420
Thiago Goncales12793312013-05-23 11:26:17 -0700421 def _MountTarget(self):
422 logging.info('Mounting Chrome...')
423
Anushruth8d797672019-10-17 12:22:31 -0700424 # Create directory if does not exist.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400425 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
Anushruth8d797672019-10-17 12:22:31 -0700426 try:
427 # Umount the existing mount on mount_dir if present first.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400428 self.device.run(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
429 {'dir': self.options.mount_dir})
Anushruth8d797672019-10-17 12:22:31 -0700430 except cros_build_lib.RunCommandError as e:
431 logging.error('Failed to umount %s', self.options.mount_dir)
432 # If there is a failure, check if some processs is using the mount_dir.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400433 result = self.device.run(LSOF_COMMAND % (self.options.mount_dir,),
434 check=False, capture_output=True,
435 encoding='utf-8')
Anushruth8d797672019-10-17 12:22:31 -0700436 logging.error('lsof %s -->', self.options.mount_dir)
437 logging.error(result.stdout)
438 raise e
439
Mike Frysinger3459bf52020-03-31 00:52:11 -0400440 self.device.run(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
441 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700442
Thiago Goncales12793312013-05-23 11:26:17 -0700443 # Chrome needs partition to have exec and suid flags set
Mike Frysinger3459bf52020-03-31 00:52:11 -0400444 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500445
446 def Cleanup(self):
447 """Clean up RemoteDevice."""
448 if not self.options.staging_only:
449 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700450
David James88e6f032013-03-02 08:13:20 -0800451 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800452 self._CheckDeployType()
453
David James88e6f032013-03-02 08:13:20 -0800454 # If requested, just do the staging step.
455 if self.options.staging_only:
456 self._PrepareStagingDir()
457 return 0
458
Erik Chen75a2f492020-08-06 19:15:11 -0700459 # Check that the build matches the device. Lacros-chrome skips this check as
460 # it's currently board independent. This means that it's possible to deploy
461 # a build of lacros-chrome with a mismatched architecture. We don't try to
462 # prevent this developer foot-gun.
463 if not self.options.lacros:
464 self._CheckBoard()
Avery Musbach3edff0e2020-03-27 13:35:53 -0700465
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800466 # Ensure that the target directory exists before running parallel steps.
467 self._EnsureTargetDir()
468
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800469 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800470 steps = [self._GetDeviceInfo, self._CheckConnection,
Erik Chen75a2f492020-08-06 19:15:11 -0700471 self._MountRootfsAsWritable,
Steve Funge984a532013-11-25 17:09:25 -0800472 self._PrepareStagingDir]
Erik Chen75a2f492020-08-06 19:15:11 -0700473
474 # If this is a lacros build, we only want to restart ash-chrome if
475 # necessary, which is done below.
476 steps += ([self._KillLacrosChrome] if self.options.lacros else
477 [self._KillAshChromeIfNeeded])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700478 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
479 return_values=True)
480 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800481
Steven Bennettsca73efa2018-07-10 13:36:56 -0700482 # If the root dir is not writable, try disabling rootfs verification.
483 # (We always do this by default so that developers can write to
484 # /etc/chriome_dev.conf and other directories in the rootfs).
485 if self._root_dir_is_still_readonly.is_set():
486 if self.options.noremove_rootfs_verification:
487 logging.warning('Skipping disable rootfs verification.')
488 elif not self._DisableRootfsVerification():
489 logging.warning('Failed to disable rootfs verification.')
490
491 # If the target dir is still not writable (i.e. the user opted out or the
492 # command failed), abort.
493 if not self.device.IsDirWritable(self.options.target_dir):
Erik Chen75a2f492020-08-06 19:15:11 -0700494 if self.options.startui and self._stopped_ui:
Steven Bennettsca73efa2018-07-10 13:36:56 -0700495 logging.info('Restarting Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400496 self.device.run('start ui')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700497 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800498
Thiago Goncales12793312013-05-23 11:26:17 -0700499 if self.options.mount_dir is not None:
500 self._MountTarget()
501
Erik Chen75a2f492020-08-06 19:15:11 -0700502 if self.options.lacros:
503 # Update /etc/chrome_dev.conf to include appropriate flags.
504 restart_ui = False
505 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
506 if result.stdout.strip() == MODIFIED_CONF_FILE:
507 restart_ui = True
508 result = self.device.run(_SET_LACROS_PATH_VIA_CONF_COMMAND % {
509 'conf_file': _CONF_FILE, 'lacros_path': self.options.target_dir,
510 'modified_conf_file': MODIFIED_CONF_FILE}, shell=True)
511 if result.stdout.strip() == MODIFIED_CONF_FILE:
512 restart_ui = True
513
514 if restart_ui:
515 self._KillAshChromeIfNeeded()
516
David James88e6f032013-03-02 08:13:20 -0800517 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700518 self._Deploy()
Ben Pastenee484b342020-06-30 18:29:27 -0700519 if self.options.deploy_test_binaries:
520 self._DeployTestBinaries()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700521
522
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700523def ValidateStagingFlags(value):
524 """Convert formatted string to dictionary."""
525 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700526
527
Steven Bennetts368c3e52016-09-23 13:05:21 -0700528def ValidateGnArgs(value):
529 """Convert GN_ARGS-formatted string to dictionary."""
530 return gn_helpers.FromGNArgs(value)
531
532
Ryan Cuie535b172012-10-19 18:25:03 -0700533def _CreateParser():
534 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400535 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700536
Ryan Cuia56a71e2012-10-18 18:40:35 -0700537 # TODO(rcui): Have this use the UI-V2 format of having source and target
538 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400539 parser.add_argument('--force', action='store_true', default=False,
Avery Musbach3edff0e2020-03-27 13:35:53 -0700540 help='Skip all prompts (such as the prompt for disabling '
541 'of rootfs verification). This may result in the '
542 'target machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800543 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400544 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400545 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400546 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400547 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400548 parser.add_argument('--build-dir', type='path',
549 help='The directory with Chrome build artifacts to '
550 'deploy from. Typically of format '
551 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700552 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400553 parser.add_argument('--target-dir', type='path',
554 default=None,
555 help='Target directory on device to deploy Chrome into.')
556 parser.add_argument('-g', '--gs-path', type='gs_path',
557 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500558 parser.add_argument('--private-key', type='path', default=None,
559 help='An ssh private key to use when deploying to '
560 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400561 parser.add_argument('--nostartui', action='store_false', dest='startui',
562 default=True,
563 help="Don't restart the ui daemon after deployment.")
564 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
565 default=True,
566 help="Don't strip binaries during deployment. Warning: "
567 'the resulting binaries will be very large!')
568 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
569 help='Port of the target device to connect to.')
570 parser.add_argument('-t', '--to',
571 help='The IP address of the CrOS device to deploy to.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400572 parser.add_argument('--mount-dir', type='path', default=None,
573 help='Deploy Chrome in target directory and bind it '
574 'to the directory specified by this flag.'
575 'Any existing mount on this directory will be '
576 'umounted first.')
577 parser.add_argument('--mount', action='store_true', default=False,
578 help='Deploy Chrome to default target directory and bind '
579 'it to the default mount directory.'
580 'Any existing mount on this directory will be '
581 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700582 parser.add_argument('--noremove-rootfs-verification', action='store_true',
583 default=False, help='Never remove rootfs verification.')
Ben Pastenee484b342020-06-30 18:29:27 -0700584 parser.add_argument('--deploy-test-binaries', action='store_true',
585 default=False,
586 help='Also deploy any test binaries to %s. Useful for '
587 'running any Tast tests that execute these '
588 'binaries.' % _CHROME_TEST_BIN_DIR)
Erik Chen75a2f492020-08-06 19:15:11 -0700589 parser.add_argument('--lacros', action='store_true', default=False,
590 help='Deploys lacros-chrome rather than ash-chrome.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700591
Mike Frysingerc3061a62015-06-04 04:16:18 -0400592 group = parser.add_argument_group('Advanced Options')
593 group.add_argument('-l', '--local-pkg-path', type='path',
594 help='Path to local chrome prebuilt package to deploy.')
595 group.add_argument('--sloppy', action='store_true', default=False,
596 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700597 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400598 help=('Extra flags to control staging. Valid flags are - '
599 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700600 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400601 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700602 help='Deprecated. Default behavior is "strict". Use '
603 '--sloppy to omit warnings for missing optional '
604 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400605 group.add_argument('--strip-flags', default=None,
606 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400607 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400608 group.add_argument('--ping', action='store_true', default=False,
609 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700610 group.add_argument('--process-timeout', type=int,
611 default=KILL_PROC_MAX_WAIT,
612 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700613
Mike Frysingerc3061a62015-06-04 04:16:18 -0400614 group = parser.add_argument_group(
615 'Metadata Overrides (Advanced)',
616 description='Provide all of these overrides in order to remove '
617 'dependencies on metadata.json existence.')
618 group.add_argument('--target-tc', action='store', default=None,
619 help='Override target toolchain name, e.g. '
620 'x86_64-cros-linux-gnu')
621 group.add_argument('--toolchain-url', action='store', default=None,
622 help='Override toolchain url format pattern, e.g. '
623 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700624
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700625 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
626 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
627 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400628 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700629
630 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
631 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
632 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
633 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
634 help=argparse.SUPPRESS)
635
Ryan Cuia56a71e2012-10-18 18:40:35 -0700636 # Path of an empty directory to stage chrome artifacts to. Defaults to a
637 # temporary directory that is removed when the script finishes. If the path
638 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400639 parser.add_argument('--staging-dir', type='path', default=None,
640 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700641 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400642 parser.add_argument('--staging-only', action='store_true', default=False,
643 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700644 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
645 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
646 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400647 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900648 parser.add_argument('--compress', action='store', default='auto',
649 choices=('always', 'never', 'auto'),
650 help='Choose the data compression behavior. Default '
651 'is set to "auto", that disables compression if '
652 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700653 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700654
Ryan Cuie535b172012-10-19 18:25:03 -0700655
656def _ParseCommandLine(argv):
657 """Parse args, and run environment-independent checks."""
658 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400659 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700660
Ryan Cuia56a71e2012-10-18 18:40:35 -0700661 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
662 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800663 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700664 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
665 parser.error('Cannot specify both --build_dir and '
666 '--gs-path/--local-pkg-patch')
Erik Chen75a2f492020-08-06 19:15:11 -0700667 if options.lacros:
668 if options.board:
669 parser.error('--board is not supported with --lacros')
670 # The stripping implemented in this file rely on the cros-chrome-sdk, which
671 # is inappropriate for Lacros. Lacros stripping is currently not
672 # implemented.
673 if options.dostrip:
674 parser.error('--lacros requires --nostrip')
675 if options.mount_dir or options.mount:
676 parser.error('--lacros does not support --mount or --mount-dir')
677 if options.deploy_test_binaries:
678 parser.error('--lacros does not support --deploy-test-binaries')
679 if options.local_pkg_path:
680 parser.error('--lacros does not support --local-pkg-path')
681 else:
682 if not options.board:
683 parser.error('--board is required')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700684 if options.gs_path and options.local_pkg_path:
685 parser.error('Cannot specify both --gs-path and --local-pkg-path')
686 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700687 parser.error('Need to specify --to')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700688 if options.staging_flags and not options.build_dir:
689 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700690
Steven Bennetts46a84c32016-08-12 15:20:46 -0700691 if options.strict:
692 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700693 if options.gyp_defines:
694 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700695
696 if options.mount or options.mount_dir:
697 if not options.target_dir:
698 options.target_dir = _CHROME_DIR_MOUNT
699 else:
700 if not options.target_dir:
Erik Chen75a2f492020-08-06 19:15:11 -0700701 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700702
703 if options.mount and not options.mount_dir:
704 options.mount_dir = _CHROME_DIR
705
Mike Frysingerc3061a62015-06-04 04:16:18 -0400706 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700707
708
Mike Frysingerc3061a62015-06-04 04:16:18 -0400709def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800710 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700711
712 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400713 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700714 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700715 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
716 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
717
Steven Bennetts368c3e52016-09-23 13:05:21 -0700718 if not options.gn_args:
719 gn_env = os.getenv('GN_ARGS')
720 if gn_env is not None:
721 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800722 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700723
Steven Bennetts60600462016-05-12 10:40:20 -0700724 if not options.staging_flags:
725 use_env = os.getenv('USE')
726 if use_env is not None:
727 options.staging_flags = ' '.join(set(use_env.split()).intersection(
728 chrome_util.STAGING_FLAGS))
729 logging.info('Staging flags taken from USE in environment: %s',
730 options.staging_flags)
731
Ryan Cuia56a71e2012-10-18 18:40:35 -0700732
Ryan Cui504db722013-01-22 11:48:01 -0800733def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700734 """Get the chrome prebuilt tarball from GS.
735
Mike Frysinger02e1e072013-11-10 22:11:34 -0500736 Returns:
737 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700738 """
David James9374aac2013-10-08 16:00:17 -0700739 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500740 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800741 files = [found for found in files if
742 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
743 if not files:
744 raise Exception('No chrome package found at %s' % gs_path)
745 elif len(files) > 1:
746 # - Users should provide us with a direct link to either a stripped or
747 # unstripped chrome package.
748 # - In the case of being provided with an archive directory, where both
749 # stripped and unstripped chrome available, use the stripped chrome
750 # package.
751 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
752 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
753 files = [f for f in files if not 'unstripped' in f]
754 assert len(files) == 1
755 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800756
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800757 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800758 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800759 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
760 chrome_path = os.path.join(tempdir, filename)
761 assert os.path.exists(chrome_path)
762 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700763
764
Ryan Cuif890a3e2013-03-07 18:57:06 -0800765@contextlib.contextmanager
766def _StripBinContext(options):
767 if not options.dostrip:
768 yield None
769 elif options.strip_bin:
770 yield options.strip_bin
771 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800772 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800773 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700774 with sdk.Prepare(components=components, target_tc=options.target_tc,
775 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800776 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
777 constants.CHROME_ENV_FILE)
778 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
779 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
780 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800781 yield strip_bin
782
783
Steve Funge984a532013-11-25 17:09:25 -0800784def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
Erik Chen75a2f492020-08-06 19:15:11 -0700785 chrome_dir=None):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800786 """Place the necessary files in the staging directory.
787
788 The staging directory is the directory used to rsync the build artifacts over
789 to the device. Only the necessary Chrome build artifacts are put into the
790 staging directory.
791 """
Erik Chen75a2f492020-08-06 19:15:11 -0700792 if chrome_dir is None:
793 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Ryan Cui5866be02013-03-18 14:12:00 -0700794 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400795 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800796 if options.build_dir:
797 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700798 strip_flags = (None if options.strip_flags is None else
799 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800800 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700801 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700802 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700803 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800804 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700805 else:
806 pkg_path = options.local_pkg_path
807 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800808 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
809 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700810
811 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800812 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700813 # Extract only the ./opt/google/chrome contents, directly into the staging
814 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800815 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400816 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800817 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
818 staging_dir])
819 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
820 shutil.move(filename, staging_dir)
821 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
822 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400823 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800824 ['tar', '--strip-components', '4', '--extract',
825 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
826 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700827
Ryan Cui71aa8de2013-04-19 16:12:55 -0700828
Ryan Cui3045c5d2012-07-13 18:00:33 -0700829def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400830 options = _ParseCommandLine(argv)
831 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700832
Aviv Keshet01a82e92017-03-02 17:39:59 -0800833 with osutils.TempDir(set_global=True) as tempdir:
834 staging_dir = options.staging_dir
835 if not staging_dir:
836 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700837
Aviv Keshet01a82e92017-03-02 17:39:59 -0800838 deploy = DeployChrome(options, tempdir, staging_dir)
839 try:
840 deploy.Perform()
841 except failures_lib.StepFailure as ex:
842 raise SystemExit(str(ex).strip())
843 deploy.Cleanup()