blob: 7dffa2cfd7938efb5154061042323c8d70ca485c [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'
Ben Pastenee484b342020-06-30 18:29:27 -070063_CHROME_TEST_BIN_DIR = '/usr/local/libexec/chrome-binary-tests'
David James2cb34002013-03-01 18:42:40 -080064
David Haddock3151d912017-10-24 03:50:32 +000065_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
66 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
67_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
68_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Anushruth8d797672019-10-17 12:22:31 -070069_MKDIR_P_CMD = 'mkdir -p --mode 0775 %s'
Ben Pastenee484b342020-06-30 18:29:27 -070070_FIND_TEST_BIN_CMD = 'find %s -maxdepth 1 -executable -type f' % (
71 _CHROME_TEST_BIN_DIR)
David Haddock3151d912017-10-24 03:50:32 +000072
73DF_COMMAND = 'df -k %s'
74
ChromeOS Developer536f59a2020-08-10 14:39:27 -070075LACROS_DIR = '/usr/local/lacros-chrome'
Erik Chen75a2f492020-08-06 19:15:11 -070076_CONF_FILE = '/etc/chrome_dev.conf'
77_KILL_LACROS_CHROME_CMD = 'pkill -f %(lacros_dir)s/chrome'
Sven Zheng92eb66e2022-02-03 21:23:51 +000078_RESET_LACROS_CHROME_CMD = 'rm -rf /home/chronos/user/lacros'
Erik Chen75a2f492020-08-06 19:15:11 -070079MODIFIED_CONF_FILE = f'modified {_CONF_FILE}'
80
81# This command checks if "--enable-features=LacrosSupport" is present in
82# /etc/chrome_dev.conf. If it is not, then it is added.
83# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
84# to modify chrome_dev.conf. Either revisit this policy or find another
85# mechanism to pass configuration to ash-chrome.
86ENABLE_LACROS_VIA_CONF_COMMAND = f"""
87 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
88 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
89 echo {MODIFIED_CONF_FILE};
90 fi
91"""
92
93# This command checks if "--lacros-chrome-path=" is present with the right value
94# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
95# and the new one is added.
96# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
97# to modify chrome_dev.conf. Either revisit this policy or find another
98# mechanism to pass configuration to ash-chrome.
99_SET_LACROS_PATH_VIA_CONF_COMMAND = """
100 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
101 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
102 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
103 echo %(modified_conf_file)s;
104 fi
105"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500106
Ryan Cui3045c5d2012-07-13 18:00:33 -0700107def _UrlBaseName(url):
108 """Return the last component of the URL."""
109 return url.rstrip('/').rpartition('/')[-1]
110
111
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700112class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -0800113 """Raised whenever the deploy fails."""
114
115
Ryan Cui7193a7e2013-04-26 14:15:19 -0700116DeviceInfo = collections.namedtuple(
117 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
118
119
Ryan Cui3045c5d2012-07-13 18:00:33 -0700120class DeployChrome(object):
121 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500122
Ryan Cuia56a71e2012-10-18 18:40:35 -0700123 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -0700124 """Initialize the class.
125
Mike Frysinger02e1e072013-11-10 22:11:34 -0500126 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -0400127 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -0700128 tempdir: Scratch space for the class. Caller has responsibility to clean
129 it up.
Steve Funge984a532013-11-25 17:09:25 -0800130 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -0700131 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700132 self.tempdir = tempdir
133 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -0700134 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -0500135 if not self.options.staging_only:
Ben Pasteneb44d8e72021-02-19 13:56:43 -0800136 hostname = options.device.hostname
137 port = options.device.port
Ben Pastene3bd3e5e2020-08-10 14:40:43 -0700138 self.device = remote.ChromiumOSDevice(hostname, port=port,
Avery Musbach3edff0e2020-03-27 13:35:53 -0700139 ping=options.ping,
140 private_key=options.private_key,
141 include_dev_paths=False)
Steven Bennettsca73efa2018-07-10 13:36:56 -0700142 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000143
Erik Chen75a2f492020-08-06 19:15:11 -0700144 self._deployment_name = 'lacros' if options.lacros else 'chrome'
145 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
146
147 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
148
149 # Whether UI was stopped during setup.
150 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800151
Ryan Cui7193a7e2013-04-26 14:15:19 -0700152 def _GetRemoteMountFree(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400153 result = self.device.run(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700154 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800155 value = line.split()[3]
156 multipliers = {
157 'G': 1024 * 1024 * 1024,
158 'M': 1024 * 1024,
159 'K': 1024,
160 }
161 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700162
163 def _GetRemoteDirSize(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400164 result = self.device.run('du -ks %s' % remote_dir,
165 capture_output=True, encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700166 return int(result.output.split()[0])
167
168 def _GetStagingDirSize(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400169 result = cros_build_lib.dbg_run(['du', '-ks', self.staging_dir],
Mike Frysinger0282d222019-12-17 17:15:48 -0500170 stdout=True, capture_output=True,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400171 encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700172 return int(result.output.split()[0])
173
Ryan Cui3045c5d2012-07-13 18:00:33 -0700174 def _ChromeFileInUse(self):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400175 result = self.device.run(LSOF_COMMAND_CHROME % (self.options.target_dir,),
176 check=False, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700177 return result.returncode == 0
178
Justin TerAvestfac210e2017-04-13 11:39:00 -0600179 def _Reboot(self):
180 # A reboot in developer mode takes a while (and has delays), so the user
181 # will have time to read and act on the USB boot instructions below.
182 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
183 self.device.Reboot()
184
Ryan Cui3045c5d2012-07-13 18:00:33 -0700185 def _DisableRootfsVerification(self):
186 if not self.options.force:
187 logging.error('Detected that the device has rootfs verification enabled.')
188 logging.info('This script can automatically remove the rootfs '
189 'verification, which requires that it reboot the device.')
190 logging.info('Make sure the device is in developer mode!')
191 logging.info('Skip this prompt by specifying --force.')
Ben Chan37d64e52018-10-05 15:35:02 -0700192 if not cros_build_lib.BooleanPrompt('Remove rootfs verification?', False):
Steven Bennettsca73efa2018-07-10 13:36:56 -0700193 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700194
Ben Pasteneb44d8e72021-02-19 13:56:43 -0800195 logging.info('Removing rootfs verification from %s', self.options.device)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700196 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
197 # Use --force to bypass the checks.
David Haddock3151d912017-10-24 03:50:32 +0000198 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
199 '--remove_rootfs_verification --force')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700200 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400201 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700202
Justin TerAvestfac210e2017-04-13 11:39:00 -0600203 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700204
David James88e6f032013-03-02 08:13:20 -0800205 # Now that the machine has been rebooted, we need to kill Chrome again.
Erik Chen75a2f492020-08-06 19:15:11 -0700206 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800207
208 # Make sure the rootfs is writable now.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500209 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700210
Steven Bennettsca73efa2018-07-10 13:36:56 -0700211 return True
212
Ryan Cui3045c5d2012-07-13 18:00:33 -0700213 def _CheckUiJobStarted(self):
214 # status output is in the format:
215 # <job_name> <status> ['process' <pid>].
216 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800217 try:
Mike Frysinger3459bf52020-03-31 00:52:11 -0400218 result = self.device.run('status ui', capture_output=True,
219 encoding='utf-8')
Ryan Cuif2d1a582013-02-19 14:08:13 -0800220 except cros_build_lib.RunCommandError as e:
221 if 'Unknown job' in e.result.error:
222 return False
223 else:
224 raise e
225
Ryan Cui3045c5d2012-07-13 18:00:33 -0700226 return result.output.split()[1].split('/')[0] == 'start'
227
Erik Chen75a2f492020-08-06 19:15:11 -0700228 def _KillLacrosChrome(self):
229 """This method kills lacros-chrome on the device, if it's running."""
230 self.device.run(_KILL_LACROS_CHROME_CMD %
231 {'lacros_dir': self.options.target_dir}, check=False)
232
Sven Zheng92eb66e2022-02-03 21:23:51 +0000233 def _ResetLacrosChrome(self):
234 """Reset Lacros to fresh state by deleting user data dir."""
235 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
236
Erik Chen75a2f492020-08-06 19:15:11 -0700237 def _KillAshChromeIfNeeded(self):
238 """This method kills ash-chrome on the device, if it's running.
239
240 This method calls 'stop ui', and then also manually pkills both ash-chrome
241 and the session manager.
242 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700243 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800244 logging.info('Shutting down Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400245 self.device.run('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700246
247 # Developers sometimes run session_manager manually, in which case we'll
248 # need to help shut the chrome processes down.
249 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700250 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700251 while self._ChromeFileInUse():
252 logging.warning('The chrome binary on the device is in use.')
253 logging.warning('Killing chrome and session_manager processes...\n')
254
Mike Frysinger3459bf52020-03-31 00:52:11 -0400255 self.device.run("pkill 'chrome|session_manager'", check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700256 # Wait for processes to actually terminate
257 time.sleep(POST_KILL_WAIT)
258 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800259 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800260 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700261 'running chrome processes and try again.'
262 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800263 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700264
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500265 def _MountRootfsAsWritable(self, check=False):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800266 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700267
Steven Bennettsca73efa2018-07-10 13:36:56 -0700268 If the command fails and the root dir is not writable then this function
269 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700270
Mike Frysinger02e1e072013-11-10 22:11:34 -0500271 Args:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500272 check: See remote.RemoteAccess.RemoteSh for details.
David James88e6f032013-03-02 08:13:20 -0800273 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800274 # TODO: Should migrate to use the remount functions in remote_access.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400275 result = self.device.run(MOUNT_RW_COMMAND, check=check,
276 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700277 if result.returncode and not self.device.IsDirWritable('/'):
278 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700279
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800280 def _EnsureTargetDir(self):
281 """Ensures that the target directory exists on the remote device."""
282 target_dir = self.options.target_dir
283 # Any valid /opt directory should already exist so avoid the remote call.
284 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
285 return
Mike Frysinger3459bf52020-03-31 00:52:11 -0400286 self.device.run(['mkdir', '-p', '--mode', '0775', target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800287
Ryan Cui7193a7e2013-04-26 14:15:19 -0700288 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800289 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700290 steps = [
291 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
292 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
293 ]
294 return_values = parallel.RunParallelSteps(steps, return_values=True)
295 return DeviceInfo(*return_values)
296
297 def _CheckDeviceFreeSpace(self, device_info):
298 """See if target device has enough space for Chrome.
299
Mike Frysinger02e1e072013-11-10 22:11:34 -0500300 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700301 device_info: A DeviceInfo named tuple.
302 """
303 effective_free = device_info.target_dir_size + device_info.target_fs_free
304 staging_size = self._GetStagingDirSize()
305 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700306 raise DeployFailure(
307 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400308 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700309 if device_info.target_fs_free < (100 * 1024):
310 logging.warning('The device has less than 100MB free. deploy_chrome may '
311 'hang during the transfer.')
312
Satoru Takabayashif2893002017-06-20 14:52:48 +0900313 def _ShouldUseCompression(self):
314 """Checks if compression should be used for rsync."""
315 if self.options.compress == 'always':
316 return True
317 elif self.options.compress == 'never':
318 return False
319 elif self.options.compress == 'auto':
320 return not self.device.HasGigabitEthernet()
321
Ryan Cui3045c5d2012-07-13 18:00:33 -0700322 def _Deploy(self):
Erik Chen75a2f492020-08-06 19:15:11 -0700323 logging.info('Copying %s to %s on device...', self._deployment_name,
324 self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700325 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
326 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900327 if not self.device.HasRsync():
328 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700329 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500330 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500331 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
332 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700333 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900334 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700335 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500336 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700337
338 # Set the security context on the default Chrome dir if that's where it's
339 # getting deployed, and only on SELinux supported devices.
Erik Chen75a2f492020-08-06 19:15:11 -0700340 if not self.options.lacros and self.device.IsSELinuxAvailable() and (
Ben Pastene5f03b052019-08-12 18:03:24 -0700341 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400342 self.device.run(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800343
344 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800345 if p.mode:
346 # Set mode if necessary.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400347 self.device.run('chmod %o %s/%s' % (
David Haddock3151d912017-10-24 03:50:32 +0000348 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800349
Erik Chen75a2f492020-08-06 19:15:11 -0700350 if self.options.lacros:
351 self.device.run(['chown', '-R', 'chronos:chronos',
352 self.options.target_dir])
353
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700354 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
355 # pick up major changes (bus type, logging, etc.), but all we care about is
356 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
357 # be authorized to take ownership of its service names.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400358 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600359
Erik Chen75a2f492020-08-06 19:15:11 -0700360 if self.options.startui and self._stopped_ui:
Steve Funge984a532013-11-25 17:09:25 -0800361 logging.info('Starting UI...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400362 self.device.run('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700363
Ben Pastenee484b342020-06-30 18:29:27 -0700364 def _DeployTestBinaries(self):
365 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
366
367 There could be several binaries located in the local build dir, so compare
368 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
369 over any that we also built ourselves.
370 """
371 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
372 if r.returncode != 0:
373 raise DeployFailure('Unable to ls contents of %s' % _CHROME_TEST_BIN_DIR)
374 binaries_to_copy = []
375 for f in r.output.splitlines():
376 binaries_to_copy.append(
377 chrome_util.Path(os.path.basename(f), exe=True, optional=True))
378
379 staging_dir = os.path.join(
380 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR))
381 _PrepareStagingDir(self.options, self.tempdir, staging_dir,
382 copy_paths=binaries_to_copy)
Brian Sheedy86f12342020-10-29 15:30:02 -0700383 # Deploying can occasionally run into issues with rsync getting a broken
384 # pipe, so retry several times. See crbug.com/1141618 for more
385 # information.
386 retry_util.RetryException(
387 None, 3, self.device.CopyToDevice, staging_dir,
388 os.path.dirname(_CHROME_TEST_BIN_DIR), mode='rsync')
Ben Pastenee484b342020-06-30 18:29:27 -0700389
David James88e6f032013-03-02 08:13:20 -0800390 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700391 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800392 logging.info('Testing connection to the device...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400393 self.device.run('true')
David James88e6f032013-03-02 08:13:20 -0800394 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700395 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800396 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700397
Avery Musbach3edff0e2020-03-27 13:35:53 -0700398 def _CheckBoard(self):
399 """Check that the Chrome build is targeted for the device board."""
400 if self.options.board == self.device.board:
401 return
402 logging.warning('Device board is %s whereas target board is %s.',
403 self.device.board, self.options.board)
404 if self.options.force:
405 return
406 if not cros_build_lib.BooleanPrompt('Continue despite board mismatch?',
407 False):
408 raise DeployFailure('Aborted.')
409
Steve Funge984a532013-11-25 17:09:25 -0800410 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700411 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700412 def BinaryExists(filename):
413 """Checks if the passed-in file is present in the build directory."""
414 return os.path.exists(os.path.join(self.options.build_dir, filename))
415
Erik Chen75a2f492020-08-06 19:15:11 -0700416 # In the future, lacros-chrome and ash-chrome will likely be named
417 # something other than 'chrome' to avoid confusion.
Daniel Erat9813f0e2014-11-12 11:00:28 -0700418 # Handle non-Chrome deployments.
419 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700420 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700421 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
422
David James88e6f032013-03-02 08:13:20 -0800423 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800424 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
425 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800426
Thiago Goncales12793312013-05-23 11:26:17 -0700427 def _MountTarget(self):
428 logging.info('Mounting Chrome...')
429
Anushruth8d797672019-10-17 12:22:31 -0700430 # Create directory if does not exist.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400431 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
Anushruth8d797672019-10-17 12:22:31 -0700432 try:
433 # Umount the existing mount on mount_dir if present first.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400434 self.device.run(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
435 {'dir': self.options.mount_dir})
Anushruth8d797672019-10-17 12:22:31 -0700436 except cros_build_lib.RunCommandError as e:
437 logging.error('Failed to umount %s', self.options.mount_dir)
438 # If there is a failure, check if some processs is using the mount_dir.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400439 result = self.device.run(LSOF_COMMAND % (self.options.mount_dir,),
440 check=False, capture_output=True,
441 encoding='utf-8')
Anushruth8d797672019-10-17 12:22:31 -0700442 logging.error('lsof %s -->', self.options.mount_dir)
443 logging.error(result.stdout)
444 raise e
445
Mike Frysinger3459bf52020-03-31 00:52:11 -0400446 self.device.run(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
447 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700448
Thiago Goncales12793312013-05-23 11:26:17 -0700449 # Chrome needs partition to have exec and suid flags set
Mike Frysinger3459bf52020-03-31 00:52:11 -0400450 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500451
452 def Cleanup(self):
453 """Clean up RemoteDevice."""
454 if not self.options.staging_only:
455 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700456
David James88e6f032013-03-02 08:13:20 -0800457 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800458 self._CheckDeployType()
459
David James88e6f032013-03-02 08:13:20 -0800460 # If requested, just do the staging step.
461 if self.options.staging_only:
462 self._PrepareStagingDir()
463 return 0
464
Erik Chen75a2f492020-08-06 19:15:11 -0700465 # Check that the build matches the device. Lacros-chrome skips this check as
466 # it's currently board independent. This means that it's possible to deploy
467 # a build of lacros-chrome with a mismatched architecture. We don't try to
468 # prevent this developer foot-gun.
469 if not self.options.lacros:
470 self._CheckBoard()
Avery Musbach3edff0e2020-03-27 13:35:53 -0700471
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800472 # Ensure that the target directory exists before running parallel steps.
473 self._EnsureTargetDir()
474
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800475 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800476 steps = [self._GetDeviceInfo, self._CheckConnection,
Erik Chen75a2f492020-08-06 19:15:11 -0700477 self._MountRootfsAsWritable,
Steve Funge984a532013-11-25 17:09:25 -0800478 self._PrepareStagingDir]
Erik Chen75a2f492020-08-06 19:15:11 -0700479
Yuke Liaobe6bac32020-12-26 22:16:49 -0800480 restart_ui = True
481 if self.options.lacros:
Yuke Liao24fc60c2020-12-26 22:16:49 -0800482 # If this is a lacros build, we only want to restart ash-chrome if needed.
483 restart_ui = False
Yuke Liaobe6bac32020-12-26 22:16:49 -0800484 steps.append(self._KillLacrosChrome)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000485 if self.options.reset_lacros:
486 steps.append(self._ResetLacrosChrome)
Yuke Liao24fc60c2020-12-26 22:16:49 -0800487 if self.options.modify_config_file:
488 restart_ui = self._ModifyConfigFileIfNeededForLacros()
489
Yuke Liaobe6bac32020-12-26 22:16:49 -0800490 if restart_ui:
491 steps.append(self._KillAshChromeIfNeeded)
ChromeOS Developer78345852020-08-11 23:09:48 -0700492 self._stopped_ui = True
Yuke Liaobe6bac32020-12-26 22:16:49 -0800493
Ryan Cui7193a7e2013-04-26 14:15:19 -0700494 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
495 return_values=True)
496 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800497
Steven Bennettsca73efa2018-07-10 13:36:56 -0700498 # If the root dir is not writable, try disabling rootfs verification.
499 # (We always do this by default so that developers can write to
500 # /etc/chriome_dev.conf and other directories in the rootfs).
501 if self._root_dir_is_still_readonly.is_set():
502 if self.options.noremove_rootfs_verification:
503 logging.warning('Skipping disable rootfs verification.')
504 elif not self._DisableRootfsVerification():
505 logging.warning('Failed to disable rootfs verification.')
506
507 # If the target dir is still not writable (i.e. the user opted out or the
508 # command failed), abort.
509 if not self.device.IsDirWritable(self.options.target_dir):
Erik Chen75a2f492020-08-06 19:15:11 -0700510 if self.options.startui and self._stopped_ui:
Steven Bennettsca73efa2018-07-10 13:36:56 -0700511 logging.info('Restarting Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400512 self.device.run('start ui')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700513 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800514
Thiago Goncales12793312013-05-23 11:26:17 -0700515 if self.options.mount_dir is not None:
516 self._MountTarget()
517
David James88e6f032013-03-02 08:13:20 -0800518 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700519 self._Deploy()
Ben Pastenee484b342020-06-30 18:29:27 -0700520 if self.options.deploy_test_binaries:
521 self._DeployTestBinaries()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700522
523
Yuke Liaobe6bac32020-12-26 22:16:49 -0800524 def _ModifyConfigFileIfNeededForLacros(self):
525 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
526
527 Returns:
528 True if the file is modified, and the return value is usually used to
529 determine whether restarting ash-chrome is needed.
530 """
531 assert self.options.lacros, (
532 'Only deploying lacros-chrome needs to modify the config file.')
533 # Update /etc/chrome_dev.conf to include appropriate flags.
534 modified = False
535 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
536 if result.stdout.strip() == MODIFIED_CONF_FILE:
537 modified = True
538 result = self.device.run(
539 _SET_LACROS_PATH_VIA_CONF_COMMAND % {
540 'conf_file': _CONF_FILE,
541 'lacros_path': self.options.target_dir,
542 'modified_conf_file': MODIFIED_CONF_FILE
543 },
544 shell=True)
545 if result.stdout.strip() == MODIFIED_CONF_FILE:
546 modified = True
547
548 return modified
549
550
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700551def ValidateStagingFlags(value):
552 """Convert formatted string to dictionary."""
553 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700554
555
Steven Bennetts368c3e52016-09-23 13:05:21 -0700556def ValidateGnArgs(value):
557 """Convert GN_ARGS-formatted string to dictionary."""
558 return gn_helpers.FromGNArgs(value)
559
560
Ryan Cuie535b172012-10-19 18:25:03 -0700561def _CreateParser():
562 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400563 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700564
Ryan Cuia56a71e2012-10-18 18:40:35 -0700565 # TODO(rcui): Have this use the UI-V2 format of having source and target
566 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400567 parser.add_argument('--force', action='store_true', default=False,
Avery Musbach3edff0e2020-03-27 13:35:53 -0700568 help='Skip all prompts (such as the prompt for disabling '
569 'of rootfs verification). This may result in the '
570 'target machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800571 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400572 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400573 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400574 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400575 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400576 parser.add_argument('--build-dir', type='path',
577 help='The directory with Chrome build artifacts to '
578 'deploy from. Typically of format '
579 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700580 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400581 parser.add_argument('--target-dir', type='path',
582 default=None,
583 help='Target directory on device to deploy Chrome into.')
584 parser.add_argument('-g', '--gs-path', type='gs_path',
585 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500586 parser.add_argument('--private-key', type='path', default=None,
587 help='An ssh private key to use when deploying to '
588 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400589 parser.add_argument('--nostartui', action='store_false', dest='startui',
590 default=True,
591 help="Don't restart the ui daemon after deployment.")
592 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
593 default=True,
594 help="Don't strip binaries during deployment. Warning: "
595 'the resulting binaries will be very large!')
Ben Pastene3bd3e5e2020-08-10 14:40:43 -0700596 parser.add_argument(
597 '-d', '--device',
598 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
599 help='Device hostname or IP in the format hostname[:port].')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400600 parser.add_argument('--mount-dir', type='path', default=None,
601 help='Deploy Chrome in target directory and bind it '
602 'to the directory specified by this flag.'
603 'Any existing mount on this directory will be '
604 'umounted first.')
605 parser.add_argument('--mount', action='store_true', default=False,
606 help='Deploy Chrome to default target directory and bind '
607 'it to the default mount directory.'
608 'Any existing mount on this directory will be '
609 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700610 parser.add_argument('--noremove-rootfs-verification', action='store_true',
611 default=False, help='Never remove rootfs verification.')
Ben Pastenee484b342020-06-30 18:29:27 -0700612 parser.add_argument('--deploy-test-binaries', action='store_true',
613 default=False,
614 help='Also deploy any test binaries to %s. Useful for '
615 'running any Tast tests that execute these '
616 'binaries.' % _CHROME_TEST_BIN_DIR)
Erik Chen75a2f492020-08-06 19:15:11 -0700617 parser.add_argument('--lacros', action='store_true', default=False,
618 help='Deploys lacros-chrome rather than ash-chrome.')
Sven Zheng92eb66e2022-02-03 21:23:51 +0000619 parser.add_argument('--reset-lacros', action='store_true', default=False,
620 help='Reset Lacros by deleting Lacros user data dir if '
621 'exists.')
Yuke Liao24fc60c2020-12-26 22:16:49 -0800622 parser.add_argument('--skip-modifying-config-file', action='store_false',
623 dest='modify_config_file',
624 help='By default, deploying lacros-chrome modifies the '
625 '/etc/chrome_dev.conf file, which interferes with '
626 'automated testing, and this argument disables it.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700627
Mike Frysingerc3061a62015-06-04 04:16:18 -0400628 group = parser.add_argument_group('Advanced Options')
629 group.add_argument('-l', '--local-pkg-path', type='path',
630 help='Path to local chrome prebuilt package to deploy.')
631 group.add_argument('--sloppy', action='store_true', default=False,
632 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700633 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400634 help=('Extra flags to control staging. Valid flags are - '
635 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700636 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400637 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700638 help='Deprecated. Default behavior is "strict". Use '
639 '--sloppy to omit warnings for missing optional '
640 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400641 group.add_argument('--strip-flags', default=None,
642 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400643 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400644 group.add_argument('--ping', action='store_true', default=False,
645 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700646 group.add_argument('--process-timeout', type=int,
647 default=KILL_PROC_MAX_WAIT,
648 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700649
Mike Frysingerc3061a62015-06-04 04:16:18 -0400650 group = parser.add_argument_group(
651 'Metadata Overrides (Advanced)',
652 description='Provide all of these overrides in order to remove '
653 'dependencies on metadata.json existence.')
654 group.add_argument('--target-tc', action='store', default=None,
655 help='Override target toolchain name, e.g. '
656 'x86_64-cros-linux-gnu')
657 group.add_argument('--toolchain-url', action='store', default=None,
658 help='Override toolchain url format pattern, e.g. '
659 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700660
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700661 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
662 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
663 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400664 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700665
666 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
667 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
668 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
669 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
670 help=argparse.SUPPRESS)
671
Ryan Cuia56a71e2012-10-18 18:40:35 -0700672 # Path of an empty directory to stage chrome artifacts to. Defaults to a
673 # temporary directory that is removed when the script finishes. If the path
674 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400675 parser.add_argument('--staging-dir', type='path', default=None,
676 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700677 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400678 parser.add_argument('--staging-only', action='store_true', default=False,
679 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700680 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
681 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
682 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400683 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900684 parser.add_argument('--compress', action='store', default='auto',
685 choices=('always', 'never', 'auto'),
686 help='Choose the data compression behavior. Default '
687 'is set to "auto", that disables compression if '
688 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700689 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700690
Ryan Cuie535b172012-10-19 18:25:03 -0700691
692def _ParseCommandLine(argv):
693 """Parse args, and run environment-independent checks."""
694 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400695 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700696
Ryan Cuia56a71e2012-10-18 18:40:35 -0700697 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
698 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800699 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700700 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
701 parser.error('Cannot specify both --build_dir and '
702 '--gs-path/--local-pkg-patch')
Erik Chen75a2f492020-08-06 19:15:11 -0700703 if options.lacros:
704 if options.board:
705 parser.error('--board is not supported with --lacros')
706 # The stripping implemented in this file rely on the cros-chrome-sdk, which
707 # is inappropriate for Lacros. Lacros stripping is currently not
708 # implemented.
709 if options.dostrip:
710 parser.error('--lacros requires --nostrip')
711 if options.mount_dir or options.mount:
712 parser.error('--lacros does not support --mount or --mount-dir')
713 if options.deploy_test_binaries:
714 parser.error('--lacros does not support --deploy-test-binaries')
715 if options.local_pkg_path:
716 parser.error('--lacros does not support --local-pkg-path')
717 else:
Ryo Hashimoto77f8eca2021-04-16 16:43:37 +0900718 if not options.board and options.build_dir:
719 match = re.search(r'out_([^/]+)/Release$', options.build_dir)
720 if match:
721 options.board = match.group(1)
722 logging.info('--board is set to %s', options.board)
Erik Chen75a2f492020-08-06 19:15:11 -0700723 if not options.board:
724 parser.error('--board is required')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700725 if options.gs_path and options.local_pkg_path:
726 parser.error('Cannot specify both --gs-path and --local-pkg-path')
Ben Pasteneb44d8e72021-02-19 13:56:43 -0800727 if not (options.staging_only or options.device):
Ben Pastene3bd3e5e2020-08-10 14:40:43 -0700728 parser.error('Need to specify --device')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700729 if options.staging_flags and not options.build_dir:
730 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700731
Steven Bennetts46a84c32016-08-12 15:20:46 -0700732 if options.strict:
733 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700734 if options.gyp_defines:
735 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700736
737 if options.mount or options.mount_dir:
738 if not options.target_dir:
739 options.target_dir = _CHROME_DIR_MOUNT
740 else:
741 if not options.target_dir:
Erik Chen75a2f492020-08-06 19:15:11 -0700742 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700743
744 if options.mount and not options.mount_dir:
745 options.mount_dir = _CHROME_DIR
746
Mike Frysingerc3061a62015-06-04 04:16:18 -0400747 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700748
749
Mike Frysingerc3061a62015-06-04 04:16:18 -0400750def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800751 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700752
753 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400754 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700755 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700756 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
757 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
758
Steven Bennetts368c3e52016-09-23 13:05:21 -0700759 if not options.gn_args:
760 gn_env = os.getenv('GN_ARGS')
761 if gn_env is not None:
762 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800763 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700764
Steven Bennetts60600462016-05-12 10:40:20 -0700765 if not options.staging_flags:
766 use_env = os.getenv('USE')
767 if use_env is not None:
768 options.staging_flags = ' '.join(set(use_env.split()).intersection(
769 chrome_util.STAGING_FLAGS))
770 logging.info('Staging flags taken from USE in environment: %s',
771 options.staging_flags)
772
Ryan Cuia56a71e2012-10-18 18:40:35 -0700773
Ryan Cui504db722013-01-22 11:48:01 -0800774def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700775 """Get the chrome prebuilt tarball from GS.
776
Mike Frysinger02e1e072013-11-10 22:11:34 -0500777 Returns:
778 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700779 """
David James9374aac2013-10-08 16:00:17 -0700780 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500781 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800782 files = [found for found in files if
783 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
784 if not files:
785 raise Exception('No chrome package found at %s' % gs_path)
786 elif len(files) > 1:
787 # - Users should provide us with a direct link to either a stripped or
788 # unstripped chrome package.
789 # - In the case of being provided with an archive directory, where both
790 # stripped and unstripped chrome available, use the stripped chrome
791 # package.
792 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
793 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
794 files = [f for f in files if not 'unstripped' in f]
795 assert len(files) == 1
796 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800797
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800798 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800799 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800800 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
801 chrome_path = os.path.join(tempdir, filename)
802 assert os.path.exists(chrome_path)
803 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700804
805
Ryan Cuif890a3e2013-03-07 18:57:06 -0800806@contextlib.contextmanager
807def _StripBinContext(options):
808 if not options.dostrip:
809 yield None
810 elif options.strip_bin:
811 yield options.strip_bin
812 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800813 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800814 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700815 with sdk.Prepare(components=components, target_tc=options.target_tc,
816 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800817 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
818 constants.CHROME_ENV_FILE)
819 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
820 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
821 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800822 yield strip_bin
823
824
Steve Funge984a532013-11-25 17:09:25 -0800825def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
Erik Chen75a2f492020-08-06 19:15:11 -0700826 chrome_dir=None):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800827 """Place the necessary files in the staging directory.
828
829 The staging directory is the directory used to rsync the build artifacts over
830 to the device. Only the necessary Chrome build artifacts are put into the
831 staging directory.
832 """
Erik Chen75a2f492020-08-06 19:15:11 -0700833 if chrome_dir is None:
834 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Ryan Cui5866be02013-03-18 14:12:00 -0700835 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400836 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800837 if options.build_dir:
838 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700839 strip_flags = (None if options.strip_flags is None else
840 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800841 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700842 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700843 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700844 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800845 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700846 else:
847 pkg_path = options.local_pkg_path
848 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800849 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
850 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700851
852 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800853 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700854 # Extract only the ./opt/google/chrome contents, directly into the staging
855 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800856 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400857 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800858 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
859 staging_dir])
860 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
861 shutil.move(filename, staging_dir)
862 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
863 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400864 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800865 ['tar', '--strip-components', '4', '--extract',
866 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
867 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700868
Ryan Cui71aa8de2013-04-19 16:12:55 -0700869
Ryan Cui3045c5d2012-07-13 18:00:33 -0700870def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400871 options = _ParseCommandLine(argv)
872 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700873
Aviv Keshet01a82e92017-03-02 17:39:59 -0800874 with osutils.TempDir(set_global=True) as tempdir:
875 staging_dir = options.staging_dir
876 if not staging_dir:
877 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700878
Aviv Keshet01a82e92017-03-02 17:39:59 -0800879 deploy = DeployChrome(options, tempdir, staging_dir)
880 try:
881 deploy.Perform()
882 except failures_lib.StepFailure as ex:
883 raise SystemExit(str(ex).strip())
884 deploy.Cleanup()