blob: f050555c4f7241e8e813b57933ec5261fef31b80 [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 _Reboot(self):
217 # A reboot in developer mode takes a while (and has delays), so the user
218 # will have time to read and act on the USB boot instructions below.
219 logging.info(
220 "Please remember to press Ctrl-U if you are booting from USB."
221 )
222 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600223
Alex Klein1699fab2022-09-08 08:46:06 -0600224 def _DisableRootfsVerification(self):
225 if not self.options.force:
226 logging.error(
227 "Detected that the device has rootfs verification enabled."
228 )
229 logging.info(
230 "This script can automatically remove the rootfs "
231 "verification, which requires it to reboot the device."
232 )
233 logging.info("Make sure the device is in developer mode!")
234 logging.info("Skip this prompt by specifying --force.")
235 if not cros_build_lib.BooleanPrompt(
236 "Remove rootfs verification?", False
237 ):
238 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700239
Alex Klein1699fab2022-09-08 08:46:06 -0600240 logging.info(
241 "Removing rootfs verification from %s", self.options.device
242 )
Alex Klein68b270c2023-04-14 14:42:50 -0600243 # Running in VMs cause make_dev_ssd's firmware confidence checks to
244 # fail. Use --force to bypass the checks.
Alex Klein1699fab2022-09-08 08:46:06 -0600245 cmd = (
246 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
247 "--remove_rootfs_verification --force"
248 )
249 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
250 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 # Now that the machine has been rebooted, we need to kill Chrome again.
255 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 # Make sure the rootfs is writable now.
258 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700259
Alex Klein1699fab2022-09-08 08:46:06 -0600260 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700261
Alex Klein1699fab2022-09-08 08:46:06 -0600262 def _CheckUiJobStarted(self):
263 # status output is in the format:
264 # <job_name> <status> ['process' <pid>].
265 # <status> is in the format <goal>/<state>.
266 try:
267 result = self.device.run(
268 "status ui", capture_output=True, encoding="utf-8"
269 )
270 except cros_build_lib.RunCommandError as e:
271 if "Unknown job" in e.stderr:
272 return False
273 else:
274 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800275
Alex Klein1699fab2022-09-08 08:46:06 -0600276 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700277
Alex Klein1699fab2022-09-08 08:46:06 -0600278 def _KillLacrosChrome(self):
279 """This method kills lacros-chrome on the device, if it's running."""
Eliot Courtneyaf474342023-07-19 14:20:25 +0900280 # Mark the lacros chrome binary as not executable, so if keep-alive is
281 # enabled ash chrome can't restart lacros chrome. This prevents rsync
282 # from failing if the file is still in use (being executed by ash
283 # chrome). Note that this will cause ash chrome to continuously attempt
284 # to start lacros and fail, although it doesn't seem to cause issues.
285 if self.options.skip_restart_ui:
286 self.device.run(
287 ["chmod", "-x", f"{self.options.target_dir}/chrome"],
288 check=False,
289 )
Alex Klein1699fab2022-09-08 08:46:06 -0600290 self.device.run(
291 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
292 check=False,
293 )
Erik Chen75a2f492020-08-06 19:15:11 -0700294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 def _ResetLacrosChrome(self):
296 """Reset Lacros to fresh state by deleting user data dir."""
297 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 def _KillAshChromeIfNeeded(self):
300 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700301
Alex Klein68b270c2023-04-14 14:42:50 -0600302 This method calls 'stop ui', and then also manually pkills both
303 ash-chrome and the session manager.
Alex Klein1699fab2022-09-08 08:46:06 -0600304 """
305 if self._CheckUiJobStarted():
306 logging.info("Shutting down Chrome...")
307 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 # Developers sometimes run session_manager manually, in which case we'll
310 # need to help shut the chrome processes down.
311 try:
312 with timeout_util.Timeout(self.options.process_timeout):
313 while self._ChromeFileInUse():
314 logging.warning(
315 "The chrome binary on the device is in use."
316 )
317 logging.warning(
318 "Killing chrome and session_manager processes...\n"
319 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 self.device.run(
322 "pkill 'chrome|session_manager'", check=False
323 )
324 # Wait for processes to actually terminate
325 time.sleep(POST_KILL_WAIT)
326 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000327 if self.options.compressed_ash:
328 result = self.device.run(
329 ["umount", RAW_ASH_PATH],
330 check=False,
331 capture_output=True,
332 )
333 if result.returncode and not (
334 result.returncode == 32
335 and "not mounted" in result.stderr
336 ):
337 raise DeployFailure(
338 "Could not unmount compressed ash. "
339 f"Error Code: {result.returncode}, "
340 f"Error Message: {result.stderr}"
341 )
Alex Klein1699fab2022-09-08 08:46:06 -0600342 except timeout_util.TimeoutError:
343 msg = (
344 "Could not kill processes after %s seconds. Please exit any "
345 "running chrome processes and try again."
346 % self.options.process_timeout
347 )
348 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700349
Alex Klein1699fab2022-09-08 08:46:06 -0600350 def _MountRootfsAsWritable(self, check=False):
351 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700352
Alex Klein1699fab2022-09-08 08:46:06 -0600353 If the command fails and the root dir is not writable then this function
354 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700355
Alex Klein1699fab2022-09-08 08:46:06 -0600356 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600357 check: See remote.RemoteAccess.RemoteSh for details.
Alex Klein1699fab2022-09-08 08:46:06 -0600358 """
359 # TODO: Should migrate to use the remount functions in remote_access.
360 result = self.device.run(
361 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
362 )
363 if result.returncode and not self.device.IsDirWritable("/"):
364 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700365
Alex Klein1699fab2022-09-08 08:46:06 -0600366 def _EnsureTargetDir(self):
367 """Ensures that the target directory exists on the remote device."""
368 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600369 # Any valid /opt directory should already exist so avoid the remote
370 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600371 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
372 return
Mike Frysinger445f6bf2023-06-27 11:43:33 -0400373 self.device.mkdir(target_dir, mode=0o775)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600376 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600377 steps = [
378 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
379 functools.partial(
380 self._GetRemoteMountFree, self.options.target_dir
381 ),
382 ]
383 return_values = parallel.RunParallelSteps(steps, return_values=True)
384 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700385
Alex Klein1699fab2022-09-08 08:46:06 -0600386 def _CheckDeviceFreeSpace(self, device_info):
387 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700388
Alex Klein1699fab2022-09-08 08:46:06 -0600389 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600390 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600391 """
392 effective_free = (
393 device_info.target_dir_size + device_info.target_fs_free
394 )
395 staging_size = self._GetStagingDirSize()
396 if effective_free < staging_size:
397 raise DeployFailure(
398 "Not enough free space on the device. Required: %s MiB, "
399 "actual: %s MiB."
400 % (staging_size // 1024, effective_free // 1024)
401 )
402 if device_info.target_fs_free < (100 * 1024):
403 logging.warning(
404 "The device has less than 100MB free. deploy_chrome may "
405 "hang during the transfer."
406 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700407
Alex Klein1699fab2022-09-08 08:46:06 -0600408 def _ShouldUseCompression(self):
409 """Checks if compression should be used for rsync."""
410 if self.options.compress == "always":
411 return True
412 elif self.options.compress == "never":
413 return False
414 elif self.options.compress == "auto":
415 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900416
Alex Klein1699fab2022-09-08 08:46:06 -0600417 def _Deploy(self):
418 logging.info(
419 "Copying %s to %s on device...",
420 self._deployment_name,
421 self.options.target_dir,
422 )
423 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
424 # This does not work for deploy.
425 if not self.device.HasRsync():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200426 # This assumes that rsync is part of the bootstrap package. In the
427 # future, this might change and we'll have to install it separately.
428 if not cros_build_lib.BooleanPrompt(
429 "Run dev_install on the device to install rsync?", True
430 ):
431 raise DeployFailure("rsync is not found on the device.")
432 self.device.BootstrapDevTools()
433 if not self.device.HasRsync():
434 raise DeployFailure("Failed to install rsync")
435
Eliot Courtneyaf474342023-07-19 14:20:25 +0900436 try:
437 staging_dir = os.path.abspath(self.staging_dir)
438 staging_chrome = os.path.join(staging_dir, "chrome")
439
440 if (
441 self.options.lacros
442 and self.options.skip_restart_ui
443 and os.path.exists(staging_chrome)
444 ):
445 # Make the chrome binary not executable before deploying to
446 # prevent ash chrome from starting chrome before the rsync has
447 # finished.
448 os.chmod(staging_chrome, 0o644)
449
450 self.device.CopyToDevice(
451 f"{staging_dir}/",
452 self.options.target_dir,
453 mode="rsync",
454 inplace=True,
455 compress=self._ShouldUseCompression(),
456 debug_level=logging.INFO,
457 verbose=self.options.verbose,
458 )
459 finally:
460 if self.options.lacros and self.options.skip_restart_ui:
461 self.device.run(
462 ["chmod", "+x", f"{self.options.target_dir}/chrome"],
463 check=False,
464 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700465
Alex Klein68b270c2023-04-14 14:42:50 -0600466 # Set the security context on the default Chrome dir if that's where
467 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600468 if (
469 not self.options.lacros
470 and self.device.IsSELinuxAvailable()
471 and (
472 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
473 )
474 ):
475 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800476
Alex Klein1699fab2022-09-08 08:46:06 -0600477 for p in self.copy_paths:
478 if p.mode:
479 # Set mode if necessary.
480 self.device.run(
481 "chmod %o %s/%s"
482 % (
483 p.mode,
484 self.options.target_dir,
485 p.src if not p.dest else p.dest,
486 )
487 )
Steve Funge984a532013-11-25 17:09:25 -0800488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 if self.options.lacros:
490 self.device.run(
491 ["chown", "-R", "chronos:chronos", self.options.target_dir]
492 )
Erik Chen75a2f492020-08-06 19:15:11 -0700493
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000494 if self.options.compressed_ash:
495 self.device.run(["start", COMPRESSED_ASH_SERVICE])
496
Alex Klein68b270c2023-04-14 14:42:50 -0600497 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
498 # won't pick up major changes (bus type, logging, etc.), but all we care
499 # about is getting the latest policy from /opt/google/chrome/dbus so
500 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600501 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600502
Erik Chen75a2f492020-08-06 19:15:11 -0700503 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700504 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600505 logging.info("Starting UI...")
506 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800507
Joel Hockey764728e2023-03-14 17:10:04 -0700508 if self.options.unlock_password:
509 logging.info("Unlocking...")
510
511 @retry_util.WithRetry(max_retry=5, sleep=1)
512 def WaitForUnlockScreen():
513 if self._GetLastLogin() == last_login:
514 raise DeployFailure("Unlock screen not shown")
515
516 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700517 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700518 self.device.run(
519 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
520 )
521
522 def _GetLastLogin(self):
523 """Returns last login time"""
524 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
525
Alex Klein1699fab2022-09-08 08:46:06 -0600526 def _DeployTestBinaries(self):
527 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700528
Alex Klein68b270c2023-04-14 14:42:50 -0600529 There could be several binaries located in the local build dir, so
530 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
531 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600532 """
533 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
534 if r.returncode != 0:
535 raise DeployFailure(
536 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
537 )
538 binaries_to_copy = []
539 for f in r.stdout.splitlines():
540 binaries_to_copy.append(
541 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
542 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 staging_dir = os.path.join(
545 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
546 )
547 _PrepareStagingDir(
548 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
549 )
550 # Deploying can occasionally run into issues with rsync getting a broken
551 # pipe, so retry several times. See crbug.com/1141618 for more
552 # information.
553 retry_util.RetryException(
554 None,
555 3,
556 self.device.CopyToDevice,
557 staging_dir,
558 os.path.dirname(_CHROME_TEST_BIN_DIR),
559 mode="rsync",
560 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700561
Alex Klein1699fab2022-09-08 08:46:06 -0600562 def _CheckBoard(self):
563 """Check that the Chrome build is targeted for the device board."""
564 if self.options.board == self.device.board:
565 return
566 logging.warning(
567 "Device board is %s whereas target board is %s.",
568 self.device.board,
569 self.options.board,
570 )
571 if self.options.force:
572 return
573 if not cros_build_lib.BooleanPrompt(
574 "Continue despite board mismatch?", False
575 ):
576 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800577
Alex Klein1699fab2022-09-08 08:46:06 -0600578 def _CheckDeployType(self):
579 if self.options.build_dir:
580
581 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600582 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600583 return os.path.exists(
584 os.path.join(self.options.build_dir, filename)
585 )
586
587 # In the future, lacros-chrome and ash-chrome will likely be named
588 # something other than 'chrome' to avoid confusion.
589 # Handle non-Chrome deployments.
590 if not BinaryExists("chrome"):
591 if BinaryExists("app_shell"):
592 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
593
594 def _PrepareStagingDir(self):
595 _PrepareStagingDir(
596 self.options,
597 self.tempdir,
598 self.staging_dir,
599 self.copy_paths,
600 self.chrome_dir,
601 )
602
603 def _MountTarget(self):
604 logging.info("Mounting Chrome...")
605
606 # Create directory if does not exist.
607 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
608 try:
609 # Umount the existing mount on mount_dir if present first.
610 self.device.run(
611 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
612 )
613 except cros_build_lib.RunCommandError as e:
614 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600615 # If there is a failure, check if some process is using the
616 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600617 result = self.device.run(
618 LSOF_COMMAND % (self.options.mount_dir,),
619 check=False,
620 capture_output=True,
621 encoding="utf-8",
622 )
623 logging.error("lsof %s -->", self.options.mount_dir)
624 logging.error(result.stdout)
625 raise e
626
627 self.device.run(
628 _BIND_TO_FINAL_DIR_CMD
629 % (self.options.target_dir, self.options.mount_dir)
630 )
631
632 # Chrome needs partition to have exec and suid flags set
633 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
634
635 def Cleanup(self):
636 """Clean up RemoteDevice."""
637 if not self.options.staging_only:
638 self.device.Cleanup()
639
640 def Perform(self):
641 self._CheckDeployType()
642
643 # If requested, just do the staging step.
644 if self.options.staging_only:
645 self._PrepareStagingDir()
646 return 0
647
Alex Klein68b270c2023-04-14 14:42:50 -0600648 # Check that the build matches the device. Lacros-chrome skips this
649 # check as it's currently board independent. This means that it's
650 # possible to deploy a build of lacros-chrome with a mismatched
651 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600652 if not self.options.lacros:
653 self._CheckBoard()
654
655 # Ensure that the target directory exists before running parallel steps.
656 self._EnsureTargetDir()
657
658 logging.info("Preparing device")
659 steps = [
660 self._GetDeviceInfo,
Alex Klein1699fab2022-09-08 08:46:06 -0600661 self._MountRootfsAsWritable,
662 self._PrepareStagingDir,
663 ]
664
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900665 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600666 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600667 steps.append(self._KillLacrosChrome)
668 if self.options.reset_lacros:
669 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900670 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600671 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900672 config_modified = self._ModifyConfigFileIfNeededForLacros()
673 if config_modified and not restart_ui:
674 logging.warning(
675 "Config file modified but skipping restart_ui "
676 "due to option --skip-restart-ui. Config file "
677 "update is not reflected."
678 )
Alex Klein1699fab2022-09-08 08:46:06 -0600679
680 if restart_ui:
681 steps.append(self._KillAshChromeIfNeeded)
682 self._stopped_ui = True
683
684 ret = parallel.RunParallelSteps(
685 steps, halt_on_error=True, return_values=True
686 )
687 self._CheckDeviceFreeSpace(ret[0])
688
689 # If the root dir is not writable, try disabling rootfs verification.
690 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600691 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600692 if self._root_dir_is_still_readonly.is_set():
693 if self.options.noremove_rootfs_verification:
694 logging.warning("Skipping disable rootfs verification.")
695 elif not self._DisableRootfsVerification():
696 logging.warning("Failed to disable rootfs verification.")
697
Alex Klein68b270c2023-04-14 14:42:50 -0600698 # If the target dir is still not writable (i.e. the user opted out
699 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600700 if not self.device.IsDirWritable(self.options.target_dir):
701 if self.options.startui and self._stopped_ui:
702 logging.info("Restarting Chrome...")
703 self.device.run("start ui")
704 raise DeployFailure(
705 "Target location is not writable. Aborting."
706 )
707
708 if self.options.mount_dir is not None:
709 self._MountTarget()
710
711 # Actually deploy Chrome to the device.
712 self._Deploy()
713 if self.options.deploy_test_binaries:
714 self._DeployTestBinaries()
715
716 def _ModifyConfigFileIfNeededForLacros(self):
717 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
718
719 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600720 True if the file is modified, and the return value is usually used
721 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600722 """
723 assert (
724 self.options.lacros
725 ), "Only deploying lacros-chrome needs to modify the config file."
726 # Update /etc/chrome_dev.conf to include appropriate flags.
727 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000728 if self.options.enable_lacros_support:
729 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
730 if result.stdout.strip() == MODIFIED_CONF_FILE:
731 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600732 result = self.device.run(
733 _SET_LACROS_PATH_VIA_CONF_COMMAND
734 % {
735 "conf_file": _CONF_FILE,
736 "lacros_path": self.options.target_dir,
737 "modified_conf_file": MODIFIED_CONF_FILE,
738 },
739 shell=True,
740 )
741 if result.stdout.strip() == MODIFIED_CONF_FILE:
742 modified = True
743
744 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800745
746
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700747def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600748 """Convert formatted string to dictionary."""
749 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700750
751
Steven Bennetts368c3e52016-09-23 13:05:21 -0700752def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600753 """Convert GN_ARGS-formatted string to dictionary."""
754 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700755
756
Ryan Cuie535b172012-10-19 18:25:03 -0700757def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600758 """Create our custom parser."""
759 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700760
Alex Klein1699fab2022-09-08 08:46:06 -0600761 # TODO(rcui): Have this use the UI-V2 format of having source and target
762 # device be specified as positional arguments.
763 parser.add_argument(
764 "--force",
765 action="store_true",
766 default=False,
767 help="Skip all prompts (such as the prompt for disabling "
768 "of rootfs verification). This may result in the "
769 "target machine being rebooted.",
770 )
771 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
772 parser.add_argument(
773 "--board",
774 default=sdk_board_env,
775 help="The board the Chrome build is targeted for. When "
776 "in a 'cros chrome-sdk' shell, defaults to the SDK "
777 "board.",
778 )
779 parser.add_argument(
780 "--build-dir",
781 type="path",
782 help="The directory with Chrome build artifacts to "
783 "deploy from. Typically of format "
784 "<chrome_root>/out/Debug. When this option is used, "
785 "the GN_ARGS environment variable must be set.",
786 )
787 parser.add_argument(
788 "--target-dir",
789 type="path",
790 default=None,
791 help="Target directory on device to deploy Chrome into.",
792 )
793 parser.add_argument(
794 "-g",
795 "--gs-path",
796 type="gs_path",
797 help="GS path that contains the chrome to deploy.",
798 )
799 parser.add_argument(
800 "--private-key",
801 type="path",
802 default=None,
803 help="An ssh private key to use when deploying to " "a CrOS device.",
804 )
805 parser.add_argument(
806 "--nostartui",
807 action="store_false",
808 dest="startui",
809 default=True,
810 help="Don't restart the ui daemon after deployment.",
811 )
812 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700813 "--unlock-password",
814 default=None,
815 help="Password to use to unlock after deployment and restart.",
816 )
817 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600818 "--nostrip",
819 action="store_false",
820 dest="dostrip",
821 default=True,
822 help="Don't strip binaries during deployment. Warning: "
823 "the resulting binaries will be very large!",
824 )
825 parser.add_argument(
826 "-d",
827 "--device",
828 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
829 help="Device hostname or IP in the format hostname[:port].",
830 )
831 parser.add_argument(
832 "--mount-dir",
833 type="path",
834 default=None,
835 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000836 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600837 "Any existing mount on this directory will be "
838 "umounted first.",
839 )
840 parser.add_argument(
841 "--mount",
842 action="store_true",
843 default=False,
844 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000845 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600846 "Any existing mount on this directory will be "
847 "umounted first.",
848 )
849 parser.add_argument(
850 "--noremove-rootfs-verification",
851 action="store_true",
852 default=False,
853 help="Never remove rootfs verification.",
854 )
855 parser.add_argument(
856 "--deploy-test-binaries",
857 action="store_true",
858 default=False,
859 help="Also deploy any test binaries to %s. Useful for "
860 "running any Tast tests that execute these "
861 "binaries." % _CHROME_TEST_BIN_DIR,
862 )
863 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600864 "--use-external-config",
865 action="store_true",
866 help="When identifying the configuration for a board, "
867 "force usage of the external configuration if both "
868 "internal and external are available. This only "
869 "has an effect when stripping Chrome, i.e. when "
870 "--nostrip is not passed in.",
871 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700872
Georg Neis9b1ff192022-09-14 08:07:22 +0000873 group = parser.add_argument_group("Lacros Options")
874 group.add_argument(
875 "--lacros",
876 action="store_true",
877 default=False,
878 help="Deploys lacros-chrome rather than ash-chrome.",
879 )
880 group.add_argument(
881 "--reset-lacros",
882 action="store_true",
883 default=False,
884 help="Reset Lacros by deleting Lacros user data dir if it exists.",
885 )
886 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900887 "--skip-restart-ui",
888 action="store_true",
889 default=False,
890 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
891 "that this flag may cause ETXTBSY error on rsync, and also won't "
892 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
893 )
894 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000895 "--skip-enabling-lacros-support",
896 action="store_false",
897 dest="enable_lacros_support",
898 help="By default, deploying lacros-chrome modifies the "
899 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
900 "and (2) set the Lacros path, which can interfere with automated "
901 "testing. With this flag, part (1) will be skipped. See the "
902 "--skip-modifying-config-file flag for skipping both parts.",
903 )
904 group.add_argument(
905 "--skip-modifying-config-file",
906 action="store_false",
907 dest="modify_config_file",
908 help="When deploying lacros-chrome, do not modify the "
909 "/etc/chrome_dev.conf file. See also the "
910 "--skip-enabling-lacros-support flag.",
911 )
912
Alex Klein1699fab2022-09-08 08:46:06 -0600913 group = parser.add_argument_group("Advanced Options")
914 group.add_argument(
915 "-l",
916 "--local-pkg-path",
917 type="path",
918 help="Path to local chrome prebuilt package to deploy.",
919 )
920 group.add_argument(
921 "--sloppy",
922 action="store_true",
923 default=False,
924 help="Ignore when mandatory artifacts are missing.",
925 )
926 group.add_argument(
927 "--staging-flags",
928 default=None,
929 type=ValidateStagingFlags,
930 help=(
931 "Extra flags to control staging. Valid flags are - "
932 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
933 ),
934 )
935 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
936 group.add_argument(
937 "--strict",
938 action="store_true",
939 default=False,
940 help='Deprecated. Default behavior is "strict". Use '
941 "--sloppy to omit warnings for missing optional "
942 "files.",
943 )
944 group.add_argument(
945 "--strip-flags",
946 default=None,
947 help="Flags to call the 'strip' binutil tool with. "
948 "Overrides the default arguments.",
949 )
950 group.add_argument(
951 "--ping",
952 action="store_true",
953 default=False,
954 help="Ping the device before connection attempt.",
955 )
956 group.add_argument(
957 "--process-timeout",
958 type=int,
959 default=KILL_PROC_MAX_WAIT,
960 help="Timeout for process shutdown.",
961 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700962
Alex Klein1699fab2022-09-08 08:46:06 -0600963 group = parser.add_argument_group(
964 "Metadata Overrides (Advanced)",
965 description="Provide all of these overrides in order to remove "
966 "dependencies on metadata.json existence.",
967 )
968 group.add_argument(
969 "--target-tc",
970 action="store",
971 default=None,
972 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
973 )
974 group.add_argument(
975 "--toolchain-url",
976 action="store",
977 default=None,
978 help="Override toolchain url format pattern, e.g. "
979 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
980 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700981
Alex Klein1699fab2022-09-08 08:46:06 -0600982 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
983 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
984 parser.add_argument(
985 "--gyp-defines",
986 default=None,
987 type=ValidateStagingFlags,
988 help=argparse.SUPPRESS,
989 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700990
Alex Klein1699fab2022-09-08 08:46:06 -0600991 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600992 # when --build-dir is set. Defaults to reading from the GN_ARGS env
993 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600994 parser.add_argument(
995 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
996 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700997
Alex Klein1699fab2022-09-08 08:46:06 -0600998 # Path of an empty directory to stage chrome artifacts to. Defaults to a
999 # temporary directory that is removed when the script finishes. If the path
1000 # is specified, then it will not be removed.
1001 parser.add_argument(
1002 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
1003 )
1004 # Only prepare the staging directory, and skip deploying to the device.
1005 parser.add_argument(
1006 "--staging-only",
1007 action="store_true",
1008 default=False,
1009 help=argparse.SUPPRESS,
1010 )
1011 # Uploads the compressed staging directory to the given gs:// path URI.
1012 parser.add_argument(
1013 "--staging-upload",
1014 type="gs_path",
1015 help="GS path to upload the compressed staging files to.",
1016 )
1017 # Used alongside --staging-upload to upload with public-read ACL.
1018 parser.add_argument(
1019 "--public-read",
1020 action="store_true",
1021 default=False,
1022 help="GS path to upload the compressed staging files to.",
1023 )
1024 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
1025 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
1026 # fetching the SDK toolchain.
1027 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
1028 parser.add_argument(
1029 "--compress",
1030 action="store",
1031 default="auto",
1032 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001033 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001034 'is set to "auto", that disables compression if '
1035 "the target device has a gigabit ethernet port.",
1036 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001037 parser.add_argument(
1038 "--compressed-ash",
1039 action="store_true",
1040 default=False,
1041 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1042 "binary is stored on DUT in squashfs, mounted upon boot.",
1043 )
Alex Klein1699fab2022-09-08 08:46:06 -06001044 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001045
Ryan Cuie535b172012-10-19 18:25:03 -07001046
1047def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001048 """Parse args, and run environment-independent checks."""
1049 parser = _CreateParser()
1050 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001051
Alex Klein1699fab2022-09-08 08:46:06 -06001052 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1053 parser.error(
1054 "Need to specify either --gs-path, --local-pkg-path, or "
1055 "--build-dir"
1056 )
1057 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1058 parser.error(
1059 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1060 )
1061 if options.lacros:
1062 if options.dostrip and not options.board:
1063 parser.error("Please specify --board.")
1064 if options.mount_dir or options.mount:
1065 parser.error("--lacros does not support --mount or --mount-dir")
1066 if options.deploy_test_binaries:
1067 parser.error("--lacros does not support --deploy-test-binaries")
1068 if options.local_pkg_path:
1069 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001070 if options.compressed_ash:
1071 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001072 else:
1073 if not options.board and options.build_dir:
1074 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1075 if match:
1076 options.board = match.group(1)
1077 logging.info("--board is set to %s", options.board)
1078 if not options.board:
1079 parser.error("--board is required")
1080 if options.gs_path and options.local_pkg_path:
1081 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1082 if not (options.staging_only or options.device):
1083 parser.error("Need to specify --device")
1084 if options.staging_flags and not options.build_dir:
1085 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001086
Alex Klein1699fab2022-09-08 08:46:06 -06001087 if options.strict:
1088 logging.warning("--strict is deprecated.")
1089 if options.gyp_defines:
1090 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001091
Alex Klein1699fab2022-09-08 08:46:06 -06001092 if options.mount or options.mount_dir:
1093 if not options.target_dir:
1094 options.target_dir = _CHROME_DIR_MOUNT
1095 else:
1096 if not options.target_dir:
1097 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001098
Alex Klein1699fab2022-09-08 08:46:06 -06001099 if options.mount and not options.mount_dir:
1100 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001101
Alex Klein1699fab2022-09-08 08:46:06 -06001102 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001103
1104
Mike Frysingerc3061a62015-06-04 04:16:18 -04001105def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001106 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001107
Alex Klein1699fab2022-09-08 08:46:06 -06001108 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001109 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001110 """
1111 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1112 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001113
Alex Klein1699fab2022-09-08 08:46:06 -06001114 if not options.gn_args:
1115 gn_env = os.getenv("GN_ARGS")
1116 if gn_env is not None:
1117 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1118 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001119
Alex Klein1699fab2022-09-08 08:46:06 -06001120 if not options.staging_flags:
1121 use_env = os.getenv("USE")
1122 if use_env is not None:
1123 options.staging_flags = " ".join(
1124 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1125 )
1126 logging.info(
1127 "Staging flags taken from USE in environment: %s",
1128 options.staging_flags,
1129 )
Steven Bennetts60600462016-05-12 10:40:20 -07001130
Ryan Cuia56a71e2012-10-18 18:40:35 -07001131
Ryan Cui504db722013-01-22 11:48:01 -08001132def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001133 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001134
Alex Klein1699fab2022-09-08 08:46:06 -06001135 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001136 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001137 """
1138 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1139 files = gs_ctx.LS(gs_path)
1140 files = [
1141 found
1142 for found in files
1143 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1144 ]
1145 if not files:
1146 raise Exception("No chrome package found at %s" % gs_path)
1147 elif len(files) > 1:
1148 # - Users should provide us with a direct link to either a stripped or
1149 # unstripped chrome package.
1150 # - In the case of being provided with an archive directory, where both
1151 # stripped and unstripped chrome available, use the stripped chrome
1152 # package.
1153 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001154 # - Unstripped chrome pkg is
1155 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001156 files = [f for f in files if not "unstripped" in f]
1157 assert len(files) == 1
1158 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001159
Alex Klein1699fab2022-09-08 08:46:06 -06001160 filename = _UrlBaseName(files[0])
1161 logging.info("Fetching %s...", filename)
1162 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1163 chrome_path = os.path.join(tempdir, filename)
1164 assert os.path.exists(chrome_path)
1165 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001166
1167
Ryan Cuif890a3e2013-03-07 18:57:06 -08001168@contextlib.contextmanager
1169def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001170 if not options.dostrip:
1171 yield None
1172 elif options.strip_bin:
1173 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001174 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001175 sdk = cros_chrome_sdk.SDKFetcher(
1176 options.cache_dir,
1177 options.board,
1178 use_external_config=options.use_external_config,
1179 )
1180 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1181 with sdk.Prepare(
1182 components=components,
1183 target_tc=options.target_tc,
1184 toolchain_url=options.toolchain_url,
1185 ) as ctx:
1186 env_path = os.path.join(
1187 ctx.key_map[constants.CHROME_ENV_TAR].path,
1188 constants.CHROME_ENV_FILE,
1189 )
1190 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1191 strip_bin = os.path.join(
1192 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1193 "bin",
1194 os.path.basename(strip_bin),
1195 )
1196 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001197
Alex Klein1699fab2022-09-08 08:46:06 -06001198
1199def _UploadStagingDir(
1200 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1201) -> None:
1202 """Uploads the compressed staging directory.
1203
1204 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001205 options: options object.
1206 tempdir: Scratch space.
1207 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001208 """
1209 staging_tarball_path = os.path.join(
1210 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1211 )
1212 logging.info(
1213 "Compressing staging dir (%s) to (%s)",
1214 staging_dir,
1215 staging_tarball_path,
1216 )
1217 cros_build_lib.CreateTarball(
1218 staging_tarball_path,
1219 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001220 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001221 extra_env={"ZSTD_CLEVEL": "9"},
1222 )
1223 logging.info(
1224 "Uploading staging tarball (%s) into %s",
1225 staging_tarball_path,
1226 options.staging_upload,
1227 )
1228 ctx = gs.GSContext()
1229 ctx.Copy(
1230 staging_tarball_path,
1231 options.staging_upload,
1232 acl="public-read" if options.public_read else "",
1233 )
1234
1235
1236def _PrepareStagingDir(
1237 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1238):
1239 """Place the necessary files in the staging directory.
1240
Alex Klein68b270c2023-04-14 14:42:50 -06001241 The staging directory is the directory used to rsync the build artifacts
1242 over to the device. Only the necessary Chrome build artifacts are put into
1243 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001244 """
1245 if chrome_dir is None:
1246 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1247 osutils.SafeMakedirs(staging_dir)
1248 os.chmod(staging_dir, 0o755)
1249 if options.build_dir:
1250 with _StripBinContext(options) as strip_bin:
1251 strip_flags = (
1252 None
1253 if options.strip_flags is None
1254 else shlex.split(options.strip_flags)
1255 )
1256 chrome_util.StageChromeFromBuildDir(
1257 staging_dir,
1258 options.build_dir,
1259 strip_bin,
1260 sloppy=options.sloppy,
1261 gn_args=options.gn_args,
1262 staging_flags=options.staging_flags,
1263 strip_flags=strip_flags,
1264 copy_paths=copy_paths,
1265 )
1266 else:
1267 pkg_path = options.local_pkg_path
1268 if options.gs_path:
1269 pkg_path = _FetchChromePackage(
1270 options.cache_dir, tempdir, options.gs_path
1271 )
1272
1273 assert pkg_path
1274 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001275 # Extract only the ./opt/google/chrome contents, directly into the
1276 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001277 if pkg_path[-4:] == ".zip":
1278 cros_build_lib.dbg_run(
1279 [
1280 "unzip",
1281 "-X",
1282 pkg_path,
1283 _ANDROID_DIR_EXTRACT_PATH,
1284 "-d",
1285 staging_dir,
1286 ]
1287 )
1288 for filename in glob.glob(
1289 os.path.join(staging_dir, "system/chrome/*")
1290 ):
1291 shutil.move(filename, staging_dir)
1292 osutils.RmDir(
1293 os.path.join(staging_dir, "system"), ignore_missing=True
1294 )
1295 else:
1296 compression = cros_build_lib.CompressionDetectType(pkg_path)
1297 compressor = cros_build_lib.FindCompressor(compression)
Cindy Lin0b043c42023-06-23 20:59:26 +00001298 if compression == cros_build_lib.CompressionType.ZSTD:
1299 compressor += " -f"
Alex Klein1699fab2022-09-08 08:46:06 -06001300 cros_build_lib.dbg_run(
1301 [
1302 "tar",
1303 "--strip-components",
1304 "4",
1305 "--extract",
1306 "-I",
1307 compressor,
1308 "--preserve-permissions",
1309 "--file",
1310 pkg_path,
1311 ".%s" % chrome_dir,
1312 ],
1313 cwd=staging_dir,
1314 )
1315
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001316 if options.compressed_ash:
1317 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1318 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001319 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1320 # this is in test, cut the known suffix of experimental overlays.
1321 sdk_orig_board = options.board
1322 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1323 sdk_orig_board = sdk_orig_board[
1324 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1325 ]
1326
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001327 sdk = cros_chrome_sdk.SDKFetcher(
1328 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001329 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001330 use_external_config=options.use_external_config,
1331 )
1332 with sdk.Prepare(
1333 components=[],
1334 target_tc=options.target_tc,
1335 toolchain_url=options.toolchain_url,
1336 ):
1337 cros_build_lib.dbg_run(
1338 [
1339 "mksquashfs",
1340 RAW_ASH_FILE,
1341 COMPRESSED_ASH_FILE,
1342 "-all-root",
1343 "-no-progress",
1344 "-comp",
1345 "zstd",
1346 ],
1347 cwd=staging_dir,
1348 )
1349 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1350
Alex Klein1699fab2022-09-08 08:46:06 -06001351 if options.staging_upload:
1352 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001353
Ryan Cui71aa8de2013-04-19 16:12:55 -07001354
Ryan Cui3045c5d2012-07-13 18:00:33 -07001355def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001356 options = _ParseCommandLine(argv)
1357 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001358
Alex Klein1699fab2022-09-08 08:46:06 -06001359 with osutils.TempDir(set_global=True) as tempdir:
1360 staging_dir = options.staging_dir
1361 if not staging_dir:
1362 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001363
Alex Klein1699fab2022-09-08 08:46:06 -06001364 deploy = DeployChrome(options, tempdir, staging_dir)
1365 try:
1366 deploy.Perform()
1367 except failures_lib.StepFailure as ex:
1368 raise SystemExit(str(ex).strip())
1369 deploy.Cleanup()