blob: 82123ab6fc3937482592d3e7146b76947bfaced3 [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
Hidehiko Abe26db9392023-06-22 19:25:29 +090096# This command checks if
97# "--enable-features=LacrosOnly,LacrosPrimary,LacrosSupport" is present in
Erik Chen75a2f492020-08-06 19:15:11 -070098# /etc/chrome_dev.conf. If it is not, then it is added.
99# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
100# to modify chrome_dev.conf. Either revisit this policy or find another
101# mechanism to pass configuration to ash-chrome.
102ENABLE_LACROS_VIA_CONF_COMMAND = f"""
Hidehiko Abe26db9392023-06-22 19:25:29 +0900103 if ! grep -q "^--enable-features=LacrosOnly,LacrosPrimary,LacrosSupport$" {_CONF_FILE}; then
104 echo "--enable-features=LacrosOnly,LacrosPrimary,LacrosSupport" >> {_CONF_FILE};
Erik Chen75a2f492020-08-06 19:15:11 -0700105 echo {MODIFIED_CONF_FILE};
106 fi
107"""
108
109# This command checks if "--lacros-chrome-path=" is present with the right value
110# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
111# and the new one is added.
112# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
113# to modify chrome_dev.conf. Either revisit this policy or find another
114# mechanism to pass configuration to ash-chrome.
115_SET_LACROS_PATH_VIA_CONF_COMMAND = """
116 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
117 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
118 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
119 echo %(modified_conf_file)s;
120 fi
121"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500122
Alex Klein1699fab2022-09-08 08:46:06 -0600123
Ryan Cui3045c5d2012-07-13 18:00:33 -0700124def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600125 """Return the last component of the URL."""
126 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700127
128
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700129class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600130 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800131
132
Ryan Cui7193a7e2013-04-26 14:15:19 -0700133DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600134 "DeviceInfo", ["target_dir_size", "target_fs_free"]
135)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700136
137
Alex Klein074f94f2023-06-22 10:32:06 -0600138class DeployChrome:
Alex Klein1699fab2022-09-08 08:46:06 -0600139 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 def __init__(self, options, tempdir, staging_dir):
142 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700143
Alex Klein1699fab2022-09-08 08:46:06 -0600144 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600145 options: options object.
146 tempdir: Scratch space for the class. Caller has responsibility to
147 clean it up.
148 staging_dir: Directory to stage the files to.
Alex Klein1699fab2022-09-08 08:46:06 -0600149 """
150 self.tempdir = tempdir
151 self.options = options
152 self.staging_dir = staging_dir
153 if not self.options.staging_only:
154 hostname = options.device.hostname
155 port = options.device.port
156 self.device = remote.ChromiumOSDevice(
157 hostname,
158 port=port,
159 ping=options.ping,
160 private_key=options.private_key,
161 include_dev_paths=False,
162 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000163 if self._ShouldUseCompressedAsh():
164 self.options.compressed_ash = True
165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000167
Alex Klein1699fab2022-09-08 08:46:06 -0600168 self._deployment_name = "lacros" if options.lacros else "chrome"
169 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 # Whether UI was stopped during setup.
174 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800175
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000176 def _ShouldUseCompressedAsh(self):
177 """Detects if the DUT uses compressed-ash setup."""
178 if self.options.lacros:
179 return False
180
181 return self.device.IfFileExists(COMPRESSED_ASH_PATH)
182
Alex Klein1699fab2022-09-08 08:46:06 -0600183 def _GetRemoteMountFree(self, remote_dir):
184 result = self.device.run(DF_COMMAND % remote_dir)
185 line = result.stdout.splitlines()[1]
186 value = line.split()[3]
187 multipliers = {
188 "G": 1024 * 1024 * 1024,
189 "M": 1024 * 1024,
190 "K": 1024,
191 }
192 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 def _GetRemoteDirSize(self, remote_dir):
195 result = self.device.run(
196 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
197 )
198 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700199
Alex Klein1699fab2022-09-08 08:46:06 -0600200 def _GetStagingDirSize(self):
201 result = cros_build_lib.dbg_run(
202 ["du", "-ks", self.staging_dir],
203 capture_output=True,
204 encoding="utf-8",
205 )
206 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700207
Alex Klein1699fab2022-09-08 08:46:06 -0600208 def _ChromeFileInUse(self):
209 result = self.device.run(
210 LSOF_COMMAND_CHROME % (self.options.target_dir,),
211 check=False,
212 capture_output=True,
213 )
214 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700215
Alex Klein1699fab2022-09-08 08:46:06 -0600216 def _DisableRootfsVerification(self):
217 if not self.options.force:
218 logging.error(
219 "Detected that the device has rootfs verification enabled."
220 )
221 logging.info(
222 "This script can automatically remove the rootfs "
223 "verification, which requires it to reboot the device."
224 )
225 logging.info("Make sure the device is in developer mode!")
226 logging.info("Skip this prompt by specifying --force.")
227 if not cros_build_lib.BooleanPrompt(
228 "Remove rootfs verification?", False
229 ):
230 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700231
Alex Klein1699fab2022-09-08 08:46:06 -0600232 logging.info(
233 "Removing rootfs verification from %s", self.options.device
234 )
Alex Klein68b270c2023-04-14 14:42:50 -0600235 # Running in VMs cause make_dev_ssd's firmware confidence checks to
236 # fail. Use --force to bypass the checks.
Mike Frysingerd62b06d2023-08-31 15:06:16 -0400237 # TODO(b/269266992): Switch back to a list.
238 cmd = (
239 "/usr/share/vboot/bin/make_dev_ssd.sh "
240 f"--partitions '{KERNEL_A_PARTITION} {KERNEL_B_PARTITION}' "
241 "--remove_rootfs_verification "
242 "--force"
243 )
244 self.device.run(cmd, shell=True, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700245
Mike Frysinger76f632a2023-07-19 13:39:55 -0400246 self.device.Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 # Make sure the rootfs is writable now.
249 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700250
Mike Frysinger00e30152023-09-06 15:51:12 -0400251 # Now that the machine has been rebooted, we need to kill Chrome again.
252 self._KillAshChromeIfNeeded()
253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 def _CheckUiJobStarted(self):
257 # status output is in the format:
258 # <job_name> <status> ['process' <pid>].
259 # <status> is in the format <goal>/<state>.
260 try:
261 result = self.device.run(
262 "status ui", capture_output=True, encoding="utf-8"
263 )
264 except cros_build_lib.RunCommandError as e:
265 if "Unknown job" in e.stderr:
266 return False
267 else:
268 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700271
Alex Klein1699fab2022-09-08 08:46:06 -0600272 def _KillLacrosChrome(self):
273 """This method kills lacros-chrome on the device, if it's running."""
Eliot Courtneyaf474342023-07-19 14:20:25 +0900274 # Mark the lacros chrome binary as not executable, so if keep-alive is
275 # enabled ash chrome can't restart lacros chrome. This prevents rsync
276 # from failing if the file is still in use (being executed by ash
277 # chrome). Note that this will cause ash chrome to continuously attempt
278 # to start lacros and fail, although it doesn't seem to cause issues.
279 if self.options.skip_restart_ui:
280 self.device.run(
281 ["chmod", "-x", f"{self.options.target_dir}/chrome"],
282 check=False,
283 )
Alex Klein1699fab2022-09-08 08:46:06 -0600284 self.device.run(
285 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
286 check=False,
287 )
Erik Chen75a2f492020-08-06 19:15:11 -0700288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 def _ResetLacrosChrome(self):
290 """Reset Lacros to fresh state by deleting user data dir."""
291 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000292
Alex Klein1699fab2022-09-08 08:46:06 -0600293 def _KillAshChromeIfNeeded(self):
294 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700295
Alex Klein68b270c2023-04-14 14:42:50 -0600296 This method calls 'stop ui', and then also manually pkills both
297 ash-chrome and the session manager.
Alex Klein1699fab2022-09-08 08:46:06 -0600298 """
299 if self._CheckUiJobStarted():
300 logging.info("Shutting down Chrome...")
301 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700302
Alex Klein1699fab2022-09-08 08:46:06 -0600303 # Developers sometimes run session_manager manually, in which case we'll
304 # need to help shut the chrome processes down.
305 try:
306 with timeout_util.Timeout(self.options.process_timeout):
307 while self._ChromeFileInUse():
308 logging.warning(
309 "The chrome binary on the device is in use."
310 )
311 logging.warning(
312 "Killing chrome and session_manager processes...\n"
313 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700314
Alex Klein1699fab2022-09-08 08:46:06 -0600315 self.device.run(
316 "pkill 'chrome|session_manager'", check=False
317 )
318 # Wait for processes to actually terminate
319 time.sleep(POST_KILL_WAIT)
320 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000321 if self.options.compressed_ash:
322 result = self.device.run(
323 ["umount", RAW_ASH_PATH],
324 check=False,
325 capture_output=True,
326 )
327 if result.returncode and not (
328 result.returncode == 32
329 and "not mounted" in result.stderr
330 ):
331 raise DeployFailure(
332 "Could not unmount compressed ash. "
333 f"Error Code: {result.returncode}, "
334 f"Error Message: {result.stderr}"
335 )
Alex Klein1699fab2022-09-08 08:46:06 -0600336 except timeout_util.TimeoutError:
337 msg = (
338 "Could not kill processes after %s seconds. Please exit any "
339 "running chrome processes and try again."
340 % self.options.process_timeout
341 )
342 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700343
Alex Klein1699fab2022-09-08 08:46:06 -0600344 def _MountRootfsAsWritable(self, check=False):
345 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700346
Alex Klein1699fab2022-09-08 08:46:06 -0600347 If the command fails and the root dir is not writable then this function
348 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700349
Alex Klein1699fab2022-09-08 08:46:06 -0600350 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600351 check: See remote.RemoteAccess.RemoteSh for details.
Alex Klein1699fab2022-09-08 08:46:06 -0600352 """
353 # TODO: Should migrate to use the remount functions in remote_access.
Jonathan Fane9430632023-08-21 14:36:28 -0700354 result = None
355 try:
356 result = self.device.run(
357 MOUNT_RW_COMMAND,
358 capture_output=True,
359 check=check,
360 encoding="utf-8",
361 )
362 except cros_build_lib.RunCommandError as e:
363 # Remounting could fail with the following error message:
364 # "cannot remount {dev} read-write, is write-protected"
365 logging.warning("Mounting root as writable failed: %s", e.stderr)
366 if re.search(
367 r"cannot\sremount\s\S*\sread-write,\sis\swrite-protected",
368 e.stderr,
369 ):
370 # Dump debug info to help diagnose b/293204438.
371 findmnt_result = self.device.run(
372 ["findmnt"], capture_output=True
373 )
374 logging.info("findmnt: %s", findmnt_result.stdout)
375 dmesg_result = self.device.run(["dmesg"], capture_output=True)
376 logging.info("dmesg: %s", dmesg_result.stdout)
377
378 # The device where root is mounting to could be read only.
379 # We will set the device to writable and remount again.
380 rootdev_result = self.device.run(
381 ["rootdev"],
382 capture_output=True,
383 check=check,
384 encoding="utf-8",
385 )
386 if rootdev_result.returncode == 0:
387 device = rootdev_result.stdout.strip("\n")
388 if device:
389 self.device.run(
390 ["blockdev", "--setrw", device],
391 encoding="utf-8",
392 check=check,
393 )
394 result = self.device.run(
395 MOUNT_RW_COMMAND,
396 check=check,
397 capture_output=True,
398 encoding="utf-8",
399 )
400 else:
401 if check:
402 raise e
403
404 if result and result.returncode and not self.device.IsDirWritable("/"):
Alex Klein1699fab2022-09-08 08:46:06 -0600405 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700406
Alex Klein1699fab2022-09-08 08:46:06 -0600407 def _EnsureTargetDir(self):
408 """Ensures that the target directory exists on the remote device."""
409 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600410 # Any valid /opt directory should already exist so avoid the remote
411 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600412 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
413 return
Mike Frysinger445f6bf2023-06-27 11:43:33 -0400414 self.device.mkdir(target_dir, mode=0o775)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800415
Alex Klein1699fab2022-09-08 08:46:06 -0600416 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600417 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600418 steps = [
419 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
420 functools.partial(
421 self._GetRemoteMountFree, self.options.target_dir
422 ),
423 ]
424 return_values = parallel.RunParallelSteps(steps, return_values=True)
425 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700426
Alex Klein1699fab2022-09-08 08:46:06 -0600427 def _CheckDeviceFreeSpace(self, device_info):
428 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700429
Alex Klein1699fab2022-09-08 08:46:06 -0600430 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600431 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600432 """
433 effective_free = (
434 device_info.target_dir_size + device_info.target_fs_free
435 )
436 staging_size = self._GetStagingDirSize()
437 if effective_free < staging_size:
438 raise DeployFailure(
439 "Not enough free space on the device. Required: %s MiB, "
440 "actual: %s MiB."
441 % (staging_size // 1024, effective_free // 1024)
442 )
443 if device_info.target_fs_free < (100 * 1024):
444 logging.warning(
445 "The device has less than 100MB free. deploy_chrome may "
446 "hang during the transfer."
447 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700448
Alex Klein1699fab2022-09-08 08:46:06 -0600449 def _ShouldUseCompression(self):
450 """Checks if compression should be used for rsync."""
451 if self.options.compress == "always":
452 return True
453 elif self.options.compress == "never":
454 return False
455 elif self.options.compress == "auto":
456 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 def _Deploy(self):
459 logging.info(
460 "Copying %s to %s on device...",
461 self._deployment_name,
462 self.options.target_dir,
463 )
464 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
465 # This does not work for deploy.
466 if not self.device.HasRsync():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200467 # This assumes that rsync is part of the bootstrap package. In the
468 # future, this might change and we'll have to install it separately.
469 if not cros_build_lib.BooleanPrompt(
470 "Run dev_install on the device to install rsync?", True
471 ):
472 raise DeployFailure("rsync is not found on the device.")
473 self.device.BootstrapDevTools()
474 if not self.device.HasRsync():
475 raise DeployFailure("Failed to install rsync")
476
Eliot Courtneyaf474342023-07-19 14:20:25 +0900477 try:
478 staging_dir = os.path.abspath(self.staging_dir)
479 staging_chrome = os.path.join(staging_dir, "chrome")
480
481 if (
482 self.options.lacros
483 and self.options.skip_restart_ui
484 and os.path.exists(staging_chrome)
485 ):
486 # Make the chrome binary not executable before deploying to
487 # prevent ash chrome from starting chrome before the rsync has
488 # finished.
489 os.chmod(staging_chrome, 0o644)
490
491 self.device.CopyToDevice(
492 f"{staging_dir}/",
493 self.options.target_dir,
494 mode="rsync",
495 inplace=True,
496 compress=self._ShouldUseCompression(),
497 debug_level=logging.INFO,
498 verbose=self.options.verbose,
499 )
500 finally:
501 if self.options.lacros and self.options.skip_restart_ui:
502 self.device.run(
503 ["chmod", "+x", f"{self.options.target_dir}/chrome"],
504 check=False,
505 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700506
Alex Klein68b270c2023-04-14 14:42:50 -0600507 # Set the security context on the default Chrome dir if that's where
508 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600509 if (
510 not self.options.lacros
511 and self.device.IsSELinuxAvailable()
512 and (
513 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
514 )
515 ):
516 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800517
Alex Klein1699fab2022-09-08 08:46:06 -0600518 for p in self.copy_paths:
519 if p.mode:
520 # Set mode if necessary.
521 self.device.run(
522 "chmod %o %s/%s"
523 % (
524 p.mode,
525 self.options.target_dir,
526 p.src if not p.dest else p.dest,
527 )
528 )
Steve Funge984a532013-11-25 17:09:25 -0800529
Alex Klein1699fab2022-09-08 08:46:06 -0600530 if self.options.lacros:
531 self.device.run(
532 ["chown", "-R", "chronos:chronos", self.options.target_dir]
533 )
Erik Chen75a2f492020-08-06 19:15:11 -0700534
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000535 if self.options.compressed_ash:
536 self.device.run(["start", COMPRESSED_ASH_SERVICE])
537
Alex Klein68b270c2023-04-14 14:42:50 -0600538 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
539 # won't pick up major changes (bus type, logging, etc.), but all we care
540 # about is getting the latest policy from /opt/google/chrome/dbus so
541 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600542 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600543
Erik Chen75a2f492020-08-06 19:15:11 -0700544 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700545 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600546 logging.info("Starting UI...")
547 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800548
Joel Hockey764728e2023-03-14 17:10:04 -0700549 if self.options.unlock_password:
550 logging.info("Unlocking...")
551
552 @retry_util.WithRetry(max_retry=5, sleep=1)
553 def WaitForUnlockScreen():
554 if self._GetLastLogin() == last_login:
555 raise DeployFailure("Unlock screen not shown")
556
557 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700558 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700559 self.device.run(
560 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
561 )
562
563 def _GetLastLogin(self):
564 """Returns last login time"""
565 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
566
Alex Klein1699fab2022-09-08 08:46:06 -0600567 def _DeployTestBinaries(self):
568 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700569
Alex Klein68b270c2023-04-14 14:42:50 -0600570 There could be several binaries located in the local build dir, so
571 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
572 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600573 """
574 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
575 if r.returncode != 0:
576 raise DeployFailure(
577 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
578 )
579 binaries_to_copy = []
580 for f in r.stdout.splitlines():
581 binaries_to_copy.append(
582 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
583 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700584
Alex Klein1699fab2022-09-08 08:46:06 -0600585 staging_dir = os.path.join(
586 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
587 )
588 _PrepareStagingDir(
589 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
590 )
591 # Deploying can occasionally run into issues with rsync getting a broken
592 # pipe, so retry several times. See crbug.com/1141618 for more
593 # information.
594 retry_util.RetryException(
595 None,
596 3,
597 self.device.CopyToDevice,
598 staging_dir,
599 os.path.dirname(_CHROME_TEST_BIN_DIR),
600 mode="rsync",
601 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700602
Alex Klein1699fab2022-09-08 08:46:06 -0600603 def _CheckBoard(self):
604 """Check that the Chrome build is targeted for the device board."""
605 if self.options.board == self.device.board:
606 return
607 logging.warning(
608 "Device board is %s whereas target board is %s.",
609 self.device.board,
610 self.options.board,
611 )
612 if self.options.force:
613 return
614 if not cros_build_lib.BooleanPrompt(
615 "Continue despite board mismatch?", False
616 ):
617 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800618
Alex Klein1699fab2022-09-08 08:46:06 -0600619 def _CheckDeployType(self):
620 if self.options.build_dir:
621
622 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600623 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600624 return os.path.exists(
625 os.path.join(self.options.build_dir, filename)
626 )
627
628 # In the future, lacros-chrome and ash-chrome will likely be named
629 # something other than 'chrome' to avoid confusion.
630 # Handle non-Chrome deployments.
631 if not BinaryExists("chrome"):
632 if BinaryExists("app_shell"):
633 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
634
635 def _PrepareStagingDir(self):
636 _PrepareStagingDir(
637 self.options,
638 self.tempdir,
639 self.staging_dir,
640 self.copy_paths,
641 self.chrome_dir,
642 )
643
644 def _MountTarget(self):
645 logging.info("Mounting Chrome...")
646
647 # Create directory if does not exist.
648 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
649 try:
650 # Umount the existing mount on mount_dir if present first.
651 self.device.run(
652 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
653 )
654 except cros_build_lib.RunCommandError as e:
655 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600656 # If there is a failure, check if some process is using the
657 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600658 result = self.device.run(
659 LSOF_COMMAND % (self.options.mount_dir,),
660 check=False,
661 capture_output=True,
662 encoding="utf-8",
663 )
664 logging.error("lsof %s -->", self.options.mount_dir)
665 logging.error(result.stdout)
666 raise e
667
668 self.device.run(
669 _BIND_TO_FINAL_DIR_CMD
670 % (self.options.target_dir, self.options.mount_dir)
671 )
672
673 # Chrome needs partition to have exec and suid flags set
674 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
675
676 def Cleanup(self):
677 """Clean up RemoteDevice."""
678 if not self.options.staging_only:
679 self.device.Cleanup()
680
681 def Perform(self):
682 self._CheckDeployType()
683
684 # If requested, just do the staging step.
685 if self.options.staging_only:
686 self._PrepareStagingDir()
687 return 0
688
Alex Klein68b270c2023-04-14 14:42:50 -0600689 # Check that the build matches the device. Lacros-chrome skips this
690 # check as it's currently board independent. This means that it's
691 # possible to deploy a build of lacros-chrome with a mismatched
692 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600693 if not self.options.lacros:
694 self._CheckBoard()
695
696 # Ensure that the target directory exists before running parallel steps.
697 self._EnsureTargetDir()
698
699 logging.info("Preparing device")
700 steps = [
701 self._GetDeviceInfo,
Alex Klein1699fab2022-09-08 08:46:06 -0600702 self._MountRootfsAsWritable,
703 self._PrepareStagingDir,
704 ]
705
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900706 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600707 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600708 steps.append(self._KillLacrosChrome)
709 if self.options.reset_lacros:
710 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900711 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600712 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900713 config_modified = self._ModifyConfigFileIfNeededForLacros()
714 if config_modified and not restart_ui:
715 logging.warning(
716 "Config file modified but skipping restart_ui "
717 "due to option --skip-restart-ui. Config file "
718 "update is not reflected."
719 )
Alex Klein1699fab2022-09-08 08:46:06 -0600720
721 if restart_ui:
722 steps.append(self._KillAshChromeIfNeeded)
723 self._stopped_ui = True
724
725 ret = parallel.RunParallelSteps(
726 steps, halt_on_error=True, return_values=True
727 )
728 self._CheckDeviceFreeSpace(ret[0])
729
730 # If the root dir is not writable, try disabling rootfs verification.
731 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600732 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600733 if self._root_dir_is_still_readonly.is_set():
734 if self.options.noremove_rootfs_verification:
735 logging.warning("Skipping disable rootfs verification.")
736 elif not self._DisableRootfsVerification():
737 logging.warning("Failed to disable rootfs verification.")
738
Alex Klein68b270c2023-04-14 14:42:50 -0600739 # If the target dir is still not writable (i.e. the user opted out
740 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600741 if not self.device.IsDirWritable(self.options.target_dir):
742 if self.options.startui and self._stopped_ui:
743 logging.info("Restarting Chrome...")
744 self.device.run("start ui")
745 raise DeployFailure(
746 "Target location is not writable. Aborting."
747 )
748
749 if self.options.mount_dir is not None:
750 self._MountTarget()
751
752 # Actually deploy Chrome to the device.
753 self._Deploy()
754 if self.options.deploy_test_binaries:
755 self._DeployTestBinaries()
756
757 def _ModifyConfigFileIfNeededForLacros(self):
758 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
759
760 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600761 True if the file is modified, and the return value is usually used
762 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600763 """
764 assert (
765 self.options.lacros
766 ), "Only deploying lacros-chrome needs to modify the config file."
767 # Update /etc/chrome_dev.conf to include appropriate flags.
768 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000769 if self.options.enable_lacros_support:
770 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
771 if result.stdout.strip() == MODIFIED_CONF_FILE:
772 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600773 result = self.device.run(
774 _SET_LACROS_PATH_VIA_CONF_COMMAND
775 % {
776 "conf_file": _CONF_FILE,
777 "lacros_path": self.options.target_dir,
778 "modified_conf_file": MODIFIED_CONF_FILE,
779 },
780 shell=True,
781 )
782 if result.stdout.strip() == MODIFIED_CONF_FILE:
783 modified = True
784
785 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800786
787
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700788def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600789 """Convert formatted string to dictionary."""
790 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700791
792
Steven Bennetts368c3e52016-09-23 13:05:21 -0700793def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600794 """Convert GN_ARGS-formatted string to dictionary."""
795 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700796
797
Ryan Cuie535b172012-10-19 18:25:03 -0700798def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600799 """Create our custom parser."""
800 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700801
Alex Klein1699fab2022-09-08 08:46:06 -0600802 # TODO(rcui): Have this use the UI-V2 format of having source and target
803 # device be specified as positional arguments.
804 parser.add_argument(
805 "--force",
806 action="store_true",
807 default=False,
808 help="Skip all prompts (such as the prompt for disabling "
809 "of rootfs verification). This may result in the "
810 "target machine being rebooted.",
811 )
812 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
813 parser.add_argument(
814 "--board",
815 default=sdk_board_env,
816 help="The board the Chrome build is targeted for. When "
817 "in a 'cros chrome-sdk' shell, defaults to the SDK "
818 "board.",
819 )
820 parser.add_argument(
821 "--build-dir",
822 type="path",
823 help="The directory with Chrome build artifacts to "
824 "deploy from. Typically of format "
825 "<chrome_root>/out/Debug. When this option is used, "
826 "the GN_ARGS environment variable must be set.",
827 )
828 parser.add_argument(
829 "--target-dir",
830 type="path",
831 default=None,
832 help="Target directory on device to deploy Chrome into.",
833 )
834 parser.add_argument(
835 "-g",
836 "--gs-path",
837 type="gs_path",
838 help="GS path that contains the chrome to deploy.",
839 )
840 parser.add_argument(
841 "--private-key",
842 type="path",
843 default=None,
844 help="An ssh private key to use when deploying to " "a CrOS device.",
845 )
846 parser.add_argument(
847 "--nostartui",
848 action="store_false",
849 dest="startui",
850 default=True,
851 help="Don't restart the ui daemon after deployment.",
852 )
853 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700854 "--unlock-password",
855 default=None,
856 help="Password to use to unlock after deployment and restart.",
857 )
858 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600859 "--nostrip",
860 action="store_false",
861 dest="dostrip",
862 default=True,
863 help="Don't strip binaries during deployment. Warning: "
864 "the resulting binaries will be very large!",
865 )
866 parser.add_argument(
867 "-d",
868 "--device",
869 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
870 help="Device hostname or IP in the format hostname[:port].",
871 )
872 parser.add_argument(
873 "--mount-dir",
874 type="path",
875 default=None,
876 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000877 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600878 "Any existing mount on this directory will be "
879 "umounted first.",
880 )
881 parser.add_argument(
882 "--mount",
883 action="store_true",
884 default=False,
885 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000886 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600887 "Any existing mount on this directory will be "
888 "umounted first.",
889 )
890 parser.add_argument(
891 "--noremove-rootfs-verification",
892 action="store_true",
893 default=False,
894 help="Never remove rootfs verification.",
895 )
896 parser.add_argument(
897 "--deploy-test-binaries",
898 action="store_true",
899 default=False,
900 help="Also deploy any test binaries to %s. Useful for "
901 "running any Tast tests that execute these "
902 "binaries." % _CHROME_TEST_BIN_DIR,
903 )
904 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600905 "--use-external-config",
906 action="store_true",
907 help="When identifying the configuration for a board, "
908 "force usage of the external configuration if both "
909 "internal and external are available. This only "
910 "has an effect when stripping Chrome, i.e. when "
911 "--nostrip is not passed in.",
912 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700913
Georg Neis9b1ff192022-09-14 08:07:22 +0000914 group = parser.add_argument_group("Lacros Options")
915 group.add_argument(
916 "--lacros",
917 action="store_true",
918 default=False,
919 help="Deploys lacros-chrome rather than ash-chrome.",
920 )
921 group.add_argument(
922 "--reset-lacros",
923 action="store_true",
924 default=False,
925 help="Reset Lacros by deleting Lacros user data dir if it exists.",
926 )
927 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900928 "--skip-restart-ui",
929 action="store_true",
930 default=False,
931 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
932 "that this flag may cause ETXTBSY error on rsync, and also won't "
933 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
934 )
935 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000936 "--skip-enabling-lacros-support",
937 action="store_false",
938 dest="enable_lacros_support",
939 help="By default, deploying lacros-chrome modifies the "
940 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
941 "and (2) set the Lacros path, which can interfere with automated "
942 "testing. With this flag, part (1) will be skipped. See the "
943 "--skip-modifying-config-file flag for skipping both parts.",
944 )
945 group.add_argument(
946 "--skip-modifying-config-file",
947 action="store_false",
948 dest="modify_config_file",
949 help="When deploying lacros-chrome, do not modify the "
950 "/etc/chrome_dev.conf file. See also the "
951 "--skip-enabling-lacros-support flag.",
952 )
953
Alex Klein1699fab2022-09-08 08:46:06 -0600954 group = parser.add_argument_group("Advanced Options")
955 group.add_argument(
956 "-l",
957 "--local-pkg-path",
958 type="path",
959 help="Path to local chrome prebuilt package to deploy.",
960 )
961 group.add_argument(
962 "--sloppy",
963 action="store_true",
964 default=False,
965 help="Ignore when mandatory artifacts are missing.",
966 )
967 group.add_argument(
968 "--staging-flags",
969 default=None,
970 type=ValidateStagingFlags,
971 help=(
972 "Extra flags to control staging. Valid flags are - "
973 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
974 ),
975 )
976 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
977 group.add_argument(
978 "--strict",
979 action="store_true",
980 default=False,
981 help='Deprecated. Default behavior is "strict". Use '
982 "--sloppy to omit warnings for missing optional "
983 "files.",
984 )
985 group.add_argument(
986 "--strip-flags",
987 default=None,
988 help="Flags to call the 'strip' binutil tool with. "
989 "Overrides the default arguments.",
990 )
991 group.add_argument(
992 "--ping",
993 action="store_true",
994 default=False,
995 help="Ping the device before connection attempt.",
996 )
997 group.add_argument(
998 "--process-timeout",
999 type=int,
1000 default=KILL_PROC_MAX_WAIT,
1001 help="Timeout for process shutdown.",
1002 )
Ryan Cuia56a71e2012-10-18 18:40:35 -07001003
Alex Klein1699fab2022-09-08 08:46:06 -06001004 group = parser.add_argument_group(
1005 "Metadata Overrides (Advanced)",
1006 description="Provide all of these overrides in order to remove "
1007 "dependencies on metadata.json existence.",
1008 )
1009 group.add_argument(
1010 "--target-tc",
1011 action="store",
1012 default=None,
1013 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
1014 )
1015 group.add_argument(
1016 "--toolchain-url",
1017 action="store",
1018 default=None,
1019 help="Override toolchain url format pattern, e.g. "
1020 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
1021 )
Aviv Keshet1c986f32014-04-24 13:20:49 -07001022
Alex Klein1699fab2022-09-08 08:46:06 -06001023 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
1024 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
1025 parser.add_argument(
1026 "--gyp-defines",
1027 default=None,
1028 type=ValidateStagingFlags,
1029 help=argparse.SUPPRESS,
1030 )
Steven Bennetts368c3e52016-09-23 13:05:21 -07001031
Alex Klein1699fab2022-09-08 08:46:06 -06001032 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -06001033 # when --build-dir is set. Defaults to reading from the GN_ARGS env
1034 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -06001035 parser.add_argument(
1036 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
1037 )
Steven Bennetts368c3e52016-09-23 13:05:21 -07001038
Alex Klein1699fab2022-09-08 08:46:06 -06001039 # Path of an empty directory to stage chrome artifacts to. Defaults to a
1040 # temporary directory that is removed when the script finishes. If the path
1041 # is specified, then it will not be removed.
1042 parser.add_argument(
1043 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
1044 )
1045 # Only prepare the staging directory, and skip deploying to the device.
1046 parser.add_argument(
1047 "--staging-only",
1048 action="store_true",
1049 default=False,
1050 help=argparse.SUPPRESS,
1051 )
1052 # Uploads the compressed staging directory to the given gs:// path URI.
1053 parser.add_argument(
1054 "--staging-upload",
1055 type="gs_path",
1056 help="GS path to upload the compressed staging files to.",
1057 )
1058 # Used alongside --staging-upload to upload with public-read ACL.
1059 parser.add_argument(
1060 "--public-read",
1061 action="store_true",
1062 default=False,
1063 help="GS path to upload the compressed staging files to.",
1064 )
1065 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
1066 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
1067 # fetching the SDK toolchain.
1068 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
1069 parser.add_argument(
1070 "--compress",
1071 action="store",
1072 default="auto",
1073 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001074 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001075 'is set to "auto", that disables compression if '
1076 "the target device has a gigabit ethernet port.",
1077 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001078 parser.add_argument(
1079 "--compressed-ash",
1080 action="store_true",
1081 default=False,
1082 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1083 "binary is stored on DUT in squashfs, mounted upon boot.",
1084 )
Alex Klein1699fab2022-09-08 08:46:06 -06001085 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001086
Ryan Cuie535b172012-10-19 18:25:03 -07001087
1088def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001089 """Parse args, and run environment-independent checks."""
1090 parser = _CreateParser()
1091 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1094 parser.error(
1095 "Need to specify either --gs-path, --local-pkg-path, or "
1096 "--build-dir"
1097 )
1098 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1099 parser.error(
1100 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1101 )
1102 if options.lacros:
1103 if options.dostrip and not options.board:
1104 parser.error("Please specify --board.")
1105 if options.mount_dir or options.mount:
1106 parser.error("--lacros does not support --mount or --mount-dir")
1107 if options.deploy_test_binaries:
1108 parser.error("--lacros does not support --deploy-test-binaries")
1109 if options.local_pkg_path:
1110 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001111 if options.compressed_ash:
1112 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001113 else:
1114 if not options.board and options.build_dir:
1115 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1116 if match:
1117 options.board = match.group(1)
1118 logging.info("--board is set to %s", options.board)
1119 if not options.board:
1120 parser.error("--board is required")
1121 if options.gs_path and options.local_pkg_path:
1122 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1123 if not (options.staging_only or options.device):
1124 parser.error("Need to specify --device")
1125 if options.staging_flags and not options.build_dir:
1126 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001127
Alex Klein1699fab2022-09-08 08:46:06 -06001128 if options.strict:
1129 logging.warning("--strict is deprecated.")
1130 if options.gyp_defines:
1131 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001132
Alex Klein1699fab2022-09-08 08:46:06 -06001133 if options.mount or options.mount_dir:
1134 if not options.target_dir:
1135 options.target_dir = _CHROME_DIR_MOUNT
1136 else:
1137 if not options.target_dir:
1138 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001139
Alex Klein1699fab2022-09-08 08:46:06 -06001140 if options.mount and not options.mount_dir:
1141 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001142
Alex Klein1699fab2022-09-08 08:46:06 -06001143 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001144
1145
Mike Frysingerc3061a62015-06-04 04:16:18 -04001146def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001147 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001148
Alex Klein1699fab2022-09-08 08:46:06 -06001149 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001150 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001151 """
1152 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1153 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001154
Alex Klein1699fab2022-09-08 08:46:06 -06001155 if not options.gn_args:
1156 gn_env = os.getenv("GN_ARGS")
1157 if gn_env is not None:
1158 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1159 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001160
Alex Klein1699fab2022-09-08 08:46:06 -06001161 if not options.staging_flags:
1162 use_env = os.getenv("USE")
1163 if use_env is not None:
1164 options.staging_flags = " ".join(
1165 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1166 )
1167 logging.info(
1168 "Staging flags taken from USE in environment: %s",
1169 options.staging_flags,
1170 )
Steven Bennetts60600462016-05-12 10:40:20 -07001171
Ryan Cuia56a71e2012-10-18 18:40:35 -07001172
Ryan Cui504db722013-01-22 11:48:01 -08001173def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001174 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001175
Alex Klein1699fab2022-09-08 08:46:06 -06001176 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001177 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001178 """
1179 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1180 files = gs_ctx.LS(gs_path)
1181 files = [
1182 found
1183 for found in files
1184 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1185 ]
1186 if not files:
1187 raise Exception("No chrome package found at %s" % gs_path)
1188 elif len(files) > 1:
1189 # - Users should provide us with a direct link to either a stripped or
1190 # unstripped chrome package.
1191 # - In the case of being provided with an archive directory, where both
1192 # stripped and unstripped chrome available, use the stripped chrome
1193 # package.
1194 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001195 # - Unstripped chrome pkg is
1196 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001197 files = [f for f in files if not "unstripped" in f]
1198 assert len(files) == 1
1199 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001200
Alex Klein1699fab2022-09-08 08:46:06 -06001201 filename = _UrlBaseName(files[0])
1202 logging.info("Fetching %s...", filename)
1203 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1204 chrome_path = os.path.join(tempdir, filename)
1205 assert os.path.exists(chrome_path)
1206 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001207
1208
Ryan Cuif890a3e2013-03-07 18:57:06 -08001209@contextlib.contextmanager
1210def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001211 if not options.dostrip:
1212 yield None
1213 elif options.strip_bin:
1214 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001215 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001216 sdk = cros_chrome_sdk.SDKFetcher(
1217 options.cache_dir,
1218 options.board,
1219 use_external_config=options.use_external_config,
1220 )
1221 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1222 with sdk.Prepare(
1223 components=components,
1224 target_tc=options.target_tc,
1225 toolchain_url=options.toolchain_url,
1226 ) as ctx:
1227 env_path = os.path.join(
1228 ctx.key_map[constants.CHROME_ENV_TAR].path,
1229 constants.CHROME_ENV_FILE,
1230 )
1231 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1232 strip_bin = os.path.join(
1233 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1234 "bin",
1235 os.path.basename(strip_bin),
1236 )
1237 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001238
Alex Klein1699fab2022-09-08 08:46:06 -06001239
1240def _UploadStagingDir(
1241 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1242) -> None:
1243 """Uploads the compressed staging directory.
1244
1245 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001246 options: options object.
1247 tempdir: Scratch space.
1248 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001249 """
1250 staging_tarball_path = os.path.join(
1251 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1252 )
1253 logging.info(
1254 "Compressing staging dir (%s) to (%s)",
1255 staging_dir,
1256 staging_tarball_path,
1257 )
1258 cros_build_lib.CreateTarball(
1259 staging_tarball_path,
1260 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001261 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001262 extra_env={"ZSTD_CLEVEL": "9"},
1263 )
1264 logging.info(
1265 "Uploading staging tarball (%s) into %s",
1266 staging_tarball_path,
1267 options.staging_upload,
1268 )
1269 ctx = gs.GSContext()
1270 ctx.Copy(
1271 staging_tarball_path,
1272 options.staging_upload,
1273 acl="public-read" if options.public_read else "",
1274 )
1275
1276
1277def _PrepareStagingDir(
1278 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1279):
1280 """Place the necessary files in the staging directory.
1281
Alex Klein68b270c2023-04-14 14:42:50 -06001282 The staging directory is the directory used to rsync the build artifacts
1283 over to the device. Only the necessary Chrome build artifacts are put into
1284 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001285 """
1286 if chrome_dir is None:
1287 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1288 osutils.SafeMakedirs(staging_dir)
1289 os.chmod(staging_dir, 0o755)
1290 if options.build_dir:
1291 with _StripBinContext(options) as strip_bin:
1292 strip_flags = (
1293 None
1294 if options.strip_flags is None
1295 else shlex.split(options.strip_flags)
1296 )
1297 chrome_util.StageChromeFromBuildDir(
1298 staging_dir,
1299 options.build_dir,
1300 strip_bin,
1301 sloppy=options.sloppy,
1302 gn_args=options.gn_args,
1303 staging_flags=options.staging_flags,
1304 strip_flags=strip_flags,
1305 copy_paths=copy_paths,
1306 )
1307 else:
1308 pkg_path = options.local_pkg_path
1309 if options.gs_path:
1310 pkg_path = _FetchChromePackage(
1311 options.cache_dir, tempdir, options.gs_path
1312 )
1313
1314 assert pkg_path
1315 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001316 # Extract only the ./opt/google/chrome contents, directly into the
1317 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001318 if pkg_path[-4:] == ".zip":
1319 cros_build_lib.dbg_run(
1320 [
1321 "unzip",
1322 "-X",
1323 pkg_path,
1324 _ANDROID_DIR_EXTRACT_PATH,
1325 "-d",
1326 staging_dir,
1327 ]
1328 )
1329 for filename in glob.glob(
1330 os.path.join(staging_dir, "system/chrome/*")
1331 ):
1332 shutil.move(filename, staging_dir)
1333 osutils.RmDir(
1334 os.path.join(staging_dir, "system"), ignore_missing=True
1335 )
1336 else:
1337 compression = cros_build_lib.CompressionDetectType(pkg_path)
1338 compressor = cros_build_lib.FindCompressor(compression)
Cindy Lin0b043c42023-06-23 20:59:26 +00001339 if compression == cros_build_lib.CompressionType.ZSTD:
1340 compressor += " -f"
Alex Klein1699fab2022-09-08 08:46:06 -06001341 cros_build_lib.dbg_run(
1342 [
1343 "tar",
1344 "--strip-components",
1345 "4",
1346 "--extract",
1347 "-I",
1348 compressor,
1349 "--preserve-permissions",
1350 "--file",
1351 pkg_path,
1352 ".%s" % chrome_dir,
1353 ],
1354 cwd=staging_dir,
1355 )
1356
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001357 if options.compressed_ash:
1358 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1359 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001360 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1361 # this is in test, cut the known suffix of experimental overlays.
1362 sdk_orig_board = options.board
1363 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1364 sdk_orig_board = sdk_orig_board[
1365 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1366 ]
1367
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001368 sdk = cros_chrome_sdk.SDKFetcher(
1369 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001370 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001371 use_external_config=options.use_external_config,
1372 )
1373 with sdk.Prepare(
1374 components=[],
1375 target_tc=options.target_tc,
1376 toolchain_url=options.toolchain_url,
1377 ):
1378 cros_build_lib.dbg_run(
1379 [
1380 "mksquashfs",
1381 RAW_ASH_FILE,
1382 COMPRESSED_ASH_FILE,
1383 "-all-root",
1384 "-no-progress",
1385 "-comp",
1386 "zstd",
1387 ],
1388 cwd=staging_dir,
1389 )
1390 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1391
Alex Klein1699fab2022-09-08 08:46:06 -06001392 if options.staging_upload:
1393 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001394
Ryan Cui71aa8de2013-04-19 16:12:55 -07001395
Ryan Cui3045c5d2012-07-13 18:00:33 -07001396def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001397 options = _ParseCommandLine(argv)
1398 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001399
Alex Klein1699fab2022-09-08 08:46:06 -06001400 with osutils.TempDir(set_global=True) as tempdir:
1401 staging_dir = options.staging_dir
1402 if not staging_dir:
1403 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001404
Alex Klein1699fab2022-09-08 08:46:06 -06001405 deploy = DeployChrome(options, tempdir, staging_dir)
1406 try:
1407 deploy.Perform()
1408 except failures_lib.StepFailure as ex:
1409 raise SystemExit(str(ex).strip())
1410 deploy.Cleanup()