blob: b434108697bed3e110f800c48143a5ef70e2df7f [file] [log] [blame]
Ryan Cui3045c5d2012-07-13 18:00:33 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Steve Funge984a532013-11-25 17:09:25 -08005"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07006
7The script supports deploying Chrome from these sources:
8
91. A local build output directory, such as chromium/src/out/[Debug|Release].
102. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
113. A Chrome tarball existing locally.
12
13The script copies the necessary contents of the source location (tarball or
14build directory) and rsyncs the contents of the staging directory onto your
15device's rootfs.
16"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070017
Mike Frysingerc3061a62015-06-04 04:16:18 -040018import argparse
Ryan Cui7193a7e2013-04-26 14:15:19 -070019import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080020import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070021import functools
Steve Funge984a532013-11-25 17:09:25 -080022import glob
Chris McDonald59650c32021-07-20 15:29:28 -060023import logging
David James88e6f032013-03-02 08:13:20 -080024import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070025import os
Ryo Hashimoto77f8eca2021-04-16 16:43:37 +090026import re
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070027import shlex
Steve Funge984a532013-11-25 17:09:25 -080028import shutil
Ryan Cui3045c5d2012-07-13 18:00:33 -070029import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070030
Chris McDonald59650c32021-07-20 15:29:28 -060031from chromite.third_party.gn_helpers import gn_helpers
32
David Pursellcfd58872015-03-19 09:15:48 -070033from chromite.cli.cros import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070034from chromite.lib import chrome_util
Ryan Cuie535b172012-10-19 18:25:03 -070035from chromite.lib import commandline
Mike Frysinger47b10992021-01-23 00:23:25 -050036from chromite.lib import constants
Ralph Nathan91874ca2015-03-19 13:29:41 -070037from chromite.lib import cros_build_lib
Mike Frysinger47b10992021-01-23 00:23:25 -050038from chromite.lib import failures_lib
Ryan Cui777ff422012-12-07 13:12:54 -080039from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070040from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080041from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070042from chromite.lib import remote_access as remote
Brian Sheedy86f12342020-10-29 15:30:02 -070043from chromite.lib import retry_util
David James3432acd2013-11-27 10:02:18 -080044from chromite.lib import timeout_util
Ryan Cui3045c5d2012-07-13 18:00:33 -070045
46
Ryan Cui3045c5d2012-07-13 18:00:33 -070047KERNEL_A_PARTITION = 2
48KERNEL_B_PARTITION = 4
49
50KILL_PROC_MAX_WAIT = 10
51POST_KILL_WAIT = 2
52
David Haddock3151d912017-10-24 03:50:32 +000053MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Anushruth8d797672019-10-17 12:22:31 -070054LSOF_COMMAND_CHROME = 'lsof %s/chrome'
55LSOF_COMMAND = 'lsof %s'
David Haddock3151d912017-10-24 03:50:32 +000056DBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon'
Ryan Cui3045c5d2012-07-13 18:00:33 -070057
Steve Funge984a532013-11-25 17:09:25 -080058_ANDROID_DIR = '/system/chrome'
59_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
60
David James2cb34002013-03-01 18:42:40 -080061_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070062_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
Jae Hoon Kimdf842912022-05-19 06:40:42 +000063_CHROME_DIR_STAGING_TARBALL_ZSTD = 'chrome.tar.zst'
Ben Pastenee484b342020-06-30 18:29:27 -070064_CHROME_TEST_BIN_DIR = '/usr/local/libexec/chrome-binary-tests'
David James2cb34002013-03-01 18:42:40 -080065
David Haddock3151d912017-10-24 03:50:32 +000066_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
67 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
68_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
69_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Anushruth8d797672019-10-17 12:22:31 -070070_MKDIR_P_CMD = 'mkdir -p --mode 0775 %s'
Ben Pastenee484b342020-06-30 18:29:27 -070071_FIND_TEST_BIN_CMD = 'find %s -maxdepth 1 -executable -type f' % (
72 _CHROME_TEST_BIN_DIR)
David Haddock3151d912017-10-24 03:50:32 +000073
74DF_COMMAND = 'df -k %s'
75
ChromeOS Developer536f59a2020-08-10 14:39:27 -070076LACROS_DIR = '/usr/local/lacros-chrome'
Erik Chen75a2f492020-08-06 19:15:11 -070077_CONF_FILE = '/etc/chrome_dev.conf'
78_KILL_LACROS_CHROME_CMD = 'pkill -f %(lacros_dir)s/chrome'
Sven Zheng92eb66e2022-02-03 21:23:51 +000079_RESET_LACROS_CHROME_CMD = 'rm -rf /home/chronos/user/lacros'
Erik Chen75a2f492020-08-06 19:15:11 -070080MODIFIED_CONF_FILE = f'modified {_CONF_FILE}'
81
82# This command checks if "--enable-features=LacrosSupport" is present in
83# /etc/chrome_dev.conf. If it is not, then it is added.
84# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
85# to modify chrome_dev.conf. Either revisit this policy or find another
86# mechanism to pass configuration to ash-chrome.
87ENABLE_LACROS_VIA_CONF_COMMAND = f"""
88 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
89 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
90 echo {MODIFIED_CONF_FILE};
91 fi
92"""
93
94# This command checks if "--lacros-chrome-path=" is present with the right value
95# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
96# and the new one is added.
97# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
98# to modify chrome_dev.conf. Either revisit this policy or find another
99# mechanism to pass configuration to ash-chrome.
100_SET_LACROS_PATH_VIA_CONF_COMMAND = """
101 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
102 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
103 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
104 echo %(modified_conf_file)s;
105 fi
106"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500107
Ryan Cui3045c5d2012-07-13 18:00:33 -0700108def _UrlBaseName(url):
109 """Return the last component of the URL."""
110 return url.rstrip('/').rpartition('/')[-1]
111
112
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700113class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -0800114 """Raised whenever the deploy fails."""
115
116
Ryan Cui7193a7e2013-04-26 14:15:19 -0700117DeviceInfo = collections.namedtuple(
118 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
119
120
Ryan Cui3045c5d2012-07-13 18:00:33 -0700121class DeployChrome(object):
122 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500123
Ryan Cuia56a71e2012-10-18 18:40:35 -0700124 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -0700125 """Initialize the class.
126
Mike Frysinger02e1e072013-11-10 22:11:34 -0500127 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -0400128 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -0700129 tempdir: Scratch space for the class. Caller has responsibility to clean
130 it up.
Steve Funge984a532013-11-25 17:09:25 -0800131 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -0700132 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700133 self.tempdir = tempdir
134 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -0700135 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -0500136 if not self.options.staging_only:
Ben Pasteneb44d8e72021-02-19 13:56:43 -0800137 hostname = options.device.hostname
138 port = options.device.port
Ben Pastene3bd3e5e2020-08-10 14:40:43 -0700139 self.device = remote.ChromiumOSDevice(hostname, port=port,
Avery Musbach3edff0e2020-03-27 13:35:53 -0700140 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)
Mike Frysinger876a8e52022-06-23 18:07:30 -0400155 line = result.stdout.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')
Mike Frysinger876a8e52022-06-23 18:07:30 -0400167 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700168
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')
Mike Frysinger876a8e52022-06-23 18:07:30 -0400173 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700174
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 '
Roland Bock2bc5b2b2022-07-14 09:21:16 +0000190 'verification, which requires it to reboot the device.')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700191 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
Ben Pasteneb44d8e72021-02-19 13:56:43 -0800196 logging.info('Removing rootfs verification from %s', self.options.device)
Sloan Johnsonc59e9262022-06-10 20:51:53 +0000197 # Running in VMs cause make_dev_ssd's firmware confidence checks to fail.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700198 # 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:
Mike Frysinger876a8e52022-06-23 18:07:30 -0400222 if 'Unknown job' in e.result.stderr:
Ryan Cuif2d1a582013-02-19 14:08:13 -0800223 return False
224 else:
225 raise e
226
Mike Frysinger876a8e52022-06-23 18:07:30 -0400227 return result.stdout.split()[1].split('/')[0] == 'start'
Ryan Cui3045c5d2012-07-13 18:00:33 -0700228
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
Sven Zheng92eb66e2022-02-03 21:23:51 +0000234 def _ResetLacrosChrome(self):
235 """Reset Lacros to fresh state by deleting user data dir."""
236 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
237
Erik Chen75a2f492020-08-06 19:15:11 -0700238 def _KillAshChromeIfNeeded(self):
239 """This method kills ash-chrome on the device, if it's running.
240
241 This method calls 'stop ui', and then also manually pkills both ash-chrome
242 and the session manager.
243 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700244 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800245 logging.info('Shutting down Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400246 self.device.run('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700247
248 # Developers sometimes run session_manager manually, in which case we'll
249 # need to help shut the chrome processes down.
250 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700251 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700252 while self._ChromeFileInUse():
253 logging.warning('The chrome binary on the device is in use.')
254 logging.warning('Killing chrome and session_manager processes...\n')
255
Mike Frysinger3459bf52020-03-31 00:52:11 -0400256 self.device.run("pkill 'chrome|session_manager'", check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700257 # Wait for processes to actually terminate
258 time.sleep(POST_KILL_WAIT)
259 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800260 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800261 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700262 'running chrome processes and try again.'
263 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800264 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700265
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500266 def _MountRootfsAsWritable(self, check=False):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800267 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700268
Steven Bennettsca73efa2018-07-10 13:36:56 -0700269 If the command fails and the root dir is not writable then this function
270 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700271
Mike Frysinger02e1e072013-11-10 22:11:34 -0500272 Args:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500273 check: See remote.RemoteAccess.RemoteSh for details.
David James88e6f032013-03-02 08:13:20 -0800274 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800275 # TODO: Should migrate to use the remount functions in remote_access.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400276 result = self.device.run(MOUNT_RW_COMMAND, check=check,
277 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700278 if result.returncode and not self.device.IsDirWritable('/'):
279 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700280
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800281 def _EnsureTargetDir(self):
282 """Ensures that the target directory exists on the remote device."""
283 target_dir = self.options.target_dir
284 # Any valid /opt directory should already exist so avoid the remote call.
285 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
286 return
Mike Frysinger3459bf52020-03-31 00:52:11 -0400287 self.device.run(['mkdir', '-p', '--mode', '0775', target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800288
Ryan Cui7193a7e2013-04-26 14:15:19 -0700289 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800290 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700291 steps = [
292 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
293 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
294 ]
295 return_values = parallel.RunParallelSteps(steps, return_values=True)
296 return DeviceInfo(*return_values)
297
298 def _CheckDeviceFreeSpace(self, device_info):
299 """See if target device has enough space for Chrome.
300
Mike Frysinger02e1e072013-11-10 22:11:34 -0500301 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700302 device_info: A DeviceInfo named tuple.
303 """
304 effective_free = device_info.target_dir_size + device_info.target_fs_free
305 staging_size = self._GetStagingDirSize()
306 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700307 raise DeployFailure(
308 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400309 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700310 if device_info.target_fs_free < (100 * 1024):
311 logging.warning('The device has less than 100MB free. deploy_chrome may '
312 'hang during the transfer.')
313
Satoru Takabayashif2893002017-06-20 14:52:48 +0900314 def _ShouldUseCompression(self):
315 """Checks if compression should be used for rsync."""
316 if self.options.compress == 'always':
317 return True
318 elif self.options.compress == 'never':
319 return False
320 elif self.options.compress == 'auto':
321 return not self.device.HasGigabitEthernet()
322
Ryan Cui3045c5d2012-07-13 18:00:33 -0700323 def _Deploy(self):
Erik Chen75a2f492020-08-06 19:15:11 -0700324 logging.info('Copying %s to %s on device...', self._deployment_name,
325 self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700326 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
327 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900328 if not self.device.HasRsync():
329 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700330 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500331 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500332 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
333 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700334 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900335 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700336 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500337 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700338
339 # Set the security context on the default Chrome dir if that's where it's
340 # getting deployed, and only on SELinux supported devices.
Erik Chen75a2f492020-08-06 19:15:11 -0700341 if not self.options.lacros and self.device.IsSELinuxAvailable() and (
Ben Pastene5f03b052019-08-12 18:03:24 -0700342 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400343 self.device.run(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800344
345 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800346 if p.mode:
347 # Set mode if necessary.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400348 self.device.run('chmod %o %s/%s' % (
David Haddock3151d912017-10-24 03:50:32 +0000349 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800350
Erik Chen75a2f492020-08-06 19:15:11 -0700351 if self.options.lacros:
352 self.device.run(['chown', '-R', 'chronos:chronos',
353 self.options.target_dir])
354
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700355 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
356 # pick up major changes (bus type, logging, etc.), but all we care about is
357 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
358 # be authorized to take ownership of its service names.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400359 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600360
Erik Chen75a2f492020-08-06 19:15:11 -0700361 if self.options.startui and self._stopped_ui:
Steve Funge984a532013-11-25 17:09:25 -0800362 logging.info('Starting UI...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400363 self.device.run('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700364
Ben Pastenee484b342020-06-30 18:29:27 -0700365 def _DeployTestBinaries(self):
366 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
367
368 There could be several binaries located in the local build dir, so compare
369 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
370 over any that we also built ourselves.
371 """
372 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
373 if r.returncode != 0:
374 raise DeployFailure('Unable to ls contents of %s' % _CHROME_TEST_BIN_DIR)
375 binaries_to_copy = []
Mike Frysinger876a8e52022-06-23 18:07:30 -0400376 for f in r.stdout.splitlines():
Ben Pastenee484b342020-06-30 18:29:27 -0700377 binaries_to_copy.append(
378 chrome_util.Path(os.path.basename(f), exe=True, optional=True))
379
380 staging_dir = os.path.join(
381 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR))
382 _PrepareStagingDir(self.options, self.tempdir, staging_dir,
383 copy_paths=binaries_to_copy)
Brian Sheedy86f12342020-10-29 15:30:02 -0700384 # Deploying can occasionally run into issues with rsync getting a broken
385 # pipe, so retry several times. See crbug.com/1141618 for more
386 # information.
387 retry_util.RetryException(
388 None, 3, self.device.CopyToDevice, staging_dir,
389 os.path.dirname(_CHROME_TEST_BIN_DIR), mode='rsync')
Ben Pastenee484b342020-06-30 18:29:27 -0700390
David James88e6f032013-03-02 08:13:20 -0800391 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700392 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800393 logging.info('Testing connection to the device...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400394 self.device.run('true')
David James88e6f032013-03-02 08:13:20 -0800395 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700396 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800397 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700398
Avery Musbach3edff0e2020-03-27 13:35:53 -0700399 def _CheckBoard(self):
400 """Check that the Chrome build is targeted for the device board."""
401 if self.options.board == self.device.board:
402 return
403 logging.warning('Device board is %s whereas target board is %s.',
404 self.device.board, self.options.board)
405 if self.options.force:
406 return
407 if not cros_build_lib.BooleanPrompt('Continue despite board mismatch?',
408 False):
409 raise DeployFailure('Aborted.')
410
Steve Funge984a532013-11-25 17:09:25 -0800411 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700412 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700413 def BinaryExists(filename):
414 """Checks if the passed-in file is present in the build directory."""
415 return os.path.exists(os.path.join(self.options.build_dir, filename))
416
Erik Chen75a2f492020-08-06 19:15:11 -0700417 # In the future, lacros-chrome and ash-chrome will likely be named
418 # something other than 'chrome' to avoid confusion.
Daniel Erat9813f0e2014-11-12 11:00:28 -0700419 # Handle non-Chrome deployments.
420 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700421 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700422 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
423
David James88e6f032013-03-02 08:13:20 -0800424 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800425 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
426 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800427
Thiago Goncales12793312013-05-23 11:26:17 -0700428 def _MountTarget(self):
429 logging.info('Mounting Chrome...')
430
Anushruth8d797672019-10-17 12:22:31 -0700431 # Create directory if does not exist.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400432 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
Anushruth8d797672019-10-17 12:22:31 -0700433 try:
434 # Umount the existing mount on mount_dir if present first.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400435 self.device.run(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
436 {'dir': self.options.mount_dir})
Anushruth8d797672019-10-17 12:22:31 -0700437 except cros_build_lib.RunCommandError as e:
438 logging.error('Failed to umount %s', self.options.mount_dir)
439 # If there is a failure, check if some processs is using the mount_dir.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400440 result = self.device.run(LSOF_COMMAND % (self.options.mount_dir,),
441 check=False, capture_output=True,
442 encoding='utf-8')
Anushruth8d797672019-10-17 12:22:31 -0700443 logging.error('lsof %s -->', self.options.mount_dir)
444 logging.error(result.stdout)
445 raise e
446
Mike Frysinger3459bf52020-03-31 00:52:11 -0400447 self.device.run(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
448 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700449
Thiago Goncales12793312013-05-23 11:26:17 -0700450 # Chrome needs partition to have exec and suid flags set
Mike Frysinger3459bf52020-03-31 00:52:11 -0400451 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500452
453 def Cleanup(self):
454 """Clean up RemoteDevice."""
455 if not self.options.staging_only:
456 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700457
David James88e6f032013-03-02 08:13:20 -0800458 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800459 self._CheckDeployType()
460
David James88e6f032013-03-02 08:13:20 -0800461 # If requested, just do the staging step.
462 if self.options.staging_only:
463 self._PrepareStagingDir()
464 return 0
465
Erik Chen75a2f492020-08-06 19:15:11 -0700466 # Check that the build matches the device. Lacros-chrome skips this check as
467 # it's currently board independent. This means that it's possible to deploy
468 # a build of lacros-chrome with a mismatched architecture. We don't try to
469 # prevent this developer foot-gun.
470 if not self.options.lacros:
471 self._CheckBoard()
Avery Musbach3edff0e2020-03-27 13:35:53 -0700472
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800473 # Ensure that the target directory exists before running parallel steps.
474 self._EnsureTargetDir()
475
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800476 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800477 steps = [self._GetDeviceInfo, self._CheckConnection,
Erik Chen75a2f492020-08-06 19:15:11 -0700478 self._MountRootfsAsWritable,
Steve Funge984a532013-11-25 17:09:25 -0800479 self._PrepareStagingDir]
Erik Chen75a2f492020-08-06 19:15:11 -0700480
Yuke Liaobe6bac32020-12-26 22:16:49 -0800481 restart_ui = True
482 if self.options.lacros:
Yuke Liao24fc60c2020-12-26 22:16:49 -0800483 # If this is a lacros build, we only want to restart ash-chrome if needed.
484 restart_ui = False
Yuke Liaobe6bac32020-12-26 22:16:49 -0800485 steps.append(self._KillLacrosChrome)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000486 if self.options.reset_lacros:
487 steps.append(self._ResetLacrosChrome)
Yuke Liao24fc60c2020-12-26 22:16:49 -0800488 if self.options.modify_config_file:
489 restart_ui = self._ModifyConfigFileIfNeededForLacros()
490
Yuke Liaobe6bac32020-12-26 22:16:49 -0800491 if restart_ui:
492 steps.append(self._KillAshChromeIfNeeded)
ChromeOS Developer78345852020-08-11 23:09:48 -0700493 self._stopped_ui = True
Yuke Liaobe6bac32020-12-26 22:16:49 -0800494
Ryan Cui7193a7e2013-04-26 14:15:19 -0700495 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
496 return_values=True)
497 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800498
Steven Bennettsca73efa2018-07-10 13:36:56 -0700499 # If the root dir is not writable, try disabling rootfs verification.
500 # (We always do this by default so that developers can write to
501 # /etc/chriome_dev.conf and other directories in the rootfs).
502 if self._root_dir_is_still_readonly.is_set():
503 if self.options.noremove_rootfs_verification:
504 logging.warning('Skipping disable rootfs verification.')
505 elif not self._DisableRootfsVerification():
506 logging.warning('Failed to disable rootfs verification.')
507
508 # If the target dir is still not writable (i.e. the user opted out or the
509 # command failed), abort.
510 if not self.device.IsDirWritable(self.options.target_dir):
Erik Chen75a2f492020-08-06 19:15:11 -0700511 if self.options.startui and self._stopped_ui:
Steven Bennettsca73efa2018-07-10 13:36:56 -0700512 logging.info('Restarting Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400513 self.device.run('start ui')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700514 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800515
Thiago Goncales12793312013-05-23 11:26:17 -0700516 if self.options.mount_dir is not None:
517 self._MountTarget()
518
David James88e6f032013-03-02 08:13:20 -0800519 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700520 self._Deploy()
Ben Pastenee484b342020-06-30 18:29:27 -0700521 if self.options.deploy_test_binaries:
522 self._DeployTestBinaries()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700523
524
Yuke Liaobe6bac32020-12-26 22:16:49 -0800525 def _ModifyConfigFileIfNeededForLacros(self):
526 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
527
528 Returns:
529 True if the file is modified, and the return value is usually used to
530 determine whether restarting ash-chrome is needed.
531 """
532 assert self.options.lacros, (
533 'Only deploying lacros-chrome needs to modify the config file.')
534 # Update /etc/chrome_dev.conf to include appropriate flags.
535 modified = False
536 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
537 if result.stdout.strip() == MODIFIED_CONF_FILE:
538 modified = True
539 result = self.device.run(
540 _SET_LACROS_PATH_VIA_CONF_COMMAND % {
541 'conf_file': _CONF_FILE,
542 'lacros_path': self.options.target_dir,
543 'modified_conf_file': MODIFIED_CONF_FILE
544 },
545 shell=True)
546 if result.stdout.strip() == MODIFIED_CONF_FILE:
547 modified = True
548
549 return modified
550
551
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700552def ValidateStagingFlags(value):
553 """Convert formatted string to dictionary."""
554 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700555
556
Steven Bennetts368c3e52016-09-23 13:05:21 -0700557def ValidateGnArgs(value):
558 """Convert GN_ARGS-formatted string to dictionary."""
559 return gn_helpers.FromGNArgs(value)
560
561
Ryan Cuie535b172012-10-19 18:25:03 -0700562def _CreateParser():
563 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400564 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700565
Ryan Cuia56a71e2012-10-18 18:40:35 -0700566 # TODO(rcui): Have this use the UI-V2 format of having source and target
567 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400568 parser.add_argument('--force', action='store_true', default=False,
Avery Musbach3edff0e2020-03-27 13:35:53 -0700569 help='Skip all prompts (such as the prompt for disabling '
570 'of rootfs verification). This may result in the '
571 'target machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800572 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400573 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400574 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400575 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400576 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400577 parser.add_argument('--build-dir', type='path',
578 help='The directory with Chrome build artifacts to '
579 'deploy from. Typically of format '
580 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700581 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400582 parser.add_argument('--target-dir', type='path',
583 default=None,
584 help='Target directory on device to deploy Chrome into.')
585 parser.add_argument('-g', '--gs-path', type='gs_path',
586 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500587 parser.add_argument('--private-key', type='path', default=None,
588 help='An ssh private key to use when deploying to '
589 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400590 parser.add_argument('--nostartui', action='store_false', dest='startui',
591 default=True,
592 help="Don't restart the ui daemon after deployment.")
593 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
594 default=True,
595 help="Don't strip binaries during deployment. Warning: "
596 'the resulting binaries will be very large!')
Ben Pastene3bd3e5e2020-08-10 14:40:43 -0700597 parser.add_argument(
598 '-d', '--device',
599 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
600 help='Device hostname or IP in the format hostname[:port].')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400601 parser.add_argument('--mount-dir', type='path', default=None,
602 help='Deploy Chrome in target directory and bind it '
603 'to the directory specified by this flag.'
604 'Any existing mount on this directory will be '
605 'umounted first.')
606 parser.add_argument('--mount', action='store_true', default=False,
607 help='Deploy Chrome to default target directory and bind '
608 'it to the default mount directory.'
609 'Any existing mount on this directory will be '
610 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700611 parser.add_argument('--noremove-rootfs-verification', action='store_true',
612 default=False, help='Never remove rootfs verification.')
Ben Pastenee484b342020-06-30 18:29:27 -0700613 parser.add_argument('--deploy-test-binaries', action='store_true',
614 default=False,
615 help='Also deploy any test binaries to %s. Useful for '
616 'running any Tast tests that execute these '
617 'binaries.' % _CHROME_TEST_BIN_DIR)
Erik Chen75a2f492020-08-06 19:15:11 -0700618 parser.add_argument('--lacros', action='store_true', default=False,
619 help='Deploys lacros-chrome rather than ash-chrome.')
Sven Zheng92eb66e2022-02-03 21:23:51 +0000620 parser.add_argument('--reset-lacros', action='store_true', default=False,
621 help='Reset Lacros by deleting Lacros user data dir if '
622 'exists.')
Yuke Liao24fc60c2020-12-26 22:16:49 -0800623 parser.add_argument('--skip-modifying-config-file', action='store_false',
624 dest='modify_config_file',
625 help='By default, deploying lacros-chrome modifies the '
626 '/etc/chrome_dev.conf file, which interferes with '
627 'automated testing, and this argument disables it.')
Brian Sheedyd23c21d2022-04-26 16:35:11 -0700628 parser.add_argument('--use-external-config', action='store_true',
629 help='When identifying the configuration for a board, '
630 'force usage of the external configuration if both '
631 'internal and external are available. This only '
632 'has an effect when stripping Chrome, i.e. when '
633 '--nostrip is not passed in.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700634
Mike Frysingerc3061a62015-06-04 04:16:18 -0400635 group = parser.add_argument_group('Advanced Options')
636 group.add_argument('-l', '--local-pkg-path', type='path',
637 help='Path to local chrome prebuilt package to deploy.')
638 group.add_argument('--sloppy', action='store_true', default=False,
639 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700640 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400641 help=('Extra flags to control staging. Valid flags are - '
642 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700643 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400644 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700645 help='Deprecated. Default behavior is "strict". Use '
646 '--sloppy to omit warnings for missing optional '
647 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400648 group.add_argument('--strip-flags', default=None,
649 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400650 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400651 group.add_argument('--ping', action='store_true', default=False,
652 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700653 group.add_argument('--process-timeout', type=int,
654 default=KILL_PROC_MAX_WAIT,
655 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700656
Mike Frysingerc3061a62015-06-04 04:16:18 -0400657 group = parser.add_argument_group(
658 'Metadata Overrides (Advanced)',
659 description='Provide all of these overrides in order to remove '
660 'dependencies on metadata.json existence.')
661 group.add_argument('--target-tc', action='store', default=None,
662 help='Override target toolchain name, e.g. '
663 'x86_64-cros-linux-gnu')
664 group.add_argument('--toolchain-url', action='store', default=None,
665 help='Override toolchain url format pattern, e.g. '
666 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700667
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700668 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
669 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
670 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400671 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700672
673 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
674 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
675 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
676 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
677 help=argparse.SUPPRESS)
678
Ryan Cuia56a71e2012-10-18 18:40:35 -0700679 # Path of an empty directory to stage chrome artifacts to. Defaults to a
680 # temporary directory that is removed when the script finishes. If the path
681 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400682 parser.add_argument('--staging-dir', type='path', default=None,
683 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700684 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400685 parser.add_argument('--staging-only', action='store_true', default=False,
686 help=argparse.SUPPRESS)
Jae Hoon Kimdf842912022-05-19 06:40:42 +0000687 # Uploads the compressed staging directory to the given gs:// path URI.
688 parser.add_argument('--staging-upload', type='gs_path',
689 help='GS path to upload the compressed staging files to.')
690 # Used alongside --staging-upload to upload with public-read ACL.
691 parser.add_argument('--public-read', action='store_true', default=False,
692 help='GS path to upload the compressed staging files to.')
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700693 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
694 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
695 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400696 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900697 parser.add_argument('--compress', action='store', default='auto',
698 choices=('always', 'never', 'auto'),
699 help='Choose the data compression behavior. Default '
700 'is set to "auto", that disables compression if '
701 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700702 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700703
Ryan Cuie535b172012-10-19 18:25:03 -0700704
705def _ParseCommandLine(argv):
706 """Parse args, and run environment-independent checks."""
707 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400708 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700709
Ryan Cuia56a71e2012-10-18 18:40:35 -0700710 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
711 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800712 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700713 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
714 parser.error('Cannot specify both --build_dir and '
715 '--gs-path/--local-pkg-patch')
Erik Chen75a2f492020-08-06 19:15:11 -0700716 if options.lacros:
717 if options.board:
718 parser.error('--board is not supported with --lacros')
719 # The stripping implemented in this file rely on the cros-chrome-sdk, which
720 # is inappropriate for Lacros. Lacros stripping is currently not
721 # implemented.
722 if options.dostrip:
723 parser.error('--lacros requires --nostrip')
724 if options.mount_dir or options.mount:
725 parser.error('--lacros does not support --mount or --mount-dir')
726 if options.deploy_test_binaries:
727 parser.error('--lacros does not support --deploy-test-binaries')
728 if options.local_pkg_path:
729 parser.error('--lacros does not support --local-pkg-path')
730 else:
Ryo Hashimoto77f8eca2021-04-16 16:43:37 +0900731 if not options.board and options.build_dir:
732 match = re.search(r'out_([^/]+)/Release$', options.build_dir)
733 if match:
734 options.board = match.group(1)
735 logging.info('--board is set to %s', options.board)
Erik Chen75a2f492020-08-06 19:15:11 -0700736 if not options.board:
737 parser.error('--board is required')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700738 if options.gs_path and options.local_pkg_path:
739 parser.error('Cannot specify both --gs-path and --local-pkg-path')
Ben Pasteneb44d8e72021-02-19 13:56:43 -0800740 if not (options.staging_only or options.device):
Ben Pastene3bd3e5e2020-08-10 14:40:43 -0700741 parser.error('Need to specify --device')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700742 if options.staging_flags and not options.build_dir:
743 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700744
Steven Bennetts46a84c32016-08-12 15:20:46 -0700745 if options.strict:
746 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700747 if options.gyp_defines:
748 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700749
750 if options.mount or options.mount_dir:
751 if not options.target_dir:
752 options.target_dir = _CHROME_DIR_MOUNT
753 else:
754 if not options.target_dir:
Erik Chen75a2f492020-08-06 19:15:11 -0700755 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700756
757 if options.mount and not options.mount_dir:
758 options.mount_dir = _CHROME_DIR
759
Mike Frysingerc3061a62015-06-04 04:16:18 -0400760 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700761
762
Mike Frysingerc3061a62015-06-04 04:16:18 -0400763def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800764 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700765
766 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400767 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700768 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700769 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
770 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
771
Steven Bennetts368c3e52016-09-23 13:05:21 -0700772 if not options.gn_args:
773 gn_env = os.getenv('GN_ARGS')
774 if gn_env is not None:
775 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800776 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700777
Steven Bennetts60600462016-05-12 10:40:20 -0700778 if not options.staging_flags:
779 use_env = os.getenv('USE')
780 if use_env is not None:
781 options.staging_flags = ' '.join(set(use_env.split()).intersection(
782 chrome_util.STAGING_FLAGS))
783 logging.info('Staging flags taken from USE in environment: %s',
784 options.staging_flags)
785
Ryan Cuia56a71e2012-10-18 18:40:35 -0700786
Ryan Cui504db722013-01-22 11:48:01 -0800787def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700788 """Get the chrome prebuilt tarball from GS.
789
Mike Frysinger02e1e072013-11-10 22:11:34 -0500790 Returns:
791 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700792 """
David James9374aac2013-10-08 16:00:17 -0700793 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500794 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800795 files = [found for found in files if
796 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
797 if not files:
798 raise Exception('No chrome package found at %s' % gs_path)
799 elif len(files) > 1:
800 # - Users should provide us with a direct link to either a stripped or
801 # unstripped chrome package.
802 # - In the case of being provided with an archive directory, where both
803 # stripped and unstripped chrome available, use the stripped chrome
804 # package.
805 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
806 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
807 files = [f for f in files if not 'unstripped' in f]
808 assert len(files) == 1
809 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800810
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800811 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800812 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800813 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
814 chrome_path = os.path.join(tempdir, filename)
815 assert os.path.exists(chrome_path)
816 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700817
818
Ryan Cuif890a3e2013-03-07 18:57:06 -0800819@contextlib.contextmanager
820def _StripBinContext(options):
821 if not options.dostrip:
822 yield None
823 elif options.strip_bin:
824 yield options.strip_bin
825 else:
Brian Sheedyd23c21d2022-04-26 16:35:11 -0700826 sdk = cros_chrome_sdk.SDKFetcher(
827 options.cache_dir, options.board,
828 use_external_config=options.use_external_config)
Ryan Cui686ec052013-02-12 16:39:41 -0800829 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700830 with sdk.Prepare(components=components, target_tc=options.target_tc,
831 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800832 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
833 constants.CHROME_ENV_FILE)
834 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
835 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
836 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800837 yield strip_bin
838
839
Jae Hoon Kimdf842912022-05-19 06:40:42 +0000840def _UploadStagingDir(options: commandline.ArgumentNamespace, tempdir: str,
841 staging_dir: str) -> None:
842 """Uploads the compressed staging directory.
843
844 Args:
845 options: options object.
846 tempdir: Scratch space.
847 staging_dir: Directory staging chrome files.
848 """
849 staging_tarball_path = os.path.join(tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD)
850 logging.info('Compressing staging dir (%s) to (%s)',
851 staging_dir, staging_tarball_path)
852 cros_build_lib.CreateTarball(
853 staging_tarball_path,
854 staging_dir,
855 compression=cros_build_lib.COMP_ZSTD,
856 extra_env={'ZSTD_CLEVEL': '9'})
857 logging.info('Uploading staging tarball (%s) into %s',
858 staging_tarball_path, options.staging_upload)
859 ctx = gs.GSContext()
860 ctx.Copy(staging_tarball_path, options.staging_upload,
861 acl='public-read' if options.public_read else '')
862
863
Steve Funge984a532013-11-25 17:09:25 -0800864def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
Erik Chen75a2f492020-08-06 19:15:11 -0700865 chrome_dir=None):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800866 """Place the necessary files in the staging directory.
867
868 The staging directory is the directory used to rsync the build artifacts over
869 to the device. Only the necessary Chrome build artifacts are put into the
870 staging directory.
871 """
Erik Chen75a2f492020-08-06 19:15:11 -0700872 if chrome_dir is None:
873 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Ryan Cui5866be02013-03-18 14:12:00 -0700874 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400875 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800876 if options.build_dir:
877 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700878 strip_flags = (None if options.strip_flags is None else
879 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800880 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700881 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700882 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700883 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800884 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700885 else:
886 pkg_path = options.local_pkg_path
887 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800888 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
889 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700890
891 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800892 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700893 # Extract only the ./opt/google/chrome contents, directly into the staging
894 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800895 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400896 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800897 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
898 staging_dir])
899 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
900 shutil.move(filename, staging_dir)
901 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
902 else:
Mike Frysingere2e5e3b2022-07-03 01:51:25 -0400903 compression = cros_build_lib.CompressionDetectType(pkg_path)
904 compressor = cros_build_lib.FindCompressor(compression)
Mike Frysinger45602c72019-09-22 02:15:11 -0400905 cros_build_lib.dbg_run(
Mike Frysingere2e5e3b2022-07-03 01:51:25 -0400906 ['tar', '--strip-components', '4', '--extract', '-I', compressor,
Steve Funge984a532013-11-25 17:09:25 -0800907 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
908 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700909
Jae Hoon Kimdf842912022-05-19 06:40:42 +0000910 if options.staging_upload:
911 _UploadStagingDir(options, tempdir, staging_dir)
912
Ryan Cui71aa8de2013-04-19 16:12:55 -0700913
Ryan Cui3045c5d2012-07-13 18:00:33 -0700914def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400915 options = _ParseCommandLine(argv)
916 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700917
Aviv Keshet01a82e92017-03-02 17:39:59 -0800918 with osutils.TempDir(set_global=True) as tempdir:
919 staging_dir = options.staging_dir
920 if not staging_dir:
921 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700922
Aviv Keshet01a82e92017-03-02 17:39:59 -0800923 deploy = DeployChrome(options, tempdir, staging_dir)
924 try:
925 deploy.Perform()
926 except failures_lib.StepFailure as ex:
927 raise SystemExit(str(ex).strip())
928 deploy.Cleanup()