blob: 37a09f92a580999e0b0aa61984bb29d16cca67c7 [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.
354 result = self.device.run(
355 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
356 )
357 if result.returncode and not self.device.IsDirWritable("/"):
358 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700359
Alex Klein1699fab2022-09-08 08:46:06 -0600360 def _EnsureTargetDir(self):
361 """Ensures that the target directory exists on the remote device."""
362 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600363 # Any valid /opt directory should already exist so avoid the remote
364 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600365 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
366 return
Mike Frysinger445f6bf2023-06-27 11:43:33 -0400367 self.device.mkdir(target_dir, mode=0o775)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800368
Alex Klein1699fab2022-09-08 08:46:06 -0600369 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600370 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600371 steps = [
372 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
373 functools.partial(
374 self._GetRemoteMountFree, self.options.target_dir
375 ),
376 ]
377 return_values = parallel.RunParallelSteps(steps, return_values=True)
378 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700379
Alex Klein1699fab2022-09-08 08:46:06 -0600380 def _CheckDeviceFreeSpace(self, device_info):
381 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700382
Alex Klein1699fab2022-09-08 08:46:06 -0600383 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600384 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600385 """
386 effective_free = (
387 device_info.target_dir_size + device_info.target_fs_free
388 )
389 staging_size = self._GetStagingDirSize()
390 if effective_free < staging_size:
391 raise DeployFailure(
392 "Not enough free space on the device. Required: %s MiB, "
393 "actual: %s MiB."
394 % (staging_size // 1024, effective_free // 1024)
395 )
396 if device_info.target_fs_free < (100 * 1024):
397 logging.warning(
398 "The device has less than 100MB free. deploy_chrome may "
399 "hang during the transfer."
400 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 def _ShouldUseCompression(self):
403 """Checks if compression should be used for rsync."""
404 if self.options.compress == "always":
405 return True
406 elif self.options.compress == "never":
407 return False
408 elif self.options.compress == "auto":
409 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900410
Alex Klein1699fab2022-09-08 08:46:06 -0600411 def _Deploy(self):
412 logging.info(
413 "Copying %s to %s on device...",
414 self._deployment_name,
415 self.options.target_dir,
416 )
417 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
418 # This does not work for deploy.
419 if not self.device.HasRsync():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200420 # This assumes that rsync is part of the bootstrap package. In the
421 # future, this might change and we'll have to install it separately.
422 if not cros_build_lib.BooleanPrompt(
423 "Run dev_install on the device to install rsync?", True
424 ):
425 raise DeployFailure("rsync is not found on the device.")
426 self.device.BootstrapDevTools()
427 if not self.device.HasRsync():
428 raise DeployFailure("Failed to install rsync")
429
Eliot Courtneyaf474342023-07-19 14:20:25 +0900430 try:
431 staging_dir = os.path.abspath(self.staging_dir)
432 staging_chrome = os.path.join(staging_dir, "chrome")
433
434 if (
435 self.options.lacros
436 and self.options.skip_restart_ui
437 and os.path.exists(staging_chrome)
438 ):
439 # Make the chrome binary not executable before deploying to
440 # prevent ash chrome from starting chrome before the rsync has
441 # finished.
442 os.chmod(staging_chrome, 0o644)
443
444 self.device.CopyToDevice(
445 f"{staging_dir}/",
446 self.options.target_dir,
447 mode="rsync",
448 inplace=True,
449 compress=self._ShouldUseCompression(),
450 debug_level=logging.INFO,
451 verbose=self.options.verbose,
452 )
453 finally:
454 if self.options.lacros and self.options.skip_restart_ui:
455 self.device.run(
456 ["chmod", "+x", f"{self.options.target_dir}/chrome"],
457 check=False,
458 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700459
Alex Klein68b270c2023-04-14 14:42:50 -0600460 # Set the security context on the default Chrome dir if that's where
461 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600462 if (
463 not self.options.lacros
464 and self.device.IsSELinuxAvailable()
465 and (
466 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
467 )
468 ):
469 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800470
Alex Klein1699fab2022-09-08 08:46:06 -0600471 for p in self.copy_paths:
472 if p.mode:
473 # Set mode if necessary.
474 self.device.run(
475 "chmod %o %s/%s"
476 % (
477 p.mode,
478 self.options.target_dir,
479 p.src if not p.dest else p.dest,
480 )
481 )
Steve Funge984a532013-11-25 17:09:25 -0800482
Alex Klein1699fab2022-09-08 08:46:06 -0600483 if self.options.lacros:
484 self.device.run(
485 ["chown", "-R", "chronos:chronos", self.options.target_dir]
486 )
Erik Chen75a2f492020-08-06 19:15:11 -0700487
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000488 if self.options.compressed_ash:
489 self.device.run(["start", COMPRESSED_ASH_SERVICE])
490
Alex Klein68b270c2023-04-14 14:42:50 -0600491 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
492 # won't pick up major changes (bus type, logging, etc.), but all we care
493 # about is getting the latest policy from /opt/google/chrome/dbus so
494 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600495 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600496
Erik Chen75a2f492020-08-06 19:15:11 -0700497 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700498 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600499 logging.info("Starting UI...")
500 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800501
Joel Hockey764728e2023-03-14 17:10:04 -0700502 if self.options.unlock_password:
503 logging.info("Unlocking...")
504
505 @retry_util.WithRetry(max_retry=5, sleep=1)
506 def WaitForUnlockScreen():
507 if self._GetLastLogin() == last_login:
508 raise DeployFailure("Unlock screen not shown")
509
510 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700511 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700512 self.device.run(
513 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
514 )
515
516 def _GetLastLogin(self):
517 """Returns last login time"""
518 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
519
Alex Klein1699fab2022-09-08 08:46:06 -0600520 def _DeployTestBinaries(self):
521 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700522
Alex Klein68b270c2023-04-14 14:42:50 -0600523 There could be several binaries located in the local build dir, so
524 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
525 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600526 """
527 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
528 if r.returncode != 0:
529 raise DeployFailure(
530 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
531 )
532 binaries_to_copy = []
533 for f in r.stdout.splitlines():
534 binaries_to_copy.append(
535 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
536 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700537
Alex Klein1699fab2022-09-08 08:46:06 -0600538 staging_dir = os.path.join(
539 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
540 )
541 _PrepareStagingDir(
542 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
543 )
544 # Deploying can occasionally run into issues with rsync getting a broken
545 # pipe, so retry several times. See crbug.com/1141618 for more
546 # information.
547 retry_util.RetryException(
548 None,
549 3,
550 self.device.CopyToDevice,
551 staging_dir,
552 os.path.dirname(_CHROME_TEST_BIN_DIR),
553 mode="rsync",
554 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700555
Alex Klein1699fab2022-09-08 08:46:06 -0600556 def _CheckBoard(self):
557 """Check that the Chrome build is targeted for the device board."""
558 if self.options.board == self.device.board:
559 return
560 logging.warning(
561 "Device board is %s whereas target board is %s.",
562 self.device.board,
563 self.options.board,
564 )
565 if self.options.force:
566 return
567 if not cros_build_lib.BooleanPrompt(
568 "Continue despite board mismatch?", False
569 ):
570 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800571
Alex Klein1699fab2022-09-08 08:46:06 -0600572 def _CheckDeployType(self):
573 if self.options.build_dir:
574
575 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600576 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600577 return os.path.exists(
578 os.path.join(self.options.build_dir, filename)
579 )
580
581 # In the future, lacros-chrome and ash-chrome will likely be named
582 # something other than 'chrome' to avoid confusion.
583 # Handle non-Chrome deployments.
584 if not BinaryExists("chrome"):
585 if BinaryExists("app_shell"):
586 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
587
588 def _PrepareStagingDir(self):
589 _PrepareStagingDir(
590 self.options,
591 self.tempdir,
592 self.staging_dir,
593 self.copy_paths,
594 self.chrome_dir,
595 )
596
597 def _MountTarget(self):
598 logging.info("Mounting Chrome...")
599
600 # Create directory if does not exist.
601 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
602 try:
603 # Umount the existing mount on mount_dir if present first.
604 self.device.run(
605 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
606 )
607 except cros_build_lib.RunCommandError as e:
608 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600609 # If there is a failure, check if some process is using the
610 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600611 result = self.device.run(
612 LSOF_COMMAND % (self.options.mount_dir,),
613 check=False,
614 capture_output=True,
615 encoding="utf-8",
616 )
617 logging.error("lsof %s -->", self.options.mount_dir)
618 logging.error(result.stdout)
619 raise e
620
621 self.device.run(
622 _BIND_TO_FINAL_DIR_CMD
623 % (self.options.target_dir, self.options.mount_dir)
624 )
625
626 # Chrome needs partition to have exec and suid flags set
627 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
628
629 def Cleanup(self):
630 """Clean up RemoteDevice."""
631 if not self.options.staging_only:
632 self.device.Cleanup()
633
634 def Perform(self):
635 self._CheckDeployType()
636
637 # If requested, just do the staging step.
638 if self.options.staging_only:
639 self._PrepareStagingDir()
640 return 0
641
Alex Klein68b270c2023-04-14 14:42:50 -0600642 # Check that the build matches the device. Lacros-chrome skips this
643 # check as it's currently board independent. This means that it's
644 # possible to deploy a build of lacros-chrome with a mismatched
645 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600646 if not self.options.lacros:
647 self._CheckBoard()
648
649 # Ensure that the target directory exists before running parallel steps.
650 self._EnsureTargetDir()
651
652 logging.info("Preparing device")
653 steps = [
654 self._GetDeviceInfo,
Alex Klein1699fab2022-09-08 08:46:06 -0600655 self._MountRootfsAsWritable,
656 self._PrepareStagingDir,
657 ]
658
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900659 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600660 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600661 steps.append(self._KillLacrosChrome)
662 if self.options.reset_lacros:
663 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900664 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600665 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900666 config_modified = self._ModifyConfigFileIfNeededForLacros()
667 if config_modified and not restart_ui:
668 logging.warning(
669 "Config file modified but skipping restart_ui "
670 "due to option --skip-restart-ui. Config file "
671 "update is not reflected."
672 )
Alex Klein1699fab2022-09-08 08:46:06 -0600673
674 if restart_ui:
675 steps.append(self._KillAshChromeIfNeeded)
676 self._stopped_ui = True
677
678 ret = parallel.RunParallelSteps(
679 steps, halt_on_error=True, return_values=True
680 )
681 self._CheckDeviceFreeSpace(ret[0])
682
683 # If the root dir is not writable, try disabling rootfs verification.
684 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600685 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600686 if self._root_dir_is_still_readonly.is_set():
687 if self.options.noremove_rootfs_verification:
688 logging.warning("Skipping disable rootfs verification.")
689 elif not self._DisableRootfsVerification():
690 logging.warning("Failed to disable rootfs verification.")
691
Alex Klein68b270c2023-04-14 14:42:50 -0600692 # If the target dir is still not writable (i.e. the user opted out
693 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600694 if not self.device.IsDirWritable(self.options.target_dir):
695 if self.options.startui and self._stopped_ui:
696 logging.info("Restarting Chrome...")
697 self.device.run("start ui")
698 raise DeployFailure(
699 "Target location is not writable. Aborting."
700 )
701
702 if self.options.mount_dir is not None:
703 self._MountTarget()
704
705 # Actually deploy Chrome to the device.
706 self._Deploy()
707 if self.options.deploy_test_binaries:
708 self._DeployTestBinaries()
709
710 def _ModifyConfigFileIfNeededForLacros(self):
711 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
712
713 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600714 True if the file is modified, and the return value is usually used
715 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600716 """
717 assert (
718 self.options.lacros
719 ), "Only deploying lacros-chrome needs to modify the config file."
720 # Update /etc/chrome_dev.conf to include appropriate flags.
721 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000722 if self.options.enable_lacros_support:
723 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
724 if result.stdout.strip() == MODIFIED_CONF_FILE:
725 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600726 result = self.device.run(
727 _SET_LACROS_PATH_VIA_CONF_COMMAND
728 % {
729 "conf_file": _CONF_FILE,
730 "lacros_path": self.options.target_dir,
731 "modified_conf_file": MODIFIED_CONF_FILE,
732 },
733 shell=True,
734 )
735 if result.stdout.strip() == MODIFIED_CONF_FILE:
736 modified = True
737
738 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800739
740
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700741def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600742 """Convert formatted string to dictionary."""
743 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700744
745
Steven Bennetts368c3e52016-09-23 13:05:21 -0700746def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600747 """Convert GN_ARGS-formatted string to dictionary."""
748 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700749
750
Ryan Cuie535b172012-10-19 18:25:03 -0700751def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600752 """Create our custom parser."""
753 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700754
Alex Klein1699fab2022-09-08 08:46:06 -0600755 # TODO(rcui): Have this use the UI-V2 format of having source and target
756 # device be specified as positional arguments.
757 parser.add_argument(
758 "--force",
759 action="store_true",
760 default=False,
761 help="Skip all prompts (such as the prompt for disabling "
762 "of rootfs verification). This may result in the "
763 "target machine being rebooted.",
764 )
765 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
766 parser.add_argument(
767 "--board",
768 default=sdk_board_env,
769 help="The board the Chrome build is targeted for. When "
770 "in a 'cros chrome-sdk' shell, defaults to the SDK "
771 "board.",
772 )
773 parser.add_argument(
774 "--build-dir",
775 type="path",
776 help="The directory with Chrome build artifacts to "
777 "deploy from. Typically of format "
778 "<chrome_root>/out/Debug. When this option is used, "
779 "the GN_ARGS environment variable must be set.",
780 )
781 parser.add_argument(
782 "--target-dir",
783 type="path",
784 default=None,
785 help="Target directory on device to deploy Chrome into.",
786 )
787 parser.add_argument(
788 "-g",
789 "--gs-path",
790 type="gs_path",
791 help="GS path that contains the chrome to deploy.",
792 )
793 parser.add_argument(
794 "--private-key",
795 type="path",
796 default=None,
797 help="An ssh private key to use when deploying to " "a CrOS device.",
798 )
799 parser.add_argument(
800 "--nostartui",
801 action="store_false",
802 dest="startui",
803 default=True,
804 help="Don't restart the ui daemon after deployment.",
805 )
806 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700807 "--unlock-password",
808 default=None,
809 help="Password to use to unlock after deployment and restart.",
810 )
811 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600812 "--nostrip",
813 action="store_false",
814 dest="dostrip",
815 default=True,
816 help="Don't strip binaries during deployment. Warning: "
817 "the resulting binaries will be very large!",
818 )
819 parser.add_argument(
820 "-d",
821 "--device",
822 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
823 help="Device hostname or IP in the format hostname[:port].",
824 )
825 parser.add_argument(
826 "--mount-dir",
827 type="path",
828 default=None,
829 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000830 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600831 "Any existing mount on this directory will be "
832 "umounted first.",
833 )
834 parser.add_argument(
835 "--mount",
836 action="store_true",
837 default=False,
838 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000839 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600840 "Any existing mount on this directory will be "
841 "umounted first.",
842 )
843 parser.add_argument(
844 "--noremove-rootfs-verification",
845 action="store_true",
846 default=False,
847 help="Never remove rootfs verification.",
848 )
849 parser.add_argument(
850 "--deploy-test-binaries",
851 action="store_true",
852 default=False,
853 help="Also deploy any test binaries to %s. Useful for "
854 "running any Tast tests that execute these "
855 "binaries." % _CHROME_TEST_BIN_DIR,
856 )
857 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600858 "--use-external-config",
859 action="store_true",
860 help="When identifying the configuration for a board, "
861 "force usage of the external configuration if both "
862 "internal and external are available. This only "
863 "has an effect when stripping Chrome, i.e. when "
864 "--nostrip is not passed in.",
865 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700866
Georg Neis9b1ff192022-09-14 08:07:22 +0000867 group = parser.add_argument_group("Lacros Options")
868 group.add_argument(
869 "--lacros",
870 action="store_true",
871 default=False,
872 help="Deploys lacros-chrome rather than ash-chrome.",
873 )
874 group.add_argument(
875 "--reset-lacros",
876 action="store_true",
877 default=False,
878 help="Reset Lacros by deleting Lacros user data dir if it exists.",
879 )
880 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900881 "--skip-restart-ui",
882 action="store_true",
883 default=False,
884 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
885 "that this flag may cause ETXTBSY error on rsync, and also won't "
886 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
887 )
888 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000889 "--skip-enabling-lacros-support",
890 action="store_false",
891 dest="enable_lacros_support",
892 help="By default, deploying lacros-chrome modifies the "
893 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
894 "and (2) set the Lacros path, which can interfere with automated "
895 "testing. With this flag, part (1) will be skipped. See the "
896 "--skip-modifying-config-file flag for skipping both parts.",
897 )
898 group.add_argument(
899 "--skip-modifying-config-file",
900 action="store_false",
901 dest="modify_config_file",
902 help="When deploying lacros-chrome, do not modify the "
903 "/etc/chrome_dev.conf file. See also the "
904 "--skip-enabling-lacros-support flag.",
905 )
906
Alex Klein1699fab2022-09-08 08:46:06 -0600907 group = parser.add_argument_group("Advanced Options")
908 group.add_argument(
909 "-l",
910 "--local-pkg-path",
911 type="path",
912 help="Path to local chrome prebuilt package to deploy.",
913 )
914 group.add_argument(
915 "--sloppy",
916 action="store_true",
917 default=False,
918 help="Ignore when mandatory artifacts are missing.",
919 )
920 group.add_argument(
921 "--staging-flags",
922 default=None,
923 type=ValidateStagingFlags,
924 help=(
925 "Extra flags to control staging. Valid flags are - "
926 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
927 ),
928 )
929 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
930 group.add_argument(
931 "--strict",
932 action="store_true",
933 default=False,
934 help='Deprecated. Default behavior is "strict". Use '
935 "--sloppy to omit warnings for missing optional "
936 "files.",
937 )
938 group.add_argument(
939 "--strip-flags",
940 default=None,
941 help="Flags to call the 'strip' binutil tool with. "
942 "Overrides the default arguments.",
943 )
944 group.add_argument(
945 "--ping",
946 action="store_true",
947 default=False,
948 help="Ping the device before connection attempt.",
949 )
950 group.add_argument(
951 "--process-timeout",
952 type=int,
953 default=KILL_PROC_MAX_WAIT,
954 help="Timeout for process shutdown.",
955 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700956
Alex Klein1699fab2022-09-08 08:46:06 -0600957 group = parser.add_argument_group(
958 "Metadata Overrides (Advanced)",
959 description="Provide all of these overrides in order to remove "
960 "dependencies on metadata.json existence.",
961 )
962 group.add_argument(
963 "--target-tc",
964 action="store",
965 default=None,
966 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
967 )
968 group.add_argument(
969 "--toolchain-url",
970 action="store",
971 default=None,
972 help="Override toolchain url format pattern, e.g. "
973 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
974 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700975
Alex Klein1699fab2022-09-08 08:46:06 -0600976 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
977 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
978 parser.add_argument(
979 "--gyp-defines",
980 default=None,
981 type=ValidateStagingFlags,
982 help=argparse.SUPPRESS,
983 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700984
Alex Klein1699fab2022-09-08 08:46:06 -0600985 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600986 # when --build-dir is set. Defaults to reading from the GN_ARGS env
987 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600988 parser.add_argument(
989 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
990 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700991
Alex Klein1699fab2022-09-08 08:46:06 -0600992 # Path of an empty directory to stage chrome artifacts to. Defaults to a
993 # temporary directory that is removed when the script finishes. If the path
994 # is specified, then it will not be removed.
995 parser.add_argument(
996 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
997 )
998 # Only prepare the staging directory, and skip deploying to the device.
999 parser.add_argument(
1000 "--staging-only",
1001 action="store_true",
1002 default=False,
1003 help=argparse.SUPPRESS,
1004 )
1005 # Uploads the compressed staging directory to the given gs:// path URI.
1006 parser.add_argument(
1007 "--staging-upload",
1008 type="gs_path",
1009 help="GS path to upload the compressed staging files to.",
1010 )
1011 # Used alongside --staging-upload to upload with public-read ACL.
1012 parser.add_argument(
1013 "--public-read",
1014 action="store_true",
1015 default=False,
1016 help="GS path to upload the compressed staging files to.",
1017 )
1018 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
1019 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
1020 # fetching the SDK toolchain.
1021 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
1022 parser.add_argument(
1023 "--compress",
1024 action="store",
1025 default="auto",
1026 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001027 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001028 'is set to "auto", that disables compression if '
1029 "the target device has a gigabit ethernet port.",
1030 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001031 parser.add_argument(
1032 "--compressed-ash",
1033 action="store_true",
1034 default=False,
1035 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1036 "binary is stored on DUT in squashfs, mounted upon boot.",
1037 )
Alex Klein1699fab2022-09-08 08:46:06 -06001038 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001039
Ryan Cuie535b172012-10-19 18:25:03 -07001040
1041def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001042 """Parse args, and run environment-independent checks."""
1043 parser = _CreateParser()
1044 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001045
Alex Klein1699fab2022-09-08 08:46:06 -06001046 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1047 parser.error(
1048 "Need to specify either --gs-path, --local-pkg-path, or "
1049 "--build-dir"
1050 )
1051 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1052 parser.error(
1053 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1054 )
1055 if options.lacros:
1056 if options.dostrip and not options.board:
1057 parser.error("Please specify --board.")
1058 if options.mount_dir or options.mount:
1059 parser.error("--lacros does not support --mount or --mount-dir")
1060 if options.deploy_test_binaries:
1061 parser.error("--lacros does not support --deploy-test-binaries")
1062 if options.local_pkg_path:
1063 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001064 if options.compressed_ash:
1065 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001066 else:
1067 if not options.board and options.build_dir:
1068 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1069 if match:
1070 options.board = match.group(1)
1071 logging.info("--board is set to %s", options.board)
1072 if not options.board:
1073 parser.error("--board is required")
1074 if options.gs_path and options.local_pkg_path:
1075 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1076 if not (options.staging_only or options.device):
1077 parser.error("Need to specify --device")
1078 if options.staging_flags and not options.build_dir:
1079 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001080
Alex Klein1699fab2022-09-08 08:46:06 -06001081 if options.strict:
1082 logging.warning("--strict is deprecated.")
1083 if options.gyp_defines:
1084 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001085
Alex Klein1699fab2022-09-08 08:46:06 -06001086 if options.mount or options.mount_dir:
1087 if not options.target_dir:
1088 options.target_dir = _CHROME_DIR_MOUNT
1089 else:
1090 if not options.target_dir:
1091 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 if options.mount and not options.mount_dir:
1094 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001095
Alex Klein1699fab2022-09-08 08:46:06 -06001096 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001097
1098
Mike Frysingerc3061a62015-06-04 04:16:18 -04001099def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001100 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001101
Alex Klein1699fab2022-09-08 08:46:06 -06001102 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001103 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001104 """
1105 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1106 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001107
Alex Klein1699fab2022-09-08 08:46:06 -06001108 if not options.gn_args:
1109 gn_env = os.getenv("GN_ARGS")
1110 if gn_env is not None:
1111 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1112 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001113
Alex Klein1699fab2022-09-08 08:46:06 -06001114 if not options.staging_flags:
1115 use_env = os.getenv("USE")
1116 if use_env is not None:
1117 options.staging_flags = " ".join(
1118 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1119 )
1120 logging.info(
1121 "Staging flags taken from USE in environment: %s",
1122 options.staging_flags,
1123 )
Steven Bennetts60600462016-05-12 10:40:20 -07001124
Ryan Cuia56a71e2012-10-18 18:40:35 -07001125
Ryan Cui504db722013-01-22 11:48:01 -08001126def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001127 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001128
Alex Klein1699fab2022-09-08 08:46:06 -06001129 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001130 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001131 """
1132 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1133 files = gs_ctx.LS(gs_path)
1134 files = [
1135 found
1136 for found in files
1137 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1138 ]
1139 if not files:
1140 raise Exception("No chrome package found at %s" % gs_path)
1141 elif len(files) > 1:
1142 # - Users should provide us with a direct link to either a stripped or
1143 # unstripped chrome package.
1144 # - In the case of being provided with an archive directory, where both
1145 # stripped and unstripped chrome available, use the stripped chrome
1146 # package.
1147 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001148 # - Unstripped chrome pkg is
1149 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001150 files = [f for f in files if not "unstripped" in f]
1151 assert len(files) == 1
1152 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001153
Alex Klein1699fab2022-09-08 08:46:06 -06001154 filename = _UrlBaseName(files[0])
1155 logging.info("Fetching %s...", filename)
1156 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1157 chrome_path = os.path.join(tempdir, filename)
1158 assert os.path.exists(chrome_path)
1159 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001160
1161
Ryan Cuif890a3e2013-03-07 18:57:06 -08001162@contextlib.contextmanager
1163def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001164 if not options.dostrip:
1165 yield None
1166 elif options.strip_bin:
1167 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001168 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001169 sdk = cros_chrome_sdk.SDKFetcher(
1170 options.cache_dir,
1171 options.board,
1172 use_external_config=options.use_external_config,
1173 )
1174 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1175 with sdk.Prepare(
1176 components=components,
1177 target_tc=options.target_tc,
1178 toolchain_url=options.toolchain_url,
1179 ) as ctx:
1180 env_path = os.path.join(
1181 ctx.key_map[constants.CHROME_ENV_TAR].path,
1182 constants.CHROME_ENV_FILE,
1183 )
1184 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1185 strip_bin = os.path.join(
1186 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1187 "bin",
1188 os.path.basename(strip_bin),
1189 )
1190 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001191
Alex Klein1699fab2022-09-08 08:46:06 -06001192
1193def _UploadStagingDir(
1194 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1195) -> None:
1196 """Uploads the compressed staging directory.
1197
1198 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001199 options: options object.
1200 tempdir: Scratch space.
1201 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001202 """
1203 staging_tarball_path = os.path.join(
1204 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1205 )
1206 logging.info(
1207 "Compressing staging dir (%s) to (%s)",
1208 staging_dir,
1209 staging_tarball_path,
1210 )
1211 cros_build_lib.CreateTarball(
1212 staging_tarball_path,
1213 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001214 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001215 extra_env={"ZSTD_CLEVEL": "9"},
1216 )
1217 logging.info(
1218 "Uploading staging tarball (%s) into %s",
1219 staging_tarball_path,
1220 options.staging_upload,
1221 )
1222 ctx = gs.GSContext()
1223 ctx.Copy(
1224 staging_tarball_path,
1225 options.staging_upload,
1226 acl="public-read" if options.public_read else "",
1227 )
1228
1229
1230def _PrepareStagingDir(
1231 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1232):
1233 """Place the necessary files in the staging directory.
1234
Alex Klein68b270c2023-04-14 14:42:50 -06001235 The staging directory is the directory used to rsync the build artifacts
1236 over to the device. Only the necessary Chrome build artifacts are put into
1237 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001238 """
1239 if chrome_dir is None:
1240 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1241 osutils.SafeMakedirs(staging_dir)
1242 os.chmod(staging_dir, 0o755)
1243 if options.build_dir:
1244 with _StripBinContext(options) as strip_bin:
1245 strip_flags = (
1246 None
1247 if options.strip_flags is None
1248 else shlex.split(options.strip_flags)
1249 )
1250 chrome_util.StageChromeFromBuildDir(
1251 staging_dir,
1252 options.build_dir,
1253 strip_bin,
1254 sloppy=options.sloppy,
1255 gn_args=options.gn_args,
1256 staging_flags=options.staging_flags,
1257 strip_flags=strip_flags,
1258 copy_paths=copy_paths,
1259 )
1260 else:
1261 pkg_path = options.local_pkg_path
1262 if options.gs_path:
1263 pkg_path = _FetchChromePackage(
1264 options.cache_dir, tempdir, options.gs_path
1265 )
1266
1267 assert pkg_path
1268 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001269 # Extract only the ./opt/google/chrome contents, directly into the
1270 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001271 if pkg_path[-4:] == ".zip":
1272 cros_build_lib.dbg_run(
1273 [
1274 "unzip",
1275 "-X",
1276 pkg_path,
1277 _ANDROID_DIR_EXTRACT_PATH,
1278 "-d",
1279 staging_dir,
1280 ]
1281 )
1282 for filename in glob.glob(
1283 os.path.join(staging_dir, "system/chrome/*")
1284 ):
1285 shutil.move(filename, staging_dir)
1286 osutils.RmDir(
1287 os.path.join(staging_dir, "system"), ignore_missing=True
1288 )
1289 else:
1290 compression = cros_build_lib.CompressionDetectType(pkg_path)
1291 compressor = cros_build_lib.FindCompressor(compression)
Cindy Lin0b043c42023-06-23 20:59:26 +00001292 if compression == cros_build_lib.CompressionType.ZSTD:
1293 compressor += " -f"
Alex Klein1699fab2022-09-08 08:46:06 -06001294 cros_build_lib.dbg_run(
1295 [
1296 "tar",
1297 "--strip-components",
1298 "4",
1299 "--extract",
1300 "-I",
1301 compressor,
1302 "--preserve-permissions",
1303 "--file",
1304 pkg_path,
1305 ".%s" % chrome_dir,
1306 ],
1307 cwd=staging_dir,
1308 )
1309
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001310 if options.compressed_ash:
1311 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1312 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001313 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1314 # this is in test, cut the known suffix of experimental overlays.
1315 sdk_orig_board = options.board
1316 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1317 sdk_orig_board = sdk_orig_board[
1318 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1319 ]
1320
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001321 sdk = cros_chrome_sdk.SDKFetcher(
1322 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001323 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001324 use_external_config=options.use_external_config,
1325 )
1326 with sdk.Prepare(
1327 components=[],
1328 target_tc=options.target_tc,
1329 toolchain_url=options.toolchain_url,
1330 ):
1331 cros_build_lib.dbg_run(
1332 [
1333 "mksquashfs",
1334 RAW_ASH_FILE,
1335 COMPRESSED_ASH_FILE,
1336 "-all-root",
1337 "-no-progress",
1338 "-comp",
1339 "zstd",
1340 ],
1341 cwd=staging_dir,
1342 )
1343 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1344
Alex Klein1699fab2022-09-08 08:46:06 -06001345 if options.staging_upload:
1346 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001347
Ryan Cui71aa8de2013-04-19 16:12:55 -07001348
Ryan Cui3045c5d2012-07-13 18:00:33 -07001349def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001350 options = _ParseCommandLine(argv)
1351 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001352
Alex Klein1699fab2022-09-08 08:46:06 -06001353 with osutils.TempDir(set_global=True) as tempdir:
1354 staging_dir = options.staging_dir
1355 if not staging_dir:
1356 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001357
Alex Klein1699fab2022-09-08 08:46:06 -06001358 deploy = DeployChrome(options, tempdir, staging_dir)
1359 try:
1360 deploy.Perform()
1361 except failures_lib.StepFailure as ex:
1362 raise SystemExit(str(ex).strip())
1363 deploy.Cleanup()