blob: 4701481f83288036ee40554e020ba511017e9097 [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.
Alex Klein1699fab2022-09-08 08:46:06 -0600237 cmd = (
238 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
239 "--remove_rootfs_verification --force"
240 )
241 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
242 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700243
Mike Frysinger76f632a2023-07-19 13:39:55 -0400244 self.device.Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700245
Alex Klein1699fab2022-09-08 08:46:06 -0600246 # Now that the machine has been rebooted, we need to kill Chrome again.
247 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 # Make sure the rootfs is writable now.
250 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 def _CheckUiJobStarted(self):
255 # status output is in the format:
256 # <job_name> <status> ['process' <pid>].
257 # <status> is in the format <goal>/<state>.
258 try:
259 result = self.device.run(
260 "status ui", capture_output=True, encoding="utf-8"
261 )
262 except cros_build_lib.RunCommandError as e:
263 if "Unknown job" in e.stderr:
264 return False
265 else:
266 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800267
Alex Klein1699fab2022-09-08 08:46:06 -0600268 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 def _KillLacrosChrome(self):
271 """This method kills lacros-chrome on the device, if it's running."""
Eliot Courtneyaf474342023-07-19 14:20:25 +0900272 # Mark the lacros chrome binary as not executable, so if keep-alive is
273 # enabled ash chrome can't restart lacros chrome. This prevents rsync
274 # from failing if the file is still in use (being executed by ash
275 # chrome). Note that this will cause ash chrome to continuously attempt
276 # to start lacros and fail, although it doesn't seem to cause issues.
277 if self.options.skip_restart_ui:
278 self.device.run(
279 ["chmod", "-x", f"{self.options.target_dir}/chrome"],
280 check=False,
281 )
Alex Klein1699fab2022-09-08 08:46:06 -0600282 self.device.run(
283 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
284 check=False,
285 )
Erik Chen75a2f492020-08-06 19:15:11 -0700286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 def _ResetLacrosChrome(self):
288 """Reset Lacros to fresh state by deleting user data dir."""
289 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 def _KillAshChromeIfNeeded(self):
292 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700293
Alex Klein68b270c2023-04-14 14:42:50 -0600294 This method calls 'stop ui', and then also manually pkills both
295 ash-chrome and the session manager.
Alex Klein1699fab2022-09-08 08:46:06 -0600296 """
297 if self._CheckUiJobStarted():
298 logging.info("Shutting down Chrome...")
299 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 # Developers sometimes run session_manager manually, in which case we'll
302 # need to help shut the chrome processes down.
303 try:
304 with timeout_util.Timeout(self.options.process_timeout):
305 while self._ChromeFileInUse():
306 logging.warning(
307 "The chrome binary on the device is in use."
308 )
309 logging.warning(
310 "Killing chrome and session_manager processes...\n"
311 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 self.device.run(
314 "pkill 'chrome|session_manager'", check=False
315 )
316 # Wait for processes to actually terminate
317 time.sleep(POST_KILL_WAIT)
318 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000319 if self.options.compressed_ash:
320 result = self.device.run(
321 ["umount", RAW_ASH_PATH],
322 check=False,
323 capture_output=True,
324 )
325 if result.returncode and not (
326 result.returncode == 32
327 and "not mounted" in result.stderr
328 ):
329 raise DeployFailure(
330 "Could not unmount compressed ash. "
331 f"Error Code: {result.returncode}, "
332 f"Error Message: {result.stderr}"
333 )
Alex Klein1699fab2022-09-08 08:46:06 -0600334 except timeout_util.TimeoutError:
335 msg = (
336 "Could not kill processes after %s seconds. Please exit any "
337 "running chrome processes and try again."
338 % self.options.process_timeout
339 )
340 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700341
Alex Klein1699fab2022-09-08 08:46:06 -0600342 def _MountRootfsAsWritable(self, check=False):
343 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700344
Alex Klein1699fab2022-09-08 08:46:06 -0600345 If the command fails and the root dir is not writable then this function
346 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700347
Alex Klein1699fab2022-09-08 08:46:06 -0600348 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600349 check: See remote.RemoteAccess.RemoteSh for details.
Alex Klein1699fab2022-09-08 08:46:06 -0600350 """
351 # TODO: Should migrate to use the remount functions in remote_access.
352 result = self.device.run(
353 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
354 )
355 if result.returncode and not self.device.IsDirWritable("/"):
356 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700357
Alex Klein1699fab2022-09-08 08:46:06 -0600358 def _EnsureTargetDir(self):
359 """Ensures that the target directory exists on the remote device."""
360 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600361 # Any valid /opt directory should already exist so avoid the remote
362 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600363 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
364 return
Mike Frysinger445f6bf2023-06-27 11:43:33 -0400365 self.device.mkdir(target_dir, mode=0o775)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600368 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600369 steps = [
370 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
371 functools.partial(
372 self._GetRemoteMountFree, self.options.target_dir
373 ),
374 ]
375 return_values = parallel.RunParallelSteps(steps, return_values=True)
376 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700377
Alex Klein1699fab2022-09-08 08:46:06 -0600378 def _CheckDeviceFreeSpace(self, device_info):
379 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600382 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600383 """
384 effective_free = (
385 device_info.target_dir_size + device_info.target_fs_free
386 )
387 staging_size = self._GetStagingDirSize()
388 if effective_free < staging_size:
389 raise DeployFailure(
390 "Not enough free space on the device. Required: %s MiB, "
391 "actual: %s MiB."
392 % (staging_size // 1024, effective_free // 1024)
393 )
394 if device_info.target_fs_free < (100 * 1024):
395 logging.warning(
396 "The device has less than 100MB free. deploy_chrome may "
397 "hang during the transfer."
398 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700399
Alex Klein1699fab2022-09-08 08:46:06 -0600400 def _ShouldUseCompression(self):
401 """Checks if compression should be used for rsync."""
402 if self.options.compress == "always":
403 return True
404 elif self.options.compress == "never":
405 return False
406 elif self.options.compress == "auto":
407 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900408
Alex Klein1699fab2022-09-08 08:46:06 -0600409 def _Deploy(self):
410 logging.info(
411 "Copying %s to %s on device...",
412 self._deployment_name,
413 self.options.target_dir,
414 )
415 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
416 # This does not work for deploy.
417 if not self.device.HasRsync():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200418 # This assumes that rsync is part of the bootstrap package. In the
419 # future, this might change and we'll have to install it separately.
420 if not cros_build_lib.BooleanPrompt(
421 "Run dev_install on the device to install rsync?", True
422 ):
423 raise DeployFailure("rsync is not found on the device.")
424 self.device.BootstrapDevTools()
425 if not self.device.HasRsync():
426 raise DeployFailure("Failed to install rsync")
427
Eliot Courtneyaf474342023-07-19 14:20:25 +0900428 try:
429 staging_dir = os.path.abspath(self.staging_dir)
430 staging_chrome = os.path.join(staging_dir, "chrome")
431
432 if (
433 self.options.lacros
434 and self.options.skip_restart_ui
435 and os.path.exists(staging_chrome)
436 ):
437 # Make the chrome binary not executable before deploying to
438 # prevent ash chrome from starting chrome before the rsync has
439 # finished.
440 os.chmod(staging_chrome, 0o644)
441
442 self.device.CopyToDevice(
443 f"{staging_dir}/",
444 self.options.target_dir,
445 mode="rsync",
446 inplace=True,
447 compress=self._ShouldUseCompression(),
448 debug_level=logging.INFO,
449 verbose=self.options.verbose,
450 )
451 finally:
452 if self.options.lacros and self.options.skip_restart_ui:
453 self.device.run(
454 ["chmod", "+x", f"{self.options.target_dir}/chrome"],
455 check=False,
456 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700457
Alex Klein68b270c2023-04-14 14:42:50 -0600458 # Set the security context on the default Chrome dir if that's where
459 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600460 if (
461 not self.options.lacros
462 and self.device.IsSELinuxAvailable()
463 and (
464 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
465 )
466 ):
467 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800468
Alex Klein1699fab2022-09-08 08:46:06 -0600469 for p in self.copy_paths:
470 if p.mode:
471 # Set mode if necessary.
472 self.device.run(
473 "chmod %o %s/%s"
474 % (
475 p.mode,
476 self.options.target_dir,
477 p.src if not p.dest else p.dest,
478 )
479 )
Steve Funge984a532013-11-25 17:09:25 -0800480
Alex Klein1699fab2022-09-08 08:46:06 -0600481 if self.options.lacros:
482 self.device.run(
483 ["chown", "-R", "chronos:chronos", self.options.target_dir]
484 )
Erik Chen75a2f492020-08-06 19:15:11 -0700485
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000486 if self.options.compressed_ash:
487 self.device.run(["start", COMPRESSED_ASH_SERVICE])
488
Alex Klein68b270c2023-04-14 14:42:50 -0600489 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
490 # won't pick up major changes (bus type, logging, etc.), but all we care
491 # about is getting the latest policy from /opt/google/chrome/dbus so
492 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600493 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600494
Erik Chen75a2f492020-08-06 19:15:11 -0700495 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700496 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600497 logging.info("Starting UI...")
498 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800499
Joel Hockey764728e2023-03-14 17:10:04 -0700500 if self.options.unlock_password:
501 logging.info("Unlocking...")
502
503 @retry_util.WithRetry(max_retry=5, sleep=1)
504 def WaitForUnlockScreen():
505 if self._GetLastLogin() == last_login:
506 raise DeployFailure("Unlock screen not shown")
507
508 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700509 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700510 self.device.run(
511 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
512 )
513
514 def _GetLastLogin(self):
515 """Returns last login time"""
516 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
517
Alex Klein1699fab2022-09-08 08:46:06 -0600518 def _DeployTestBinaries(self):
519 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700520
Alex Klein68b270c2023-04-14 14:42:50 -0600521 There could be several binaries located in the local build dir, so
522 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
523 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600524 """
525 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
526 if r.returncode != 0:
527 raise DeployFailure(
528 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
529 )
530 binaries_to_copy = []
531 for f in r.stdout.splitlines():
532 binaries_to_copy.append(
533 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
534 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700535
Alex Klein1699fab2022-09-08 08:46:06 -0600536 staging_dir = os.path.join(
537 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
538 )
539 _PrepareStagingDir(
540 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
541 )
542 # Deploying can occasionally run into issues with rsync getting a broken
543 # pipe, so retry several times. See crbug.com/1141618 for more
544 # information.
545 retry_util.RetryException(
546 None,
547 3,
548 self.device.CopyToDevice,
549 staging_dir,
550 os.path.dirname(_CHROME_TEST_BIN_DIR),
551 mode="rsync",
552 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700553
Alex Klein1699fab2022-09-08 08:46:06 -0600554 def _CheckBoard(self):
555 """Check that the Chrome build is targeted for the device board."""
556 if self.options.board == self.device.board:
557 return
558 logging.warning(
559 "Device board is %s whereas target board is %s.",
560 self.device.board,
561 self.options.board,
562 )
563 if self.options.force:
564 return
565 if not cros_build_lib.BooleanPrompt(
566 "Continue despite board mismatch?", False
567 ):
568 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800569
Alex Klein1699fab2022-09-08 08:46:06 -0600570 def _CheckDeployType(self):
571 if self.options.build_dir:
572
573 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600574 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600575 return os.path.exists(
576 os.path.join(self.options.build_dir, filename)
577 )
578
579 # In the future, lacros-chrome and ash-chrome will likely be named
580 # something other than 'chrome' to avoid confusion.
581 # Handle non-Chrome deployments.
582 if not BinaryExists("chrome"):
583 if BinaryExists("app_shell"):
584 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
585
586 def _PrepareStagingDir(self):
587 _PrepareStagingDir(
588 self.options,
589 self.tempdir,
590 self.staging_dir,
591 self.copy_paths,
592 self.chrome_dir,
593 )
594
595 def _MountTarget(self):
596 logging.info("Mounting Chrome...")
597
598 # Create directory if does not exist.
599 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
600 try:
601 # Umount the existing mount on mount_dir if present first.
602 self.device.run(
603 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
604 )
605 except cros_build_lib.RunCommandError as e:
606 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600607 # If there is a failure, check if some process is using the
608 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600609 result = self.device.run(
610 LSOF_COMMAND % (self.options.mount_dir,),
611 check=False,
612 capture_output=True,
613 encoding="utf-8",
614 )
615 logging.error("lsof %s -->", self.options.mount_dir)
616 logging.error(result.stdout)
617 raise e
618
619 self.device.run(
620 _BIND_TO_FINAL_DIR_CMD
621 % (self.options.target_dir, self.options.mount_dir)
622 )
623
624 # Chrome needs partition to have exec and suid flags set
625 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
626
627 def Cleanup(self):
628 """Clean up RemoteDevice."""
629 if not self.options.staging_only:
630 self.device.Cleanup()
631
632 def Perform(self):
633 self._CheckDeployType()
634
635 # If requested, just do the staging step.
636 if self.options.staging_only:
637 self._PrepareStagingDir()
638 return 0
639
Alex Klein68b270c2023-04-14 14:42:50 -0600640 # Check that the build matches the device. Lacros-chrome skips this
641 # check as it's currently board independent. This means that it's
642 # possible to deploy a build of lacros-chrome with a mismatched
643 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600644 if not self.options.lacros:
645 self._CheckBoard()
646
647 # Ensure that the target directory exists before running parallel steps.
648 self._EnsureTargetDir()
649
650 logging.info("Preparing device")
651 steps = [
652 self._GetDeviceInfo,
Alex Klein1699fab2022-09-08 08:46:06 -0600653 self._MountRootfsAsWritable,
654 self._PrepareStagingDir,
655 ]
656
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900657 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600658 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600659 steps.append(self._KillLacrosChrome)
660 if self.options.reset_lacros:
661 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900662 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600663 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900664 config_modified = self._ModifyConfigFileIfNeededForLacros()
665 if config_modified and not restart_ui:
666 logging.warning(
667 "Config file modified but skipping restart_ui "
668 "due to option --skip-restart-ui. Config file "
669 "update is not reflected."
670 )
Alex Klein1699fab2022-09-08 08:46:06 -0600671
672 if restart_ui:
673 steps.append(self._KillAshChromeIfNeeded)
674 self._stopped_ui = True
675
676 ret = parallel.RunParallelSteps(
677 steps, halt_on_error=True, return_values=True
678 )
679 self._CheckDeviceFreeSpace(ret[0])
680
681 # If the root dir is not writable, try disabling rootfs verification.
682 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600683 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600684 if self._root_dir_is_still_readonly.is_set():
685 if self.options.noremove_rootfs_verification:
686 logging.warning("Skipping disable rootfs verification.")
687 elif not self._DisableRootfsVerification():
688 logging.warning("Failed to disable rootfs verification.")
689
Alex Klein68b270c2023-04-14 14:42:50 -0600690 # If the target dir is still not writable (i.e. the user opted out
691 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600692 if not self.device.IsDirWritable(self.options.target_dir):
693 if self.options.startui and self._stopped_ui:
694 logging.info("Restarting Chrome...")
695 self.device.run("start ui")
696 raise DeployFailure(
697 "Target location is not writable. Aborting."
698 )
699
700 if self.options.mount_dir is not None:
701 self._MountTarget()
702
703 # Actually deploy Chrome to the device.
704 self._Deploy()
705 if self.options.deploy_test_binaries:
706 self._DeployTestBinaries()
707
708 def _ModifyConfigFileIfNeededForLacros(self):
709 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
710
711 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600712 True if the file is modified, and the return value is usually used
713 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600714 """
715 assert (
716 self.options.lacros
717 ), "Only deploying lacros-chrome needs to modify the config file."
718 # Update /etc/chrome_dev.conf to include appropriate flags.
719 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000720 if self.options.enable_lacros_support:
721 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
722 if result.stdout.strip() == MODIFIED_CONF_FILE:
723 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600724 result = self.device.run(
725 _SET_LACROS_PATH_VIA_CONF_COMMAND
726 % {
727 "conf_file": _CONF_FILE,
728 "lacros_path": self.options.target_dir,
729 "modified_conf_file": MODIFIED_CONF_FILE,
730 },
731 shell=True,
732 )
733 if result.stdout.strip() == MODIFIED_CONF_FILE:
734 modified = True
735
736 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800737
738
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700739def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600740 """Convert formatted string to dictionary."""
741 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700742
743
Steven Bennetts368c3e52016-09-23 13:05:21 -0700744def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600745 """Convert GN_ARGS-formatted string to dictionary."""
746 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700747
748
Ryan Cuie535b172012-10-19 18:25:03 -0700749def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600750 """Create our custom parser."""
751 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700752
Alex Klein1699fab2022-09-08 08:46:06 -0600753 # TODO(rcui): Have this use the UI-V2 format of having source and target
754 # device be specified as positional arguments.
755 parser.add_argument(
756 "--force",
757 action="store_true",
758 default=False,
759 help="Skip all prompts (such as the prompt for disabling "
760 "of rootfs verification). This may result in the "
761 "target machine being rebooted.",
762 )
763 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
764 parser.add_argument(
765 "--board",
766 default=sdk_board_env,
767 help="The board the Chrome build is targeted for. When "
768 "in a 'cros chrome-sdk' shell, defaults to the SDK "
769 "board.",
770 )
771 parser.add_argument(
772 "--build-dir",
773 type="path",
774 help="The directory with Chrome build artifacts to "
775 "deploy from. Typically of format "
776 "<chrome_root>/out/Debug. When this option is used, "
777 "the GN_ARGS environment variable must be set.",
778 )
779 parser.add_argument(
780 "--target-dir",
781 type="path",
782 default=None,
783 help="Target directory on device to deploy Chrome into.",
784 )
785 parser.add_argument(
786 "-g",
787 "--gs-path",
788 type="gs_path",
789 help="GS path that contains the chrome to deploy.",
790 )
791 parser.add_argument(
792 "--private-key",
793 type="path",
794 default=None,
795 help="An ssh private key to use when deploying to " "a CrOS device.",
796 )
797 parser.add_argument(
798 "--nostartui",
799 action="store_false",
800 dest="startui",
801 default=True,
802 help="Don't restart the ui daemon after deployment.",
803 )
804 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700805 "--unlock-password",
806 default=None,
807 help="Password to use to unlock after deployment and restart.",
808 )
809 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600810 "--nostrip",
811 action="store_false",
812 dest="dostrip",
813 default=True,
814 help="Don't strip binaries during deployment. Warning: "
815 "the resulting binaries will be very large!",
816 )
817 parser.add_argument(
818 "-d",
819 "--device",
820 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
821 help="Device hostname or IP in the format hostname[:port].",
822 )
823 parser.add_argument(
824 "--mount-dir",
825 type="path",
826 default=None,
827 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000828 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600829 "Any existing mount on this directory will be "
830 "umounted first.",
831 )
832 parser.add_argument(
833 "--mount",
834 action="store_true",
835 default=False,
836 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000837 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600838 "Any existing mount on this directory will be "
839 "umounted first.",
840 )
841 parser.add_argument(
842 "--noremove-rootfs-verification",
843 action="store_true",
844 default=False,
845 help="Never remove rootfs verification.",
846 )
847 parser.add_argument(
848 "--deploy-test-binaries",
849 action="store_true",
850 default=False,
851 help="Also deploy any test binaries to %s. Useful for "
852 "running any Tast tests that execute these "
853 "binaries." % _CHROME_TEST_BIN_DIR,
854 )
855 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600856 "--use-external-config",
857 action="store_true",
858 help="When identifying the configuration for a board, "
859 "force usage of the external configuration if both "
860 "internal and external are available. This only "
861 "has an effect when stripping Chrome, i.e. when "
862 "--nostrip is not passed in.",
863 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700864
Georg Neis9b1ff192022-09-14 08:07:22 +0000865 group = parser.add_argument_group("Lacros Options")
866 group.add_argument(
867 "--lacros",
868 action="store_true",
869 default=False,
870 help="Deploys lacros-chrome rather than ash-chrome.",
871 )
872 group.add_argument(
873 "--reset-lacros",
874 action="store_true",
875 default=False,
876 help="Reset Lacros by deleting Lacros user data dir if it exists.",
877 )
878 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900879 "--skip-restart-ui",
880 action="store_true",
881 default=False,
882 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
883 "that this flag may cause ETXTBSY error on rsync, and also won't "
884 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
885 )
886 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000887 "--skip-enabling-lacros-support",
888 action="store_false",
889 dest="enable_lacros_support",
890 help="By default, deploying lacros-chrome modifies the "
891 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
892 "and (2) set the Lacros path, which can interfere with automated "
893 "testing. With this flag, part (1) will be skipped. See the "
894 "--skip-modifying-config-file flag for skipping both parts.",
895 )
896 group.add_argument(
897 "--skip-modifying-config-file",
898 action="store_false",
899 dest="modify_config_file",
900 help="When deploying lacros-chrome, do not modify the "
901 "/etc/chrome_dev.conf file. See also the "
902 "--skip-enabling-lacros-support flag.",
903 )
904
Alex Klein1699fab2022-09-08 08:46:06 -0600905 group = parser.add_argument_group("Advanced Options")
906 group.add_argument(
907 "-l",
908 "--local-pkg-path",
909 type="path",
910 help="Path to local chrome prebuilt package to deploy.",
911 )
912 group.add_argument(
913 "--sloppy",
914 action="store_true",
915 default=False,
916 help="Ignore when mandatory artifacts are missing.",
917 )
918 group.add_argument(
919 "--staging-flags",
920 default=None,
921 type=ValidateStagingFlags,
922 help=(
923 "Extra flags to control staging. Valid flags are - "
924 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
925 ),
926 )
927 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
928 group.add_argument(
929 "--strict",
930 action="store_true",
931 default=False,
932 help='Deprecated. Default behavior is "strict". Use '
933 "--sloppy to omit warnings for missing optional "
934 "files.",
935 )
936 group.add_argument(
937 "--strip-flags",
938 default=None,
939 help="Flags to call the 'strip' binutil tool with. "
940 "Overrides the default arguments.",
941 )
942 group.add_argument(
943 "--ping",
944 action="store_true",
945 default=False,
946 help="Ping the device before connection attempt.",
947 )
948 group.add_argument(
949 "--process-timeout",
950 type=int,
951 default=KILL_PROC_MAX_WAIT,
952 help="Timeout for process shutdown.",
953 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700954
Alex Klein1699fab2022-09-08 08:46:06 -0600955 group = parser.add_argument_group(
956 "Metadata Overrides (Advanced)",
957 description="Provide all of these overrides in order to remove "
958 "dependencies on metadata.json existence.",
959 )
960 group.add_argument(
961 "--target-tc",
962 action="store",
963 default=None,
964 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
965 )
966 group.add_argument(
967 "--toolchain-url",
968 action="store",
969 default=None,
970 help="Override toolchain url format pattern, e.g. "
971 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
972 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700973
Alex Klein1699fab2022-09-08 08:46:06 -0600974 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
975 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
976 parser.add_argument(
977 "--gyp-defines",
978 default=None,
979 type=ValidateStagingFlags,
980 help=argparse.SUPPRESS,
981 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700982
Alex Klein1699fab2022-09-08 08:46:06 -0600983 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600984 # when --build-dir is set. Defaults to reading from the GN_ARGS env
985 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600986 parser.add_argument(
987 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
988 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700989
Alex Klein1699fab2022-09-08 08:46:06 -0600990 # Path of an empty directory to stage chrome artifacts to. Defaults to a
991 # temporary directory that is removed when the script finishes. If the path
992 # is specified, then it will not be removed.
993 parser.add_argument(
994 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
995 )
996 # Only prepare the staging directory, and skip deploying to the device.
997 parser.add_argument(
998 "--staging-only",
999 action="store_true",
1000 default=False,
1001 help=argparse.SUPPRESS,
1002 )
1003 # Uploads the compressed staging directory to the given gs:// path URI.
1004 parser.add_argument(
1005 "--staging-upload",
1006 type="gs_path",
1007 help="GS path to upload the compressed staging files to.",
1008 )
1009 # Used alongside --staging-upload to upload with public-read ACL.
1010 parser.add_argument(
1011 "--public-read",
1012 action="store_true",
1013 default=False,
1014 help="GS path to upload the compressed staging files to.",
1015 )
1016 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
1017 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
1018 # fetching the SDK toolchain.
1019 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
1020 parser.add_argument(
1021 "--compress",
1022 action="store",
1023 default="auto",
1024 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001025 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001026 'is set to "auto", that disables compression if '
1027 "the target device has a gigabit ethernet port.",
1028 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001029 parser.add_argument(
1030 "--compressed-ash",
1031 action="store_true",
1032 default=False,
1033 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1034 "binary is stored on DUT in squashfs, mounted upon boot.",
1035 )
Alex Klein1699fab2022-09-08 08:46:06 -06001036 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001037
Ryan Cuie535b172012-10-19 18:25:03 -07001038
1039def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001040 """Parse args, and run environment-independent checks."""
1041 parser = _CreateParser()
1042 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001043
Alex Klein1699fab2022-09-08 08:46:06 -06001044 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1045 parser.error(
1046 "Need to specify either --gs-path, --local-pkg-path, or "
1047 "--build-dir"
1048 )
1049 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1050 parser.error(
1051 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1052 )
1053 if options.lacros:
1054 if options.dostrip and not options.board:
1055 parser.error("Please specify --board.")
1056 if options.mount_dir or options.mount:
1057 parser.error("--lacros does not support --mount or --mount-dir")
1058 if options.deploy_test_binaries:
1059 parser.error("--lacros does not support --deploy-test-binaries")
1060 if options.local_pkg_path:
1061 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001062 if options.compressed_ash:
1063 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001064 else:
1065 if not options.board and options.build_dir:
1066 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1067 if match:
1068 options.board = match.group(1)
1069 logging.info("--board is set to %s", options.board)
1070 if not options.board:
1071 parser.error("--board is required")
1072 if options.gs_path and options.local_pkg_path:
1073 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1074 if not (options.staging_only or options.device):
1075 parser.error("Need to specify --device")
1076 if options.staging_flags and not options.build_dir:
1077 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001078
Alex Klein1699fab2022-09-08 08:46:06 -06001079 if options.strict:
1080 logging.warning("--strict is deprecated.")
1081 if options.gyp_defines:
1082 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001083
Alex Klein1699fab2022-09-08 08:46:06 -06001084 if options.mount or options.mount_dir:
1085 if not options.target_dir:
1086 options.target_dir = _CHROME_DIR_MOUNT
1087 else:
1088 if not options.target_dir:
1089 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001090
Alex Klein1699fab2022-09-08 08:46:06 -06001091 if options.mount and not options.mount_dir:
1092 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001093
Alex Klein1699fab2022-09-08 08:46:06 -06001094 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001095
1096
Mike Frysingerc3061a62015-06-04 04:16:18 -04001097def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001098 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001099
Alex Klein1699fab2022-09-08 08:46:06 -06001100 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001101 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001102 """
1103 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1104 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001105
Alex Klein1699fab2022-09-08 08:46:06 -06001106 if not options.gn_args:
1107 gn_env = os.getenv("GN_ARGS")
1108 if gn_env is not None:
1109 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1110 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001111
Alex Klein1699fab2022-09-08 08:46:06 -06001112 if not options.staging_flags:
1113 use_env = os.getenv("USE")
1114 if use_env is not None:
1115 options.staging_flags = " ".join(
1116 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1117 )
1118 logging.info(
1119 "Staging flags taken from USE in environment: %s",
1120 options.staging_flags,
1121 )
Steven Bennetts60600462016-05-12 10:40:20 -07001122
Ryan Cuia56a71e2012-10-18 18:40:35 -07001123
Ryan Cui504db722013-01-22 11:48:01 -08001124def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001125 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001126
Alex Klein1699fab2022-09-08 08:46:06 -06001127 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001128 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001129 """
1130 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1131 files = gs_ctx.LS(gs_path)
1132 files = [
1133 found
1134 for found in files
1135 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1136 ]
1137 if not files:
1138 raise Exception("No chrome package found at %s" % gs_path)
1139 elif len(files) > 1:
1140 # - Users should provide us with a direct link to either a stripped or
1141 # unstripped chrome package.
1142 # - In the case of being provided with an archive directory, where both
1143 # stripped and unstripped chrome available, use the stripped chrome
1144 # package.
1145 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001146 # - Unstripped chrome pkg is
1147 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001148 files = [f for f in files if not "unstripped" in f]
1149 assert len(files) == 1
1150 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001151
Alex Klein1699fab2022-09-08 08:46:06 -06001152 filename = _UrlBaseName(files[0])
1153 logging.info("Fetching %s...", filename)
1154 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1155 chrome_path = os.path.join(tempdir, filename)
1156 assert os.path.exists(chrome_path)
1157 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001158
1159
Ryan Cuif890a3e2013-03-07 18:57:06 -08001160@contextlib.contextmanager
1161def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001162 if not options.dostrip:
1163 yield None
1164 elif options.strip_bin:
1165 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001166 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001167 sdk = cros_chrome_sdk.SDKFetcher(
1168 options.cache_dir,
1169 options.board,
1170 use_external_config=options.use_external_config,
1171 )
1172 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1173 with sdk.Prepare(
1174 components=components,
1175 target_tc=options.target_tc,
1176 toolchain_url=options.toolchain_url,
1177 ) as ctx:
1178 env_path = os.path.join(
1179 ctx.key_map[constants.CHROME_ENV_TAR].path,
1180 constants.CHROME_ENV_FILE,
1181 )
1182 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1183 strip_bin = os.path.join(
1184 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1185 "bin",
1186 os.path.basename(strip_bin),
1187 )
1188 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001189
Alex Klein1699fab2022-09-08 08:46:06 -06001190
1191def _UploadStagingDir(
1192 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1193) -> None:
1194 """Uploads the compressed staging directory.
1195
1196 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001197 options: options object.
1198 tempdir: Scratch space.
1199 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001200 """
1201 staging_tarball_path = os.path.join(
1202 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1203 )
1204 logging.info(
1205 "Compressing staging dir (%s) to (%s)",
1206 staging_dir,
1207 staging_tarball_path,
1208 )
1209 cros_build_lib.CreateTarball(
1210 staging_tarball_path,
1211 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001212 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001213 extra_env={"ZSTD_CLEVEL": "9"},
1214 )
1215 logging.info(
1216 "Uploading staging tarball (%s) into %s",
1217 staging_tarball_path,
1218 options.staging_upload,
1219 )
1220 ctx = gs.GSContext()
1221 ctx.Copy(
1222 staging_tarball_path,
1223 options.staging_upload,
1224 acl="public-read" if options.public_read else "",
1225 )
1226
1227
1228def _PrepareStagingDir(
1229 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1230):
1231 """Place the necessary files in the staging directory.
1232
Alex Klein68b270c2023-04-14 14:42:50 -06001233 The staging directory is the directory used to rsync the build artifacts
1234 over to the device. Only the necessary Chrome build artifacts are put into
1235 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001236 """
1237 if chrome_dir is None:
1238 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1239 osutils.SafeMakedirs(staging_dir)
1240 os.chmod(staging_dir, 0o755)
1241 if options.build_dir:
1242 with _StripBinContext(options) as strip_bin:
1243 strip_flags = (
1244 None
1245 if options.strip_flags is None
1246 else shlex.split(options.strip_flags)
1247 )
1248 chrome_util.StageChromeFromBuildDir(
1249 staging_dir,
1250 options.build_dir,
1251 strip_bin,
1252 sloppy=options.sloppy,
1253 gn_args=options.gn_args,
1254 staging_flags=options.staging_flags,
1255 strip_flags=strip_flags,
1256 copy_paths=copy_paths,
1257 )
1258 else:
1259 pkg_path = options.local_pkg_path
1260 if options.gs_path:
1261 pkg_path = _FetchChromePackage(
1262 options.cache_dir, tempdir, options.gs_path
1263 )
1264
1265 assert pkg_path
1266 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001267 # Extract only the ./opt/google/chrome contents, directly into the
1268 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001269 if pkg_path[-4:] == ".zip":
1270 cros_build_lib.dbg_run(
1271 [
1272 "unzip",
1273 "-X",
1274 pkg_path,
1275 _ANDROID_DIR_EXTRACT_PATH,
1276 "-d",
1277 staging_dir,
1278 ]
1279 )
1280 for filename in glob.glob(
1281 os.path.join(staging_dir, "system/chrome/*")
1282 ):
1283 shutil.move(filename, staging_dir)
1284 osutils.RmDir(
1285 os.path.join(staging_dir, "system"), ignore_missing=True
1286 )
1287 else:
1288 compression = cros_build_lib.CompressionDetectType(pkg_path)
1289 compressor = cros_build_lib.FindCompressor(compression)
Cindy Lin0b043c42023-06-23 20:59:26 +00001290 if compression == cros_build_lib.CompressionType.ZSTD:
1291 compressor += " -f"
Alex Klein1699fab2022-09-08 08:46:06 -06001292 cros_build_lib.dbg_run(
1293 [
1294 "tar",
1295 "--strip-components",
1296 "4",
1297 "--extract",
1298 "-I",
1299 compressor,
1300 "--preserve-permissions",
1301 "--file",
1302 pkg_path,
1303 ".%s" % chrome_dir,
1304 ],
1305 cwd=staging_dir,
1306 )
1307
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001308 if options.compressed_ash:
1309 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1310 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001311 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1312 # this is in test, cut the known suffix of experimental overlays.
1313 sdk_orig_board = options.board
1314 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1315 sdk_orig_board = sdk_orig_board[
1316 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1317 ]
1318
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001319 sdk = cros_chrome_sdk.SDKFetcher(
1320 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001321 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001322 use_external_config=options.use_external_config,
1323 )
1324 with sdk.Prepare(
1325 components=[],
1326 target_tc=options.target_tc,
1327 toolchain_url=options.toolchain_url,
1328 ):
1329 cros_build_lib.dbg_run(
1330 [
1331 "mksquashfs",
1332 RAW_ASH_FILE,
1333 COMPRESSED_ASH_FILE,
1334 "-all-root",
1335 "-no-progress",
1336 "-comp",
1337 "zstd",
1338 ],
1339 cwd=staging_dir,
1340 )
1341 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1342
Alex Klein1699fab2022-09-08 08:46:06 -06001343 if options.staging_upload:
1344 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001345
Ryan Cui71aa8de2013-04-19 16:12:55 -07001346
Ryan Cui3045c5d2012-07-13 18:00:33 -07001347def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001348 options = _ParseCommandLine(argv)
1349 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001350
Alex Klein1699fab2022-09-08 08:46:06 -06001351 with osutils.TempDir(set_global=True) as tempdir:
1352 staging_dir = options.staging_dir
1353 if not staging_dir:
1354 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001355
Alex Klein1699fab2022-09-08 08:46:06 -06001356 deploy = DeployChrome(options, tempdir, staging_dir)
1357 try:
1358 deploy.Perform()
1359 except failures_lib.StepFailure as ex:
1360 raise SystemExit(str(ex).strip())
1361 deploy.Cleanup()