blob: f7397a0d9b57e946ecec95f51417570e3e438d23 [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."""
280 self.device.run(
281 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
282 check=False,
283 )
Erik Chen75a2f492020-08-06 19:15:11 -0700284
Alex Klein1699fab2022-09-08 08:46:06 -0600285 def _ResetLacrosChrome(self):
286 """Reset Lacros to fresh state by deleting user data dir."""
287 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 def _KillAshChromeIfNeeded(self):
290 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700291
Alex Klein68b270c2023-04-14 14:42:50 -0600292 This method calls 'stop ui', and then also manually pkills both
293 ash-chrome and the session manager.
Alex Klein1699fab2022-09-08 08:46:06 -0600294 """
295 if self._CheckUiJobStarted():
296 logging.info("Shutting down Chrome...")
297 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 # Developers sometimes run session_manager manually, in which case we'll
300 # need to help shut the chrome processes down.
301 try:
302 with timeout_util.Timeout(self.options.process_timeout):
303 while self._ChromeFileInUse():
304 logging.warning(
305 "The chrome binary on the device is in use."
306 )
307 logging.warning(
308 "Killing chrome and session_manager processes...\n"
309 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700310
Alex Klein1699fab2022-09-08 08:46:06 -0600311 self.device.run(
312 "pkill 'chrome|session_manager'", check=False
313 )
314 # Wait for processes to actually terminate
315 time.sleep(POST_KILL_WAIT)
316 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000317 if self.options.compressed_ash:
318 result = self.device.run(
319 ["umount", RAW_ASH_PATH],
320 check=False,
321 capture_output=True,
322 )
323 if result.returncode and not (
324 result.returncode == 32
325 and "not mounted" in result.stderr
326 ):
327 raise DeployFailure(
328 "Could not unmount compressed ash. "
329 f"Error Code: {result.returncode}, "
330 f"Error Message: {result.stderr}"
331 )
Alex Klein1699fab2022-09-08 08:46:06 -0600332 except timeout_util.TimeoutError:
333 msg = (
334 "Could not kill processes after %s seconds. Please exit any "
335 "running chrome processes and try again."
336 % self.options.process_timeout
337 )
338 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700339
Alex Klein1699fab2022-09-08 08:46:06 -0600340 def _MountRootfsAsWritable(self, check=False):
341 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700342
Alex Klein1699fab2022-09-08 08:46:06 -0600343 If the command fails and the root dir is not writable then this function
344 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700345
Alex Klein1699fab2022-09-08 08:46:06 -0600346 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600347 check: See remote.RemoteAccess.RemoteSh for details.
Alex Klein1699fab2022-09-08 08:46:06 -0600348 """
349 # TODO: Should migrate to use the remount functions in remote_access.
350 result = self.device.run(
351 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
352 )
353 if result.returncode and not self.device.IsDirWritable("/"):
354 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700355
Alex Klein1699fab2022-09-08 08:46:06 -0600356 def _EnsureTargetDir(self):
357 """Ensures that the target directory exists on the remote device."""
358 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600359 # Any valid /opt directory should already exist so avoid the remote
360 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600361 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
362 return
Mike Frysinger445f6bf2023-06-27 11:43:33 -0400363 self.device.mkdir(target_dir, mode=0o775)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600366 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600367 steps = [
368 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
369 functools.partial(
370 self._GetRemoteMountFree, self.options.target_dir
371 ),
372 ]
373 return_values = parallel.RunParallelSteps(steps, return_values=True)
374 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700375
Alex Klein1699fab2022-09-08 08:46:06 -0600376 def _CheckDeviceFreeSpace(self, device_info):
377 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700378
Alex Klein1699fab2022-09-08 08:46:06 -0600379 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600380 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600381 """
382 effective_free = (
383 device_info.target_dir_size + device_info.target_fs_free
384 )
385 staging_size = self._GetStagingDirSize()
386 if effective_free < staging_size:
387 raise DeployFailure(
388 "Not enough free space on the device. Required: %s MiB, "
389 "actual: %s MiB."
390 % (staging_size // 1024, effective_free // 1024)
391 )
392 if device_info.target_fs_free < (100 * 1024):
393 logging.warning(
394 "The device has less than 100MB free. deploy_chrome may "
395 "hang during the transfer."
396 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700397
Alex Klein1699fab2022-09-08 08:46:06 -0600398 def _ShouldUseCompression(self):
399 """Checks if compression should be used for rsync."""
400 if self.options.compress == "always":
401 return True
402 elif self.options.compress == "never":
403 return False
404 elif self.options.compress == "auto":
405 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900406
Alex Klein1699fab2022-09-08 08:46:06 -0600407 def _Deploy(self):
408 logging.info(
409 "Copying %s to %s on device...",
410 self._deployment_name,
411 self.options.target_dir,
412 )
413 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
414 # This does not work for deploy.
415 if not self.device.HasRsync():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200416 # This assumes that rsync is part of the bootstrap package. In the
417 # future, this might change and we'll have to install it separately.
418 if not cros_build_lib.BooleanPrompt(
419 "Run dev_install on the device to install rsync?", True
420 ):
421 raise DeployFailure("rsync is not found on the device.")
422 self.device.BootstrapDevTools()
423 if not self.device.HasRsync():
424 raise DeployFailure("Failed to install rsync")
425
Alex Klein1699fab2022-09-08 08:46:06 -0600426 self.device.CopyToDevice(
427 "%s/" % os.path.abspath(self.staging_dir),
428 self.options.target_dir,
429 mode="rsync",
430 inplace=True,
431 compress=self._ShouldUseCompression(),
432 debug_level=logging.INFO,
433 verbose=self.options.verbose,
434 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700435
Alex Klein68b270c2023-04-14 14:42:50 -0600436 # Set the security context on the default Chrome dir if that's where
437 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600438 if (
439 not self.options.lacros
440 and self.device.IsSELinuxAvailable()
441 and (
442 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
443 )
444 ):
445 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800446
Alex Klein1699fab2022-09-08 08:46:06 -0600447 for p in self.copy_paths:
448 if p.mode:
449 # Set mode if necessary.
450 self.device.run(
451 "chmod %o %s/%s"
452 % (
453 p.mode,
454 self.options.target_dir,
455 p.src if not p.dest else p.dest,
456 )
457 )
Steve Funge984a532013-11-25 17:09:25 -0800458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 if self.options.lacros:
460 self.device.run(
461 ["chown", "-R", "chronos:chronos", self.options.target_dir]
462 )
Erik Chen75a2f492020-08-06 19:15:11 -0700463
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000464 if self.options.compressed_ash:
465 self.device.run(["start", COMPRESSED_ASH_SERVICE])
466
Alex Klein68b270c2023-04-14 14:42:50 -0600467 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
468 # won't pick up major changes (bus type, logging, etc.), but all we care
469 # about is getting the latest policy from /opt/google/chrome/dbus so
470 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600471 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600472
Erik Chen75a2f492020-08-06 19:15:11 -0700473 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700474 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600475 logging.info("Starting UI...")
476 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800477
Joel Hockey764728e2023-03-14 17:10:04 -0700478 if self.options.unlock_password:
479 logging.info("Unlocking...")
480
481 @retry_util.WithRetry(max_retry=5, sleep=1)
482 def WaitForUnlockScreen():
483 if self._GetLastLogin() == last_login:
484 raise DeployFailure("Unlock screen not shown")
485
486 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700487 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700488 self.device.run(
489 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
490 )
491
492 def _GetLastLogin(self):
493 """Returns last login time"""
494 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
495
Alex Klein1699fab2022-09-08 08:46:06 -0600496 def _DeployTestBinaries(self):
497 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700498
Alex Klein68b270c2023-04-14 14:42:50 -0600499 There could be several binaries located in the local build dir, so
500 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
501 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600502 """
503 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
504 if r.returncode != 0:
505 raise DeployFailure(
506 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
507 )
508 binaries_to_copy = []
509 for f in r.stdout.splitlines():
510 binaries_to_copy.append(
511 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
512 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700513
Alex Klein1699fab2022-09-08 08:46:06 -0600514 staging_dir = os.path.join(
515 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
516 )
517 _PrepareStagingDir(
518 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
519 )
520 # Deploying can occasionally run into issues with rsync getting a broken
521 # pipe, so retry several times. See crbug.com/1141618 for more
522 # information.
523 retry_util.RetryException(
524 None,
525 3,
526 self.device.CopyToDevice,
527 staging_dir,
528 os.path.dirname(_CHROME_TEST_BIN_DIR),
529 mode="rsync",
530 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700531
Alex Klein1699fab2022-09-08 08:46:06 -0600532 def _CheckBoard(self):
533 """Check that the Chrome build is targeted for the device board."""
534 if self.options.board == self.device.board:
535 return
536 logging.warning(
537 "Device board is %s whereas target board is %s.",
538 self.device.board,
539 self.options.board,
540 )
541 if self.options.force:
542 return
543 if not cros_build_lib.BooleanPrompt(
544 "Continue despite board mismatch?", False
545 ):
546 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800547
Alex Klein1699fab2022-09-08 08:46:06 -0600548 def _CheckDeployType(self):
549 if self.options.build_dir:
550
551 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600552 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600553 return os.path.exists(
554 os.path.join(self.options.build_dir, filename)
555 )
556
557 # In the future, lacros-chrome and ash-chrome will likely be named
558 # something other than 'chrome' to avoid confusion.
559 # Handle non-Chrome deployments.
560 if not BinaryExists("chrome"):
561 if BinaryExists("app_shell"):
562 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
563
564 def _PrepareStagingDir(self):
565 _PrepareStagingDir(
566 self.options,
567 self.tempdir,
568 self.staging_dir,
569 self.copy_paths,
570 self.chrome_dir,
571 )
572
573 def _MountTarget(self):
574 logging.info("Mounting Chrome...")
575
576 # Create directory if does not exist.
577 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
578 try:
579 # Umount the existing mount on mount_dir if present first.
580 self.device.run(
581 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
582 )
583 except cros_build_lib.RunCommandError as e:
584 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600585 # If there is a failure, check if some process is using the
586 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600587 result = self.device.run(
588 LSOF_COMMAND % (self.options.mount_dir,),
589 check=False,
590 capture_output=True,
591 encoding="utf-8",
592 )
593 logging.error("lsof %s -->", self.options.mount_dir)
594 logging.error(result.stdout)
595 raise e
596
597 self.device.run(
598 _BIND_TO_FINAL_DIR_CMD
599 % (self.options.target_dir, self.options.mount_dir)
600 )
601
602 # Chrome needs partition to have exec and suid flags set
603 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
604
605 def Cleanup(self):
606 """Clean up RemoteDevice."""
607 if not self.options.staging_only:
608 self.device.Cleanup()
609
610 def Perform(self):
611 self._CheckDeployType()
612
613 # If requested, just do the staging step.
614 if self.options.staging_only:
615 self._PrepareStagingDir()
616 return 0
617
Alex Klein68b270c2023-04-14 14:42:50 -0600618 # Check that the build matches the device. Lacros-chrome skips this
619 # check as it's currently board independent. This means that it's
620 # possible to deploy a build of lacros-chrome with a mismatched
621 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600622 if not self.options.lacros:
623 self._CheckBoard()
624
625 # Ensure that the target directory exists before running parallel steps.
626 self._EnsureTargetDir()
627
628 logging.info("Preparing device")
629 steps = [
630 self._GetDeviceInfo,
Alex Klein1699fab2022-09-08 08:46:06 -0600631 self._MountRootfsAsWritable,
632 self._PrepareStagingDir,
633 ]
634
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900635 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600636 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600637 steps.append(self._KillLacrosChrome)
638 if self.options.reset_lacros:
639 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900640 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600641 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900642 config_modified = self._ModifyConfigFileIfNeededForLacros()
643 if config_modified and not restart_ui:
644 logging.warning(
645 "Config file modified but skipping restart_ui "
646 "due to option --skip-restart-ui. Config file "
647 "update is not reflected."
648 )
Alex Klein1699fab2022-09-08 08:46:06 -0600649
650 if restart_ui:
651 steps.append(self._KillAshChromeIfNeeded)
652 self._stopped_ui = True
653
654 ret = parallel.RunParallelSteps(
655 steps, halt_on_error=True, return_values=True
656 )
657 self._CheckDeviceFreeSpace(ret[0])
658
659 # If the root dir is not writable, try disabling rootfs verification.
660 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600661 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600662 if self._root_dir_is_still_readonly.is_set():
663 if self.options.noremove_rootfs_verification:
664 logging.warning("Skipping disable rootfs verification.")
665 elif not self._DisableRootfsVerification():
666 logging.warning("Failed to disable rootfs verification.")
667
Alex Klein68b270c2023-04-14 14:42:50 -0600668 # If the target dir is still not writable (i.e. the user opted out
669 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600670 if not self.device.IsDirWritable(self.options.target_dir):
671 if self.options.startui and self._stopped_ui:
672 logging.info("Restarting Chrome...")
673 self.device.run("start ui")
674 raise DeployFailure(
675 "Target location is not writable. Aborting."
676 )
677
678 if self.options.mount_dir is not None:
679 self._MountTarget()
680
681 # Actually deploy Chrome to the device.
682 self._Deploy()
683 if self.options.deploy_test_binaries:
684 self._DeployTestBinaries()
685
686 def _ModifyConfigFileIfNeededForLacros(self):
687 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
688
689 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600690 True if the file is modified, and the return value is usually used
691 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600692 """
693 assert (
694 self.options.lacros
695 ), "Only deploying lacros-chrome needs to modify the config file."
696 # Update /etc/chrome_dev.conf to include appropriate flags.
697 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000698 if self.options.enable_lacros_support:
699 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
700 if result.stdout.strip() == MODIFIED_CONF_FILE:
701 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600702 result = self.device.run(
703 _SET_LACROS_PATH_VIA_CONF_COMMAND
704 % {
705 "conf_file": _CONF_FILE,
706 "lacros_path": self.options.target_dir,
707 "modified_conf_file": MODIFIED_CONF_FILE,
708 },
709 shell=True,
710 )
711 if result.stdout.strip() == MODIFIED_CONF_FILE:
712 modified = True
713
714 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800715
716
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700717def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600718 """Convert formatted string to dictionary."""
719 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700720
721
Steven Bennetts368c3e52016-09-23 13:05:21 -0700722def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600723 """Convert GN_ARGS-formatted string to dictionary."""
724 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700725
726
Ryan Cuie535b172012-10-19 18:25:03 -0700727def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600728 """Create our custom parser."""
729 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700730
Alex Klein1699fab2022-09-08 08:46:06 -0600731 # TODO(rcui): Have this use the UI-V2 format of having source and target
732 # device be specified as positional arguments.
733 parser.add_argument(
734 "--force",
735 action="store_true",
736 default=False,
737 help="Skip all prompts (such as the prompt for disabling "
738 "of rootfs verification). This may result in the "
739 "target machine being rebooted.",
740 )
741 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
742 parser.add_argument(
743 "--board",
744 default=sdk_board_env,
745 help="The board the Chrome build is targeted for. When "
746 "in a 'cros chrome-sdk' shell, defaults to the SDK "
747 "board.",
748 )
749 parser.add_argument(
750 "--build-dir",
751 type="path",
752 help="The directory with Chrome build artifacts to "
753 "deploy from. Typically of format "
754 "<chrome_root>/out/Debug. When this option is used, "
755 "the GN_ARGS environment variable must be set.",
756 )
757 parser.add_argument(
758 "--target-dir",
759 type="path",
760 default=None,
761 help="Target directory on device to deploy Chrome into.",
762 )
763 parser.add_argument(
764 "-g",
765 "--gs-path",
766 type="gs_path",
767 help="GS path that contains the chrome to deploy.",
768 )
769 parser.add_argument(
770 "--private-key",
771 type="path",
772 default=None,
773 help="An ssh private key to use when deploying to " "a CrOS device.",
774 )
775 parser.add_argument(
776 "--nostartui",
777 action="store_false",
778 dest="startui",
779 default=True,
780 help="Don't restart the ui daemon after deployment.",
781 )
782 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700783 "--unlock-password",
784 default=None,
785 help="Password to use to unlock after deployment and restart.",
786 )
787 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600788 "--nostrip",
789 action="store_false",
790 dest="dostrip",
791 default=True,
792 help="Don't strip binaries during deployment. Warning: "
793 "the resulting binaries will be very large!",
794 )
795 parser.add_argument(
796 "-d",
797 "--device",
798 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
799 help="Device hostname or IP in the format hostname[:port].",
800 )
801 parser.add_argument(
802 "--mount-dir",
803 type="path",
804 default=None,
805 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000806 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600807 "Any existing mount on this directory will be "
808 "umounted first.",
809 )
810 parser.add_argument(
811 "--mount",
812 action="store_true",
813 default=False,
814 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000815 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600816 "Any existing mount on this directory will be "
817 "umounted first.",
818 )
819 parser.add_argument(
820 "--noremove-rootfs-verification",
821 action="store_true",
822 default=False,
823 help="Never remove rootfs verification.",
824 )
825 parser.add_argument(
826 "--deploy-test-binaries",
827 action="store_true",
828 default=False,
829 help="Also deploy any test binaries to %s. Useful for "
830 "running any Tast tests that execute these "
831 "binaries." % _CHROME_TEST_BIN_DIR,
832 )
833 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600834 "--use-external-config",
835 action="store_true",
836 help="When identifying the configuration for a board, "
837 "force usage of the external configuration if both "
838 "internal and external are available. This only "
839 "has an effect when stripping Chrome, i.e. when "
840 "--nostrip is not passed in.",
841 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700842
Georg Neis9b1ff192022-09-14 08:07:22 +0000843 group = parser.add_argument_group("Lacros Options")
844 group.add_argument(
845 "--lacros",
846 action="store_true",
847 default=False,
848 help="Deploys lacros-chrome rather than ash-chrome.",
849 )
850 group.add_argument(
851 "--reset-lacros",
852 action="store_true",
853 default=False,
854 help="Reset Lacros by deleting Lacros user data dir if it exists.",
855 )
856 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900857 "--skip-restart-ui",
858 action="store_true",
859 default=False,
860 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
861 "that this flag may cause ETXTBSY error on rsync, and also won't "
862 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
863 )
864 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000865 "--skip-enabling-lacros-support",
866 action="store_false",
867 dest="enable_lacros_support",
868 help="By default, deploying lacros-chrome modifies the "
869 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
870 "and (2) set the Lacros path, which can interfere with automated "
871 "testing. With this flag, part (1) will be skipped. See the "
872 "--skip-modifying-config-file flag for skipping both parts.",
873 )
874 group.add_argument(
875 "--skip-modifying-config-file",
876 action="store_false",
877 dest="modify_config_file",
878 help="When deploying lacros-chrome, do not modify the "
879 "/etc/chrome_dev.conf file. See also the "
880 "--skip-enabling-lacros-support flag.",
881 )
882
Alex Klein1699fab2022-09-08 08:46:06 -0600883 group = parser.add_argument_group("Advanced Options")
884 group.add_argument(
885 "-l",
886 "--local-pkg-path",
887 type="path",
888 help="Path to local chrome prebuilt package to deploy.",
889 )
890 group.add_argument(
891 "--sloppy",
892 action="store_true",
893 default=False,
894 help="Ignore when mandatory artifacts are missing.",
895 )
896 group.add_argument(
897 "--staging-flags",
898 default=None,
899 type=ValidateStagingFlags,
900 help=(
901 "Extra flags to control staging. Valid flags are - "
902 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
903 ),
904 )
905 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
906 group.add_argument(
907 "--strict",
908 action="store_true",
909 default=False,
910 help='Deprecated. Default behavior is "strict". Use '
911 "--sloppy to omit warnings for missing optional "
912 "files.",
913 )
914 group.add_argument(
915 "--strip-flags",
916 default=None,
917 help="Flags to call the 'strip' binutil tool with. "
918 "Overrides the default arguments.",
919 )
920 group.add_argument(
921 "--ping",
922 action="store_true",
923 default=False,
924 help="Ping the device before connection attempt.",
925 )
926 group.add_argument(
927 "--process-timeout",
928 type=int,
929 default=KILL_PROC_MAX_WAIT,
930 help="Timeout for process shutdown.",
931 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700932
Alex Klein1699fab2022-09-08 08:46:06 -0600933 group = parser.add_argument_group(
934 "Metadata Overrides (Advanced)",
935 description="Provide all of these overrides in order to remove "
936 "dependencies on metadata.json existence.",
937 )
938 group.add_argument(
939 "--target-tc",
940 action="store",
941 default=None,
942 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
943 )
944 group.add_argument(
945 "--toolchain-url",
946 action="store",
947 default=None,
948 help="Override toolchain url format pattern, e.g. "
949 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
950 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700951
Alex Klein1699fab2022-09-08 08:46:06 -0600952 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
953 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
954 parser.add_argument(
955 "--gyp-defines",
956 default=None,
957 type=ValidateStagingFlags,
958 help=argparse.SUPPRESS,
959 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700960
Alex Klein1699fab2022-09-08 08:46:06 -0600961 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600962 # when --build-dir is set. Defaults to reading from the GN_ARGS env
963 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600964 parser.add_argument(
965 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
966 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700967
Alex Klein1699fab2022-09-08 08:46:06 -0600968 # Path of an empty directory to stage chrome artifacts to. Defaults to a
969 # temporary directory that is removed when the script finishes. If the path
970 # is specified, then it will not be removed.
971 parser.add_argument(
972 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
973 )
974 # Only prepare the staging directory, and skip deploying to the device.
975 parser.add_argument(
976 "--staging-only",
977 action="store_true",
978 default=False,
979 help=argparse.SUPPRESS,
980 )
981 # Uploads the compressed staging directory to the given gs:// path URI.
982 parser.add_argument(
983 "--staging-upload",
984 type="gs_path",
985 help="GS path to upload the compressed staging files to.",
986 )
987 # Used alongside --staging-upload to upload with public-read ACL.
988 parser.add_argument(
989 "--public-read",
990 action="store_true",
991 default=False,
992 help="GS path to upload the compressed staging files to.",
993 )
994 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
995 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
996 # fetching the SDK toolchain.
997 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
998 parser.add_argument(
999 "--compress",
1000 action="store",
1001 default="auto",
1002 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001003 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001004 'is set to "auto", that disables compression if '
1005 "the target device has a gigabit ethernet port.",
1006 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001007 parser.add_argument(
1008 "--compressed-ash",
1009 action="store_true",
1010 default=False,
1011 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1012 "binary is stored on DUT in squashfs, mounted upon boot.",
1013 )
Alex Klein1699fab2022-09-08 08:46:06 -06001014 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001015
Ryan Cuie535b172012-10-19 18:25:03 -07001016
1017def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001018 """Parse args, and run environment-independent checks."""
1019 parser = _CreateParser()
1020 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001021
Alex Klein1699fab2022-09-08 08:46:06 -06001022 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1023 parser.error(
1024 "Need to specify either --gs-path, --local-pkg-path, or "
1025 "--build-dir"
1026 )
1027 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1028 parser.error(
1029 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1030 )
1031 if options.lacros:
1032 if options.dostrip and not options.board:
1033 parser.error("Please specify --board.")
1034 if options.mount_dir or options.mount:
1035 parser.error("--lacros does not support --mount or --mount-dir")
1036 if options.deploy_test_binaries:
1037 parser.error("--lacros does not support --deploy-test-binaries")
1038 if options.local_pkg_path:
1039 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001040 if options.compressed_ash:
1041 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001042 else:
1043 if not options.board and options.build_dir:
1044 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1045 if match:
1046 options.board = match.group(1)
1047 logging.info("--board is set to %s", options.board)
1048 if not options.board:
1049 parser.error("--board is required")
1050 if options.gs_path and options.local_pkg_path:
1051 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1052 if not (options.staging_only or options.device):
1053 parser.error("Need to specify --device")
1054 if options.staging_flags and not options.build_dir:
1055 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001056
Alex Klein1699fab2022-09-08 08:46:06 -06001057 if options.strict:
1058 logging.warning("--strict is deprecated.")
1059 if options.gyp_defines:
1060 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001061
Alex Klein1699fab2022-09-08 08:46:06 -06001062 if options.mount or options.mount_dir:
1063 if not options.target_dir:
1064 options.target_dir = _CHROME_DIR_MOUNT
1065 else:
1066 if not options.target_dir:
1067 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001068
Alex Klein1699fab2022-09-08 08:46:06 -06001069 if options.mount and not options.mount_dir:
1070 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001071
Alex Klein1699fab2022-09-08 08:46:06 -06001072 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001073
1074
Mike Frysingerc3061a62015-06-04 04:16:18 -04001075def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001076 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001077
Alex Klein1699fab2022-09-08 08:46:06 -06001078 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001079 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001080 """
1081 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1082 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001083
Alex Klein1699fab2022-09-08 08:46:06 -06001084 if not options.gn_args:
1085 gn_env = os.getenv("GN_ARGS")
1086 if gn_env is not None:
1087 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1088 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001089
Alex Klein1699fab2022-09-08 08:46:06 -06001090 if not options.staging_flags:
1091 use_env = os.getenv("USE")
1092 if use_env is not None:
1093 options.staging_flags = " ".join(
1094 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1095 )
1096 logging.info(
1097 "Staging flags taken from USE in environment: %s",
1098 options.staging_flags,
1099 )
Steven Bennetts60600462016-05-12 10:40:20 -07001100
Ryan Cuia56a71e2012-10-18 18:40:35 -07001101
Ryan Cui504db722013-01-22 11:48:01 -08001102def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001103 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001104
Alex Klein1699fab2022-09-08 08:46:06 -06001105 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001106 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001107 """
1108 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1109 files = gs_ctx.LS(gs_path)
1110 files = [
1111 found
1112 for found in files
1113 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1114 ]
1115 if not files:
1116 raise Exception("No chrome package found at %s" % gs_path)
1117 elif len(files) > 1:
1118 # - Users should provide us with a direct link to either a stripped or
1119 # unstripped chrome package.
1120 # - In the case of being provided with an archive directory, where both
1121 # stripped and unstripped chrome available, use the stripped chrome
1122 # package.
1123 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001124 # - Unstripped chrome pkg is
1125 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001126 files = [f for f in files if not "unstripped" in f]
1127 assert len(files) == 1
1128 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001129
Alex Klein1699fab2022-09-08 08:46:06 -06001130 filename = _UrlBaseName(files[0])
1131 logging.info("Fetching %s...", filename)
1132 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1133 chrome_path = os.path.join(tempdir, filename)
1134 assert os.path.exists(chrome_path)
1135 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001136
1137
Ryan Cuif890a3e2013-03-07 18:57:06 -08001138@contextlib.contextmanager
1139def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001140 if not options.dostrip:
1141 yield None
1142 elif options.strip_bin:
1143 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001144 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001145 sdk = cros_chrome_sdk.SDKFetcher(
1146 options.cache_dir,
1147 options.board,
1148 use_external_config=options.use_external_config,
1149 )
1150 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1151 with sdk.Prepare(
1152 components=components,
1153 target_tc=options.target_tc,
1154 toolchain_url=options.toolchain_url,
1155 ) as ctx:
1156 env_path = os.path.join(
1157 ctx.key_map[constants.CHROME_ENV_TAR].path,
1158 constants.CHROME_ENV_FILE,
1159 )
1160 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1161 strip_bin = os.path.join(
1162 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1163 "bin",
1164 os.path.basename(strip_bin),
1165 )
1166 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001167
Alex Klein1699fab2022-09-08 08:46:06 -06001168
1169def _UploadStagingDir(
1170 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1171) -> None:
1172 """Uploads the compressed staging directory.
1173
1174 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001175 options: options object.
1176 tempdir: Scratch space.
1177 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001178 """
1179 staging_tarball_path = os.path.join(
1180 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1181 )
1182 logging.info(
1183 "Compressing staging dir (%s) to (%s)",
1184 staging_dir,
1185 staging_tarball_path,
1186 )
1187 cros_build_lib.CreateTarball(
1188 staging_tarball_path,
1189 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001190 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001191 extra_env={"ZSTD_CLEVEL": "9"},
1192 )
1193 logging.info(
1194 "Uploading staging tarball (%s) into %s",
1195 staging_tarball_path,
1196 options.staging_upload,
1197 )
1198 ctx = gs.GSContext()
1199 ctx.Copy(
1200 staging_tarball_path,
1201 options.staging_upload,
1202 acl="public-read" if options.public_read else "",
1203 )
1204
1205
1206def _PrepareStagingDir(
1207 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1208):
1209 """Place the necessary files in the staging directory.
1210
Alex Klein68b270c2023-04-14 14:42:50 -06001211 The staging directory is the directory used to rsync the build artifacts
1212 over to the device. Only the necessary Chrome build artifacts are put into
1213 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001214 """
1215 if chrome_dir is None:
1216 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1217 osutils.SafeMakedirs(staging_dir)
1218 os.chmod(staging_dir, 0o755)
1219 if options.build_dir:
1220 with _StripBinContext(options) as strip_bin:
1221 strip_flags = (
1222 None
1223 if options.strip_flags is None
1224 else shlex.split(options.strip_flags)
1225 )
1226 chrome_util.StageChromeFromBuildDir(
1227 staging_dir,
1228 options.build_dir,
1229 strip_bin,
1230 sloppy=options.sloppy,
1231 gn_args=options.gn_args,
1232 staging_flags=options.staging_flags,
1233 strip_flags=strip_flags,
1234 copy_paths=copy_paths,
1235 )
1236 else:
1237 pkg_path = options.local_pkg_path
1238 if options.gs_path:
1239 pkg_path = _FetchChromePackage(
1240 options.cache_dir, tempdir, options.gs_path
1241 )
1242
1243 assert pkg_path
1244 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001245 # Extract only the ./opt/google/chrome contents, directly into the
1246 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001247 if pkg_path[-4:] == ".zip":
1248 cros_build_lib.dbg_run(
1249 [
1250 "unzip",
1251 "-X",
1252 pkg_path,
1253 _ANDROID_DIR_EXTRACT_PATH,
1254 "-d",
1255 staging_dir,
1256 ]
1257 )
1258 for filename in glob.glob(
1259 os.path.join(staging_dir, "system/chrome/*")
1260 ):
1261 shutil.move(filename, staging_dir)
1262 osutils.RmDir(
1263 os.path.join(staging_dir, "system"), ignore_missing=True
1264 )
1265 else:
1266 compression = cros_build_lib.CompressionDetectType(pkg_path)
1267 compressor = cros_build_lib.FindCompressor(compression)
Cindy Lin0b043c42023-06-23 20:59:26 +00001268 if compression == cros_build_lib.CompressionType.ZSTD:
1269 compressor += " -f"
Alex Klein1699fab2022-09-08 08:46:06 -06001270 cros_build_lib.dbg_run(
1271 [
1272 "tar",
1273 "--strip-components",
1274 "4",
1275 "--extract",
1276 "-I",
1277 compressor,
1278 "--preserve-permissions",
1279 "--file",
1280 pkg_path,
1281 ".%s" % chrome_dir,
1282 ],
1283 cwd=staging_dir,
1284 )
1285
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001286 if options.compressed_ash:
1287 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1288 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001289 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1290 # this is in test, cut the known suffix of experimental overlays.
1291 sdk_orig_board = options.board
1292 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1293 sdk_orig_board = sdk_orig_board[
1294 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1295 ]
1296
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001297 sdk = cros_chrome_sdk.SDKFetcher(
1298 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001299 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001300 use_external_config=options.use_external_config,
1301 )
1302 with sdk.Prepare(
1303 components=[],
1304 target_tc=options.target_tc,
1305 toolchain_url=options.toolchain_url,
1306 ):
1307 cros_build_lib.dbg_run(
1308 [
1309 "mksquashfs",
1310 RAW_ASH_FILE,
1311 COMPRESSED_ASH_FILE,
1312 "-all-root",
1313 "-no-progress",
1314 "-comp",
1315 "zstd",
1316 ],
1317 cwd=staging_dir,
1318 )
1319 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1320
Alex Klein1699fab2022-09-08 08:46:06 -06001321 if options.staging_upload:
1322 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001323
Ryan Cui71aa8de2013-04-19 16:12:55 -07001324
Ryan Cui3045c5d2012-07-13 18:00:33 -07001325def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001326 options = _ParseCommandLine(argv)
1327 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001328
Alex Klein1699fab2022-09-08 08:46:06 -06001329 with osutils.TempDir(set_global=True) as tempdir:
1330 staging_dir = options.staging_dir
1331 if not staging_dir:
1332 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001333
Alex Klein1699fab2022-09-08 08:46:06 -06001334 deploy = DeployChrome(options, tempdir, staging_dir)
1335 try:
1336 deploy.Perform()
1337 except failures_lib.StepFailure as ex:
1338 raise SystemExit(str(ex).strip())
1339 deploy.Cleanup()