blob: e4b181ede7bfa2cc6a9313f1df75bee6f370ef30 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2012 The ChromiumOS Authors
Ryan Cui3045c5d2012-07-13 18:00:33 -07002# 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
Joel Hockeyee4fa302023-03-23 16:44:44 -070052POST_UNLOCK_WAIT = 3
Ryan Cui3045c5d2012-07-13 18:00:33 -070053
Alex Klein1699fab2022-09-08 08:46:06 -060054MOUNT_RW_COMMAND = "mount -o remount,rw /"
55LSOF_COMMAND_CHROME = "lsof %s/chrome"
56LSOF_COMMAND = "lsof %s"
57DBUS_RELOAD_COMMAND = "killall -HUP dbus-daemon"
Joel Hockey764728e2023-03-14 17:10:04 -070058LAST_LOGIN_COMMAND = "bootstat_get_last login-prompt-visible"
Joel Hockey95aa8b02023-03-21 22:09:22 -070059UNLOCK_PASSWORD_COMMAND = "python -m uinput.cros_type_keys $'%s\\n'"
Ryan Cui3045c5d2012-07-13 18:00:33 -070060
Alex Klein1699fab2022-09-08 08:46:06 -060061_ANDROID_DIR = "/system/chrome"
62_ANDROID_DIR_EXTRACT_PATH = "system/chrome/*"
Steve Funge984a532013-11-25 17:09:25 -080063
Alex Klein1699fab2022-09-08 08:46:06 -060064_CHROME_DIR = "/opt/google/chrome"
Ben Pastene361e2042023-03-17 11:00:33 -070065_CHROME_DIR_MOUNT = "/usr/local/opt/google/chrome"
Alex Klein1699fab2022-09-08 08:46:06 -060066_CHROME_DIR_STAGING_TARBALL_ZSTD = "chrome.tar.zst"
67_CHROME_TEST_BIN_DIR = "/usr/local/libexec/chrome-binary-tests"
David James2cb34002013-03-01 18:42:40 -080068
David Haddock3151d912017-10-24 03:50:32 +000069_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
Alex Klein1699fab2022-09-08 08:46:06 -060070 "if mountpoint -q %(dir)s; then umount %(dir)s; fi"
71)
72_BIND_TO_FINAL_DIR_CMD = "mount --rbind %s %s"
73_SET_MOUNT_FLAGS_CMD = "mount -o remount,exec,suid %s"
74_MKDIR_P_CMD = "mkdir -p --mode 0775 %s"
75_FIND_TEST_BIN_CMD = "find %s -maxdepth 1 -executable -type f" % (
76 _CHROME_TEST_BIN_DIR
77)
David Haddock3151d912017-10-24 03:50:32 +000078
Alex Klein1699fab2022-09-08 08:46:06 -060079DF_COMMAND = "df -k %s"
David Haddock3151d912017-10-24 03:50:32 +000080
Daniil Lunev0c4f65c2022-09-19 11:01:34 +100081# This constants are related to an experiment of running compressed ash chrome
82# to save rootfs space. See b/247397013
83COMPRESSED_ASH_SERVICE = "mount-ash-chrome"
84COMPRESSED_ASH_FILE = "chrome.squashfs"
85RAW_ASH_FILE = "chrome"
86COMPRESSED_ASH_PATH = os.path.join(_CHROME_DIR, COMPRESSED_ASH_FILE)
87RAW_ASH_PATH = os.path.join(_CHROME_DIR, RAW_ASH_FILE)
Daniil Luneve2954832022-10-11 11:38:51 +110088COMPRESSED_ASH_OVERLAY_SUFFIX = "-compressed-ash"
Daniil Lunev0c4f65c2022-09-19 11:01:34 +100089
Alex Klein1699fab2022-09-08 08:46:06 -060090LACROS_DIR = "/usr/local/lacros-chrome"
91_CONF_FILE = "/etc/chrome_dev.conf"
92_KILL_LACROS_CHROME_CMD = "pkill -f %(lacros_dir)s/chrome"
93_RESET_LACROS_CHROME_CMD = "rm -rf /home/chronos/user/lacros"
94MODIFIED_CONF_FILE = f"modified {_CONF_FILE}"
Erik Chen75a2f492020-08-06 19:15:11 -070095
96# This command checks if "--enable-features=LacrosSupport" is present in
97# /etc/chrome_dev.conf. If it is not, then it is added.
98# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
99# to modify chrome_dev.conf. Either revisit this policy or find another
100# mechanism to pass configuration to ash-chrome.
101ENABLE_LACROS_VIA_CONF_COMMAND = f"""
102 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
103 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
104 echo {MODIFIED_CONF_FILE};
105 fi
106"""
107
108# This command checks if "--lacros-chrome-path=" is present with the right value
109# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
110# and the new one is added.
111# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
112# to modify chrome_dev.conf. Either revisit this policy or find another
113# mechanism to pass configuration to ash-chrome.
114_SET_LACROS_PATH_VIA_CONF_COMMAND = """
115 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
116 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
117 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
118 echo %(modified_conf_file)s;
119 fi
120"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500121
Alex Klein1699fab2022-09-08 08:46:06 -0600122
Ryan Cui3045c5d2012-07-13 18:00:33 -0700123def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600124 """Return the last component of the URL."""
125 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700126
127
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700128class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600129 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800130
131
Ryan Cui7193a7e2013-04-26 14:15:19 -0700132DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600133 "DeviceInfo", ["target_dir_size", "target_fs_free"]
134)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700135
136
Ryan Cui3045c5d2012-07-13 18:00:33 -0700137class DeployChrome(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600138 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 def __init__(self, options, tempdir, staging_dir):
141 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600144 options: options object.
145 tempdir: Scratch space for the class. Caller has responsibility to
146 clean it up.
147 staging_dir: Directory to stage the files to.
Alex Klein1699fab2022-09-08 08:46:06 -0600148 """
149 self.tempdir = tempdir
150 self.options = options
151 self.staging_dir = staging_dir
152 if not self.options.staging_only:
153 hostname = options.device.hostname
154 port = options.device.port
155 self.device = remote.ChromiumOSDevice(
156 hostname,
157 port=port,
158 ping=options.ping,
159 private_key=options.private_key,
160 include_dev_paths=False,
161 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000162 if self._ShouldUseCompressedAsh():
163 self.options.compressed_ash = True
164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000166
Alex Klein1699fab2022-09-08 08:46:06 -0600167 self._deployment_name = "lacros" if options.lacros else "chrome"
168 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700169
Alex Klein1699fab2022-09-08 08:46:06 -0600170 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 # Whether UI was stopped during setup.
173 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800174
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000175 def _ShouldUseCompressedAsh(self):
176 """Detects if the DUT uses compressed-ash setup."""
177 if self.options.lacros:
178 return False
179
180 return self.device.IfFileExists(COMPRESSED_ASH_PATH)
181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 def _GetRemoteMountFree(self, remote_dir):
183 result = self.device.run(DF_COMMAND % remote_dir)
184 line = result.stdout.splitlines()[1]
185 value = line.split()[3]
186 multipliers = {
187 "G": 1024 * 1024 * 1024,
188 "M": 1024 * 1024,
189 "K": 1024,
190 }
191 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 def _GetRemoteDirSize(self, remote_dir):
194 result = self.device.run(
195 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
196 )
197 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700198
Alex Klein1699fab2022-09-08 08:46:06 -0600199 def _GetStagingDirSize(self):
200 result = cros_build_lib.dbg_run(
201 ["du", "-ks", self.staging_dir],
202 capture_output=True,
203 encoding="utf-8",
204 )
205 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700206
Alex Klein1699fab2022-09-08 08:46:06 -0600207 def _ChromeFileInUse(self):
208 result = self.device.run(
209 LSOF_COMMAND_CHROME % (self.options.target_dir,),
210 check=False,
211 capture_output=True,
212 )
213 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700214
Alex Klein1699fab2022-09-08 08:46:06 -0600215 def _Reboot(self):
216 # A reboot in developer mode takes a while (and has delays), so the user
217 # will have time to read and act on the USB boot instructions below.
218 logging.info(
219 "Please remember to press Ctrl-U if you are booting from USB."
220 )
221 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600222
Alex Klein1699fab2022-09-08 08:46:06 -0600223 def _DisableRootfsVerification(self):
224 if not self.options.force:
225 logging.error(
226 "Detected that the device has rootfs verification enabled."
227 )
228 logging.info(
229 "This script can automatically remove the rootfs "
230 "verification, which requires it to reboot the device."
231 )
232 logging.info("Make sure the device is in developer mode!")
233 logging.info("Skip this prompt by specifying --force.")
234 if not cros_build_lib.BooleanPrompt(
235 "Remove rootfs verification?", False
236 ):
237 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700238
Alex Klein1699fab2022-09-08 08:46:06 -0600239 logging.info(
240 "Removing rootfs verification from %s", self.options.device
241 )
Alex Klein68b270c2023-04-14 14:42:50 -0600242 # Running in VMs cause make_dev_ssd's firmware confidence checks to
243 # fail. Use --force to bypass the checks.
Alex Klein1699fab2022-09-08 08:46:06 -0600244 cmd = (
245 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
246 "--remove_rootfs_verification --force"
247 )
248 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
249 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 # Now that the machine has been rebooted, we need to kill Chrome again.
254 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 # Make sure the rootfs is writable now.
257 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 def _CheckUiJobStarted(self):
262 # status output is in the format:
263 # <job_name> <status> ['process' <pid>].
264 # <status> is in the format <goal>/<state>.
265 try:
266 result = self.device.run(
267 "status ui", capture_output=True, encoding="utf-8"
268 )
269 except cros_build_lib.RunCommandError as e:
270 if "Unknown job" in e.stderr:
271 return False
272 else:
273 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800274
Alex Klein1699fab2022-09-08 08:46:06 -0600275 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700276
Alex Klein1699fab2022-09-08 08:46:06 -0600277 def _KillLacrosChrome(self):
278 """This method kills lacros-chrome on the device, if it's running."""
279 self.device.run(
280 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
281 check=False,
282 )
Erik Chen75a2f492020-08-06 19:15:11 -0700283
Alex Klein1699fab2022-09-08 08:46:06 -0600284 def _ResetLacrosChrome(self):
285 """Reset Lacros to fresh state by deleting user data dir."""
286 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 def _KillAshChromeIfNeeded(self):
289 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700290
Alex Klein68b270c2023-04-14 14:42:50 -0600291 This method calls 'stop ui', and then also manually pkills both
292 ash-chrome and the session manager.
Alex Klein1699fab2022-09-08 08:46:06 -0600293 """
294 if self._CheckUiJobStarted():
295 logging.info("Shutting down Chrome...")
296 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700297
Alex Klein1699fab2022-09-08 08:46:06 -0600298 # Developers sometimes run session_manager manually, in which case we'll
299 # need to help shut the chrome processes down.
300 try:
301 with timeout_util.Timeout(self.options.process_timeout):
302 while self._ChromeFileInUse():
303 logging.warning(
304 "The chrome binary on the device is in use."
305 )
306 logging.warning(
307 "Killing chrome and session_manager processes...\n"
308 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 self.device.run(
311 "pkill 'chrome|session_manager'", check=False
312 )
313 # Wait for processes to actually terminate
314 time.sleep(POST_KILL_WAIT)
315 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000316 if self.options.compressed_ash:
317 result = self.device.run(
318 ["umount", RAW_ASH_PATH],
319 check=False,
320 capture_output=True,
321 )
322 if result.returncode and not (
323 result.returncode == 32
324 and "not mounted" in result.stderr
325 ):
326 raise DeployFailure(
327 "Could not unmount compressed ash. "
328 f"Error Code: {result.returncode}, "
329 f"Error Message: {result.stderr}"
330 )
Alex Klein1699fab2022-09-08 08:46:06 -0600331 except timeout_util.TimeoutError:
332 msg = (
333 "Could not kill processes after %s seconds. Please exit any "
334 "running chrome processes and try again."
335 % self.options.process_timeout
336 )
337 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 def _MountRootfsAsWritable(self, check=False):
340 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700341
Alex Klein1699fab2022-09-08 08:46:06 -0600342 If the command fails and the root dir is not writable then this function
343 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700344
Alex Klein1699fab2022-09-08 08:46:06 -0600345 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600346 check: See remote.RemoteAccess.RemoteSh for details.
Alex Klein1699fab2022-09-08 08:46:06 -0600347 """
348 # TODO: Should migrate to use the remount functions in remote_access.
349 result = self.device.run(
350 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
351 )
352 if result.returncode and not self.device.IsDirWritable("/"):
353 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700354
Alex Klein1699fab2022-09-08 08:46:06 -0600355 def _EnsureTargetDir(self):
356 """Ensures that the target directory exists on the remote device."""
357 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600358 # Any valid /opt directory should already exist so avoid the remote
359 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600360 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
361 return
362 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600365 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600366 steps = [
367 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
368 functools.partial(
369 self._GetRemoteMountFree, self.options.target_dir
370 ),
371 ]
372 return_values = parallel.RunParallelSteps(steps, return_values=True)
373 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 def _CheckDeviceFreeSpace(self, device_info):
376 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700377
Alex Klein1699fab2022-09-08 08:46:06 -0600378 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600379 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600380 """
381 effective_free = (
382 device_info.target_dir_size + device_info.target_fs_free
383 )
384 staging_size = self._GetStagingDirSize()
385 if effective_free < staging_size:
386 raise DeployFailure(
387 "Not enough free space on the device. Required: %s MiB, "
388 "actual: %s MiB."
389 % (staging_size // 1024, effective_free // 1024)
390 )
391 if device_info.target_fs_free < (100 * 1024):
392 logging.warning(
393 "The device has less than 100MB free. deploy_chrome may "
394 "hang during the transfer."
395 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700396
Alex Klein1699fab2022-09-08 08:46:06 -0600397 def _ShouldUseCompression(self):
398 """Checks if compression should be used for rsync."""
399 if self.options.compress == "always":
400 return True
401 elif self.options.compress == "never":
402 return False
403 elif self.options.compress == "auto":
404 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900405
Alex Klein1699fab2022-09-08 08:46:06 -0600406 def _Deploy(self):
407 logging.info(
408 "Copying %s to %s on device...",
409 self._deployment_name,
410 self.options.target_dir,
411 )
412 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
413 # This does not work for deploy.
414 if not self.device.HasRsync():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200415 # This assumes that rsync is part of the bootstrap package. In the
416 # future, this might change and we'll have to install it separately.
417 if not cros_build_lib.BooleanPrompt(
418 "Run dev_install on the device to install rsync?", True
419 ):
420 raise DeployFailure("rsync is not found on the device.")
421 self.device.BootstrapDevTools()
422 if not self.device.HasRsync():
423 raise DeployFailure("Failed to install rsync")
424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 self.device.CopyToDevice(
426 "%s/" % os.path.abspath(self.staging_dir),
427 self.options.target_dir,
428 mode="rsync",
429 inplace=True,
430 compress=self._ShouldUseCompression(),
431 debug_level=logging.INFO,
432 verbose=self.options.verbose,
433 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700434
Alex Klein68b270c2023-04-14 14:42:50 -0600435 # Set the security context on the default Chrome dir if that's where
436 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600437 if (
438 not self.options.lacros
439 and self.device.IsSELinuxAvailable()
440 and (
441 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
442 )
443 ):
444 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800445
Alex Klein1699fab2022-09-08 08:46:06 -0600446 for p in self.copy_paths:
447 if p.mode:
448 # Set mode if necessary.
449 self.device.run(
450 "chmod %o %s/%s"
451 % (
452 p.mode,
453 self.options.target_dir,
454 p.src if not p.dest else p.dest,
455 )
456 )
Steve Funge984a532013-11-25 17:09:25 -0800457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 if self.options.lacros:
459 self.device.run(
460 ["chown", "-R", "chronos:chronos", self.options.target_dir]
461 )
Erik Chen75a2f492020-08-06 19:15:11 -0700462
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000463 if self.options.compressed_ash:
464 self.device.run(["start", COMPRESSED_ASH_SERVICE])
465
Alex Klein68b270c2023-04-14 14:42:50 -0600466 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
467 # won't pick up major changes (bus type, logging, etc.), but all we care
468 # about is getting the latest policy from /opt/google/chrome/dbus so
469 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600470 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600471
Erik Chen75a2f492020-08-06 19:15:11 -0700472 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700473 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600474 logging.info("Starting UI...")
475 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800476
Joel Hockey764728e2023-03-14 17:10:04 -0700477 if self.options.unlock_password:
478 logging.info("Unlocking...")
479
480 @retry_util.WithRetry(max_retry=5, sleep=1)
481 def WaitForUnlockScreen():
482 if self._GetLastLogin() == last_login:
483 raise DeployFailure("Unlock screen not shown")
484
485 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700486 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700487 self.device.run(
488 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
489 )
490
491 def _GetLastLogin(self):
492 """Returns last login time"""
493 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
494
Alex Klein1699fab2022-09-08 08:46:06 -0600495 def _DeployTestBinaries(self):
496 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700497
Alex Klein68b270c2023-04-14 14:42:50 -0600498 There could be several binaries located in the local build dir, so
499 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
500 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600501 """
502 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
503 if r.returncode != 0:
504 raise DeployFailure(
505 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
506 )
507 binaries_to_copy = []
508 for f in r.stdout.splitlines():
509 binaries_to_copy.append(
510 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
511 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700512
Alex Klein1699fab2022-09-08 08:46:06 -0600513 staging_dir = os.path.join(
514 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
515 )
516 _PrepareStagingDir(
517 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
518 )
519 # Deploying can occasionally run into issues with rsync getting a broken
520 # pipe, so retry several times. See crbug.com/1141618 for more
521 # information.
522 retry_util.RetryException(
523 None,
524 3,
525 self.device.CopyToDevice,
526 staging_dir,
527 os.path.dirname(_CHROME_TEST_BIN_DIR),
528 mode="rsync",
529 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700530
Alex Klein1699fab2022-09-08 08:46:06 -0600531 def _CheckConnection(self):
532 try:
533 logging.info("Testing connection to the device...")
534 self.device.run("true")
535 except cros_build_lib.RunCommandError as ex:
536 logging.error("Error connecting to the test device.")
537 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 def _CheckBoard(self):
540 """Check that the Chrome build is targeted for the device board."""
541 if self.options.board == self.device.board:
542 return
543 logging.warning(
544 "Device board is %s whereas target board is %s.",
545 self.device.board,
546 self.options.board,
547 )
548 if self.options.force:
549 return
550 if not cros_build_lib.BooleanPrompt(
551 "Continue despite board mismatch?", False
552 ):
553 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800554
Alex Klein1699fab2022-09-08 08:46:06 -0600555 def _CheckDeployType(self):
556 if self.options.build_dir:
557
558 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600559 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600560 return os.path.exists(
561 os.path.join(self.options.build_dir, filename)
562 )
563
564 # In the future, lacros-chrome and ash-chrome will likely be named
565 # something other than 'chrome' to avoid confusion.
566 # Handle non-Chrome deployments.
567 if not BinaryExists("chrome"):
568 if BinaryExists("app_shell"):
569 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
570
571 def _PrepareStagingDir(self):
572 _PrepareStagingDir(
573 self.options,
574 self.tempdir,
575 self.staging_dir,
576 self.copy_paths,
577 self.chrome_dir,
578 )
579
580 def _MountTarget(self):
581 logging.info("Mounting Chrome...")
582
583 # Create directory if does not exist.
584 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
585 try:
586 # Umount the existing mount on mount_dir if present first.
587 self.device.run(
588 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
589 )
590 except cros_build_lib.RunCommandError as e:
591 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600592 # If there is a failure, check if some process is using the
593 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600594 result = self.device.run(
595 LSOF_COMMAND % (self.options.mount_dir,),
596 check=False,
597 capture_output=True,
598 encoding="utf-8",
599 )
600 logging.error("lsof %s -->", self.options.mount_dir)
601 logging.error(result.stdout)
602 raise e
603
604 self.device.run(
605 _BIND_TO_FINAL_DIR_CMD
606 % (self.options.target_dir, self.options.mount_dir)
607 )
608
609 # Chrome needs partition to have exec and suid flags set
610 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
611
612 def Cleanup(self):
613 """Clean up RemoteDevice."""
614 if not self.options.staging_only:
615 self.device.Cleanup()
616
617 def Perform(self):
618 self._CheckDeployType()
619
620 # If requested, just do the staging step.
621 if self.options.staging_only:
622 self._PrepareStagingDir()
623 return 0
624
Alex Klein68b270c2023-04-14 14:42:50 -0600625 # Check that the build matches the device. Lacros-chrome skips this
626 # check as it's currently board independent. This means that it's
627 # possible to deploy a build of lacros-chrome with a mismatched
628 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600629 if not self.options.lacros:
630 self._CheckBoard()
631
632 # Ensure that the target directory exists before running parallel steps.
633 self._EnsureTargetDir()
634
635 logging.info("Preparing device")
636 steps = [
637 self._GetDeviceInfo,
638 self._CheckConnection,
639 self._MountRootfsAsWritable,
640 self._PrepareStagingDir,
641 ]
642
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900643 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600644 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600645 steps.append(self._KillLacrosChrome)
646 if self.options.reset_lacros:
647 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900648 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600649 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900650 config_modified = self._ModifyConfigFileIfNeededForLacros()
651 if config_modified and not restart_ui:
652 logging.warning(
653 "Config file modified but skipping restart_ui "
654 "due to option --skip-restart-ui. Config file "
655 "update is not reflected."
656 )
Alex Klein1699fab2022-09-08 08:46:06 -0600657
658 if restart_ui:
659 steps.append(self._KillAshChromeIfNeeded)
660 self._stopped_ui = True
661
662 ret = parallel.RunParallelSteps(
663 steps, halt_on_error=True, return_values=True
664 )
665 self._CheckDeviceFreeSpace(ret[0])
666
667 # If the root dir is not writable, try disabling rootfs verification.
668 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600669 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600670 if self._root_dir_is_still_readonly.is_set():
671 if self.options.noremove_rootfs_verification:
672 logging.warning("Skipping disable rootfs verification.")
673 elif not self._DisableRootfsVerification():
674 logging.warning("Failed to disable rootfs verification.")
675
Alex Klein68b270c2023-04-14 14:42:50 -0600676 # If the target dir is still not writable (i.e. the user opted out
677 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600678 if not self.device.IsDirWritable(self.options.target_dir):
679 if self.options.startui and self._stopped_ui:
680 logging.info("Restarting Chrome...")
681 self.device.run("start ui")
682 raise DeployFailure(
683 "Target location is not writable. Aborting."
684 )
685
686 if self.options.mount_dir is not None:
687 self._MountTarget()
688
689 # Actually deploy Chrome to the device.
690 self._Deploy()
691 if self.options.deploy_test_binaries:
692 self._DeployTestBinaries()
693
694 def _ModifyConfigFileIfNeededForLacros(self):
695 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
696
697 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600698 True if the file is modified, and the return value is usually used
699 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600700 """
701 assert (
702 self.options.lacros
703 ), "Only deploying lacros-chrome needs to modify the config file."
704 # Update /etc/chrome_dev.conf to include appropriate flags.
705 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000706 if self.options.enable_lacros_support:
707 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
708 if result.stdout.strip() == MODIFIED_CONF_FILE:
709 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600710 result = self.device.run(
711 _SET_LACROS_PATH_VIA_CONF_COMMAND
712 % {
713 "conf_file": _CONF_FILE,
714 "lacros_path": self.options.target_dir,
715 "modified_conf_file": MODIFIED_CONF_FILE,
716 },
717 shell=True,
718 )
719 if result.stdout.strip() == MODIFIED_CONF_FILE:
720 modified = True
721
722 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800723
724
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700725def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600726 """Convert formatted string to dictionary."""
727 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700728
729
Steven Bennetts368c3e52016-09-23 13:05:21 -0700730def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600731 """Convert GN_ARGS-formatted string to dictionary."""
732 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700733
734
Ryan Cuie535b172012-10-19 18:25:03 -0700735def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600736 """Create our custom parser."""
737 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700738
Alex Klein1699fab2022-09-08 08:46:06 -0600739 # TODO(rcui): Have this use the UI-V2 format of having source and target
740 # device be specified as positional arguments.
741 parser.add_argument(
742 "--force",
743 action="store_true",
744 default=False,
745 help="Skip all prompts (such as the prompt for disabling "
746 "of rootfs verification). This may result in the "
747 "target machine being rebooted.",
748 )
749 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
750 parser.add_argument(
751 "--board",
752 default=sdk_board_env,
753 help="The board the Chrome build is targeted for. When "
754 "in a 'cros chrome-sdk' shell, defaults to the SDK "
755 "board.",
756 )
757 parser.add_argument(
758 "--build-dir",
759 type="path",
760 help="The directory with Chrome build artifacts to "
761 "deploy from. Typically of format "
762 "<chrome_root>/out/Debug. When this option is used, "
763 "the GN_ARGS environment variable must be set.",
764 )
765 parser.add_argument(
766 "--target-dir",
767 type="path",
768 default=None,
769 help="Target directory on device to deploy Chrome into.",
770 )
771 parser.add_argument(
772 "-g",
773 "--gs-path",
774 type="gs_path",
775 help="GS path that contains the chrome to deploy.",
776 )
777 parser.add_argument(
778 "--private-key",
779 type="path",
780 default=None,
781 help="An ssh private key to use when deploying to " "a CrOS device.",
782 )
783 parser.add_argument(
784 "--nostartui",
785 action="store_false",
786 dest="startui",
787 default=True,
788 help="Don't restart the ui daemon after deployment.",
789 )
790 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700791 "--unlock-password",
792 default=None,
793 help="Password to use to unlock after deployment and restart.",
794 )
795 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600796 "--nostrip",
797 action="store_false",
798 dest="dostrip",
799 default=True,
800 help="Don't strip binaries during deployment. Warning: "
801 "the resulting binaries will be very large!",
802 )
803 parser.add_argument(
804 "-d",
805 "--device",
806 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
807 help="Device hostname or IP in the format hostname[:port].",
808 )
809 parser.add_argument(
810 "--mount-dir",
811 type="path",
812 default=None,
813 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000814 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600815 "Any existing mount on this directory will be "
816 "umounted first.",
817 )
818 parser.add_argument(
819 "--mount",
820 action="store_true",
821 default=False,
822 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000823 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600824 "Any existing mount on this directory will be "
825 "umounted first.",
826 )
827 parser.add_argument(
828 "--noremove-rootfs-verification",
829 action="store_true",
830 default=False,
831 help="Never remove rootfs verification.",
832 )
833 parser.add_argument(
834 "--deploy-test-binaries",
835 action="store_true",
836 default=False,
837 help="Also deploy any test binaries to %s. Useful for "
838 "running any Tast tests that execute these "
839 "binaries." % _CHROME_TEST_BIN_DIR,
840 )
841 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600842 "--use-external-config",
843 action="store_true",
844 help="When identifying the configuration for a board, "
845 "force usage of the external configuration if both "
846 "internal and external are available. This only "
847 "has an effect when stripping Chrome, i.e. when "
848 "--nostrip is not passed in.",
849 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700850
Georg Neis9b1ff192022-09-14 08:07:22 +0000851 group = parser.add_argument_group("Lacros Options")
852 group.add_argument(
853 "--lacros",
854 action="store_true",
855 default=False,
856 help="Deploys lacros-chrome rather than ash-chrome.",
857 )
858 group.add_argument(
859 "--reset-lacros",
860 action="store_true",
861 default=False,
862 help="Reset Lacros by deleting Lacros user data dir if it exists.",
863 )
864 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900865 "--skip-restart-ui",
866 action="store_true",
867 default=False,
868 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
869 "that this flag may cause ETXTBSY error on rsync, and also won't "
870 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
871 )
872 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000873 "--skip-enabling-lacros-support",
874 action="store_false",
875 dest="enable_lacros_support",
876 help="By default, deploying lacros-chrome modifies the "
877 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
878 "and (2) set the Lacros path, which can interfere with automated "
879 "testing. With this flag, part (1) will be skipped. See the "
880 "--skip-modifying-config-file flag for skipping both parts.",
881 )
882 group.add_argument(
883 "--skip-modifying-config-file",
884 action="store_false",
885 dest="modify_config_file",
886 help="When deploying lacros-chrome, do not modify the "
887 "/etc/chrome_dev.conf file. See also the "
888 "--skip-enabling-lacros-support flag.",
889 )
890
Alex Klein1699fab2022-09-08 08:46:06 -0600891 group = parser.add_argument_group("Advanced Options")
892 group.add_argument(
893 "-l",
894 "--local-pkg-path",
895 type="path",
896 help="Path to local chrome prebuilt package to deploy.",
897 )
898 group.add_argument(
899 "--sloppy",
900 action="store_true",
901 default=False,
902 help="Ignore when mandatory artifacts are missing.",
903 )
904 group.add_argument(
905 "--staging-flags",
906 default=None,
907 type=ValidateStagingFlags,
908 help=(
909 "Extra flags to control staging. Valid flags are - "
910 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
911 ),
912 )
913 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
914 group.add_argument(
915 "--strict",
916 action="store_true",
917 default=False,
918 help='Deprecated. Default behavior is "strict". Use '
919 "--sloppy to omit warnings for missing optional "
920 "files.",
921 )
922 group.add_argument(
923 "--strip-flags",
924 default=None,
925 help="Flags to call the 'strip' binutil tool with. "
926 "Overrides the default arguments.",
927 )
928 group.add_argument(
929 "--ping",
930 action="store_true",
931 default=False,
932 help="Ping the device before connection attempt.",
933 )
934 group.add_argument(
935 "--process-timeout",
936 type=int,
937 default=KILL_PROC_MAX_WAIT,
938 help="Timeout for process shutdown.",
939 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700940
Alex Klein1699fab2022-09-08 08:46:06 -0600941 group = parser.add_argument_group(
942 "Metadata Overrides (Advanced)",
943 description="Provide all of these overrides in order to remove "
944 "dependencies on metadata.json existence.",
945 )
946 group.add_argument(
947 "--target-tc",
948 action="store",
949 default=None,
950 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
951 )
952 group.add_argument(
953 "--toolchain-url",
954 action="store",
955 default=None,
956 help="Override toolchain url format pattern, e.g. "
957 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
958 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700959
Alex Klein1699fab2022-09-08 08:46:06 -0600960 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
961 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
962 parser.add_argument(
963 "--gyp-defines",
964 default=None,
965 type=ValidateStagingFlags,
966 help=argparse.SUPPRESS,
967 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700968
Alex Klein1699fab2022-09-08 08:46:06 -0600969 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600970 # when --build-dir is set. Defaults to reading from the GN_ARGS env
971 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600972 parser.add_argument(
973 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
974 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700975
Alex Klein1699fab2022-09-08 08:46:06 -0600976 # Path of an empty directory to stage chrome artifacts to. Defaults to a
977 # temporary directory that is removed when the script finishes. If the path
978 # is specified, then it will not be removed.
979 parser.add_argument(
980 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
981 )
982 # Only prepare the staging directory, and skip deploying to the device.
983 parser.add_argument(
984 "--staging-only",
985 action="store_true",
986 default=False,
987 help=argparse.SUPPRESS,
988 )
989 # Uploads the compressed staging directory to the given gs:// path URI.
990 parser.add_argument(
991 "--staging-upload",
992 type="gs_path",
993 help="GS path to upload the compressed staging files to.",
994 )
995 # Used alongside --staging-upload to upload with public-read ACL.
996 parser.add_argument(
997 "--public-read",
998 action="store_true",
999 default=False,
1000 help="GS path to upload the compressed staging files to.",
1001 )
1002 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
1003 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
1004 # fetching the SDK toolchain.
1005 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
1006 parser.add_argument(
1007 "--compress",
1008 action="store",
1009 default="auto",
1010 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001011 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001012 'is set to "auto", that disables compression if '
1013 "the target device has a gigabit ethernet port.",
1014 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001015 parser.add_argument(
1016 "--compressed-ash",
1017 action="store_true",
1018 default=False,
1019 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1020 "binary is stored on DUT in squashfs, mounted upon boot.",
1021 )
Alex Klein1699fab2022-09-08 08:46:06 -06001022 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001023
Ryan Cuie535b172012-10-19 18:25:03 -07001024
1025def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001026 """Parse args, and run environment-independent checks."""
1027 parser = _CreateParser()
1028 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001029
Alex Klein1699fab2022-09-08 08:46:06 -06001030 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1031 parser.error(
1032 "Need to specify either --gs-path, --local-pkg-path, or "
1033 "--build-dir"
1034 )
1035 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1036 parser.error(
1037 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1038 )
1039 if options.lacros:
1040 if options.dostrip and not options.board:
1041 parser.error("Please specify --board.")
1042 if options.mount_dir or options.mount:
1043 parser.error("--lacros does not support --mount or --mount-dir")
1044 if options.deploy_test_binaries:
1045 parser.error("--lacros does not support --deploy-test-binaries")
1046 if options.local_pkg_path:
1047 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001048 if options.compressed_ash:
1049 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001050 else:
1051 if not options.board and options.build_dir:
1052 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1053 if match:
1054 options.board = match.group(1)
1055 logging.info("--board is set to %s", options.board)
1056 if not options.board:
1057 parser.error("--board is required")
1058 if options.gs_path and options.local_pkg_path:
1059 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1060 if not (options.staging_only or options.device):
1061 parser.error("Need to specify --device")
1062 if options.staging_flags and not options.build_dir:
1063 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001064
Alex Klein1699fab2022-09-08 08:46:06 -06001065 if options.strict:
1066 logging.warning("--strict is deprecated.")
1067 if options.gyp_defines:
1068 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001069
Alex Klein1699fab2022-09-08 08:46:06 -06001070 if options.mount or options.mount_dir:
1071 if not options.target_dir:
1072 options.target_dir = _CHROME_DIR_MOUNT
1073 else:
1074 if not options.target_dir:
1075 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001076
Alex Klein1699fab2022-09-08 08:46:06 -06001077 if options.mount and not options.mount_dir:
1078 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001079
Alex Klein1699fab2022-09-08 08:46:06 -06001080 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001081
1082
Mike Frysingerc3061a62015-06-04 04:16:18 -04001083def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001084 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001085
Alex Klein1699fab2022-09-08 08:46:06 -06001086 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001087 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001088 """
1089 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1090 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001091
Alex Klein1699fab2022-09-08 08:46:06 -06001092 if not options.gn_args:
1093 gn_env = os.getenv("GN_ARGS")
1094 if gn_env is not None:
1095 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1096 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001097
Alex Klein1699fab2022-09-08 08:46:06 -06001098 if not options.staging_flags:
1099 use_env = os.getenv("USE")
1100 if use_env is not None:
1101 options.staging_flags = " ".join(
1102 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1103 )
1104 logging.info(
1105 "Staging flags taken from USE in environment: %s",
1106 options.staging_flags,
1107 )
Steven Bennetts60600462016-05-12 10:40:20 -07001108
Ryan Cuia56a71e2012-10-18 18:40:35 -07001109
Ryan Cui504db722013-01-22 11:48:01 -08001110def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001111 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001112
Alex Klein1699fab2022-09-08 08:46:06 -06001113 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001114 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001115 """
1116 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1117 files = gs_ctx.LS(gs_path)
1118 files = [
1119 found
1120 for found in files
1121 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1122 ]
1123 if not files:
1124 raise Exception("No chrome package found at %s" % gs_path)
1125 elif len(files) > 1:
1126 # - Users should provide us with a direct link to either a stripped or
1127 # unstripped chrome package.
1128 # - In the case of being provided with an archive directory, where both
1129 # stripped and unstripped chrome available, use the stripped chrome
1130 # package.
1131 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001132 # - Unstripped chrome pkg is
1133 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001134 files = [f for f in files if not "unstripped" in f]
1135 assert len(files) == 1
1136 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001137
Alex Klein1699fab2022-09-08 08:46:06 -06001138 filename = _UrlBaseName(files[0])
1139 logging.info("Fetching %s...", filename)
1140 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1141 chrome_path = os.path.join(tempdir, filename)
1142 assert os.path.exists(chrome_path)
1143 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001144
1145
Ryan Cuif890a3e2013-03-07 18:57:06 -08001146@contextlib.contextmanager
1147def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001148 if not options.dostrip:
1149 yield None
1150 elif options.strip_bin:
1151 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001152 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001153 sdk = cros_chrome_sdk.SDKFetcher(
1154 options.cache_dir,
1155 options.board,
1156 use_external_config=options.use_external_config,
1157 )
1158 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1159 with sdk.Prepare(
1160 components=components,
1161 target_tc=options.target_tc,
1162 toolchain_url=options.toolchain_url,
1163 ) as ctx:
1164 env_path = os.path.join(
1165 ctx.key_map[constants.CHROME_ENV_TAR].path,
1166 constants.CHROME_ENV_FILE,
1167 )
1168 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1169 strip_bin = os.path.join(
1170 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1171 "bin",
1172 os.path.basename(strip_bin),
1173 )
1174 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001175
Alex Klein1699fab2022-09-08 08:46:06 -06001176
1177def _UploadStagingDir(
1178 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1179) -> None:
1180 """Uploads the compressed staging directory.
1181
1182 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001183 options: options object.
1184 tempdir: Scratch space.
1185 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001186 """
1187 staging_tarball_path = os.path.join(
1188 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1189 )
1190 logging.info(
1191 "Compressing staging dir (%s) to (%s)",
1192 staging_dir,
1193 staging_tarball_path,
1194 )
1195 cros_build_lib.CreateTarball(
1196 staging_tarball_path,
1197 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001198 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001199 extra_env={"ZSTD_CLEVEL": "9"},
1200 )
1201 logging.info(
1202 "Uploading staging tarball (%s) into %s",
1203 staging_tarball_path,
1204 options.staging_upload,
1205 )
1206 ctx = gs.GSContext()
1207 ctx.Copy(
1208 staging_tarball_path,
1209 options.staging_upload,
1210 acl="public-read" if options.public_read else "",
1211 )
1212
1213
1214def _PrepareStagingDir(
1215 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1216):
1217 """Place the necessary files in the staging directory.
1218
Alex Klein68b270c2023-04-14 14:42:50 -06001219 The staging directory is the directory used to rsync the build artifacts
1220 over to the device. Only the necessary Chrome build artifacts are put into
1221 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001222 """
1223 if chrome_dir is None:
1224 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1225 osutils.SafeMakedirs(staging_dir)
1226 os.chmod(staging_dir, 0o755)
1227 if options.build_dir:
1228 with _StripBinContext(options) as strip_bin:
1229 strip_flags = (
1230 None
1231 if options.strip_flags is None
1232 else shlex.split(options.strip_flags)
1233 )
1234 chrome_util.StageChromeFromBuildDir(
1235 staging_dir,
1236 options.build_dir,
1237 strip_bin,
1238 sloppy=options.sloppy,
1239 gn_args=options.gn_args,
1240 staging_flags=options.staging_flags,
1241 strip_flags=strip_flags,
1242 copy_paths=copy_paths,
1243 )
1244 else:
1245 pkg_path = options.local_pkg_path
1246 if options.gs_path:
1247 pkg_path = _FetchChromePackage(
1248 options.cache_dir, tempdir, options.gs_path
1249 )
1250
1251 assert pkg_path
1252 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001253 # Extract only the ./opt/google/chrome contents, directly into the
1254 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001255 if pkg_path[-4:] == ".zip":
1256 cros_build_lib.dbg_run(
1257 [
1258 "unzip",
1259 "-X",
1260 pkg_path,
1261 _ANDROID_DIR_EXTRACT_PATH,
1262 "-d",
1263 staging_dir,
1264 ]
1265 )
1266 for filename in glob.glob(
1267 os.path.join(staging_dir, "system/chrome/*")
1268 ):
1269 shutil.move(filename, staging_dir)
1270 osutils.RmDir(
1271 os.path.join(staging_dir, "system"), ignore_missing=True
1272 )
1273 else:
1274 compression = cros_build_lib.CompressionDetectType(pkg_path)
1275 compressor = cros_build_lib.FindCompressor(compression)
1276 cros_build_lib.dbg_run(
1277 [
1278 "tar",
1279 "--strip-components",
1280 "4",
1281 "--extract",
1282 "-I",
1283 compressor,
1284 "--preserve-permissions",
1285 "--file",
1286 pkg_path,
1287 ".%s" % chrome_dir,
1288 ],
1289 cwd=staging_dir,
1290 )
1291
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001292 if options.compressed_ash:
1293 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1294 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001295 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1296 # this is in test, cut the known suffix of experimental overlays.
1297 sdk_orig_board = options.board
1298 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1299 sdk_orig_board = sdk_orig_board[
1300 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1301 ]
1302
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001303 sdk = cros_chrome_sdk.SDKFetcher(
1304 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001305 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001306 use_external_config=options.use_external_config,
1307 )
1308 with sdk.Prepare(
1309 components=[],
1310 target_tc=options.target_tc,
1311 toolchain_url=options.toolchain_url,
1312 ):
1313 cros_build_lib.dbg_run(
1314 [
1315 "mksquashfs",
1316 RAW_ASH_FILE,
1317 COMPRESSED_ASH_FILE,
1318 "-all-root",
1319 "-no-progress",
1320 "-comp",
1321 "zstd",
1322 ],
1323 cwd=staging_dir,
1324 )
1325 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1326
Alex Klein1699fab2022-09-08 08:46:06 -06001327 if options.staging_upload:
1328 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001329
Ryan Cui71aa8de2013-04-19 16:12:55 -07001330
Ryan Cui3045c5d2012-07-13 18:00:33 -07001331def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001332 options = _ParseCommandLine(argv)
1333 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001334
Alex Klein1699fab2022-09-08 08:46:06 -06001335 with osutils.TempDir(set_global=True) as tempdir:
1336 staging_dir = options.staging_dir
1337 if not staging_dir:
1338 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001339
Alex Klein1699fab2022-09-08 08:46:06 -06001340 deploy = DeployChrome(options, tempdir, staging_dir)
1341 try:
1342 deploy.Perform()
1343 except failures_lib.StepFailure as ex:
1344 raise SystemExit(str(ex).strip())
1345 deploy.Cleanup()