blob: 575846a4978b5978e9c7eb42331e6b1d8114f4a6 [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
Ryan Cui3045c5d2012-07-13 18:00:33 -0700138class DeployChrome(object):
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
363 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
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 _CheckConnection(self):
533 try:
534 logging.info("Testing connection to the device...")
535 self.device.run("true")
536 except cros_build_lib.RunCommandError as ex:
537 logging.error("Error connecting to the test device.")
538 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800539
Alex Klein1699fab2022-09-08 08:46:06 -0600540 def _CheckBoard(self):
541 """Check that the Chrome build is targeted for the device board."""
542 if self.options.board == self.device.board:
543 return
544 logging.warning(
545 "Device board is %s whereas target board is %s.",
546 self.device.board,
547 self.options.board,
548 )
549 if self.options.force:
550 return
551 if not cros_build_lib.BooleanPrompt(
552 "Continue despite board mismatch?", False
553 ):
554 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800555
Alex Klein1699fab2022-09-08 08:46:06 -0600556 def _CheckDeployType(self):
557 if self.options.build_dir:
558
559 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600560 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600561 return os.path.exists(
562 os.path.join(self.options.build_dir, filename)
563 )
564
565 # In the future, lacros-chrome and ash-chrome will likely be named
566 # something other than 'chrome' to avoid confusion.
567 # Handle non-Chrome deployments.
568 if not BinaryExists("chrome"):
569 if BinaryExists("app_shell"):
570 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
571
572 def _PrepareStagingDir(self):
573 _PrepareStagingDir(
574 self.options,
575 self.tempdir,
576 self.staging_dir,
577 self.copy_paths,
578 self.chrome_dir,
579 )
580
581 def _MountTarget(self):
582 logging.info("Mounting Chrome...")
583
584 # Create directory if does not exist.
585 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
586 try:
587 # Umount the existing mount on mount_dir if present first.
588 self.device.run(
589 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
590 )
591 except cros_build_lib.RunCommandError as e:
592 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600593 # If there is a failure, check if some process is using the
594 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600595 result = self.device.run(
596 LSOF_COMMAND % (self.options.mount_dir,),
597 check=False,
598 capture_output=True,
599 encoding="utf-8",
600 )
601 logging.error("lsof %s -->", self.options.mount_dir)
602 logging.error(result.stdout)
603 raise e
604
605 self.device.run(
606 _BIND_TO_FINAL_DIR_CMD
607 % (self.options.target_dir, self.options.mount_dir)
608 )
609
610 # Chrome needs partition to have exec and suid flags set
611 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
612
613 def Cleanup(self):
614 """Clean up RemoteDevice."""
615 if not self.options.staging_only:
616 self.device.Cleanup()
617
618 def Perform(self):
619 self._CheckDeployType()
620
621 # If requested, just do the staging step.
622 if self.options.staging_only:
623 self._PrepareStagingDir()
624 return 0
625
Alex Klein68b270c2023-04-14 14:42:50 -0600626 # Check that the build matches the device. Lacros-chrome skips this
627 # check as it's currently board independent. This means that it's
628 # possible to deploy a build of lacros-chrome with a mismatched
629 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600630 if not self.options.lacros:
631 self._CheckBoard()
632
633 # Ensure that the target directory exists before running parallel steps.
634 self._EnsureTargetDir()
635
636 logging.info("Preparing device")
637 steps = [
638 self._GetDeviceInfo,
639 self._CheckConnection,
640 self._MountRootfsAsWritable,
641 self._PrepareStagingDir,
642 ]
643
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900644 restart_ui = not self.options.skip_restart_ui
Alex Klein1699fab2022-09-08 08:46:06 -0600645 if self.options.lacros:
Alex Klein1699fab2022-09-08 08:46:06 -0600646 steps.append(self._KillLacrosChrome)
647 if self.options.reset_lacros:
648 steps.append(self._ResetLacrosChrome)
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900649 config_modified = False
Alex Klein1699fab2022-09-08 08:46:06 -0600650 if self.options.modify_config_file:
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900651 config_modified = self._ModifyConfigFileIfNeededForLacros()
652 if config_modified and not restart_ui:
653 logging.warning(
654 "Config file modified but skipping restart_ui "
655 "due to option --skip-restart-ui. Config file "
656 "update is not reflected."
657 )
Alex Klein1699fab2022-09-08 08:46:06 -0600658
659 if restart_ui:
660 steps.append(self._KillAshChromeIfNeeded)
661 self._stopped_ui = True
662
663 ret = parallel.RunParallelSteps(
664 steps, halt_on_error=True, return_values=True
665 )
666 self._CheckDeviceFreeSpace(ret[0])
667
668 # If the root dir is not writable, try disabling rootfs verification.
669 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600670 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600671 if self._root_dir_is_still_readonly.is_set():
672 if self.options.noremove_rootfs_verification:
673 logging.warning("Skipping disable rootfs verification.")
674 elif not self._DisableRootfsVerification():
675 logging.warning("Failed to disable rootfs verification.")
676
Alex Klein68b270c2023-04-14 14:42:50 -0600677 # If the target dir is still not writable (i.e. the user opted out
678 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600679 if not self.device.IsDirWritable(self.options.target_dir):
680 if self.options.startui and self._stopped_ui:
681 logging.info("Restarting Chrome...")
682 self.device.run("start ui")
683 raise DeployFailure(
684 "Target location is not writable. Aborting."
685 )
686
687 if self.options.mount_dir is not None:
688 self._MountTarget()
689
690 # Actually deploy Chrome to the device.
691 self._Deploy()
692 if self.options.deploy_test_binaries:
693 self._DeployTestBinaries()
694
695 def _ModifyConfigFileIfNeededForLacros(self):
696 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
697
698 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600699 True if the file is modified, and the return value is usually used
700 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600701 """
702 assert (
703 self.options.lacros
704 ), "Only deploying lacros-chrome needs to modify the config file."
705 # Update /etc/chrome_dev.conf to include appropriate flags.
706 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000707 if self.options.enable_lacros_support:
708 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
709 if result.stdout.strip() == MODIFIED_CONF_FILE:
710 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600711 result = self.device.run(
712 _SET_LACROS_PATH_VIA_CONF_COMMAND
713 % {
714 "conf_file": _CONF_FILE,
715 "lacros_path": self.options.target_dir,
716 "modified_conf_file": MODIFIED_CONF_FILE,
717 },
718 shell=True,
719 )
720 if result.stdout.strip() == MODIFIED_CONF_FILE:
721 modified = True
722
723 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800724
725
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700726def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600727 """Convert formatted string to dictionary."""
728 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700729
730
Steven Bennetts368c3e52016-09-23 13:05:21 -0700731def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600732 """Convert GN_ARGS-formatted string to dictionary."""
733 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700734
735
Ryan Cuie535b172012-10-19 18:25:03 -0700736def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600737 """Create our custom parser."""
738 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700739
Alex Klein1699fab2022-09-08 08:46:06 -0600740 # TODO(rcui): Have this use the UI-V2 format of having source and target
741 # device be specified as positional arguments.
742 parser.add_argument(
743 "--force",
744 action="store_true",
745 default=False,
746 help="Skip all prompts (such as the prompt for disabling "
747 "of rootfs verification). This may result in the "
748 "target machine being rebooted.",
749 )
750 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
751 parser.add_argument(
752 "--board",
753 default=sdk_board_env,
754 help="The board the Chrome build is targeted for. When "
755 "in a 'cros chrome-sdk' shell, defaults to the SDK "
756 "board.",
757 )
758 parser.add_argument(
759 "--build-dir",
760 type="path",
761 help="The directory with Chrome build artifacts to "
762 "deploy from. Typically of format "
763 "<chrome_root>/out/Debug. When this option is used, "
764 "the GN_ARGS environment variable must be set.",
765 )
766 parser.add_argument(
767 "--target-dir",
768 type="path",
769 default=None,
770 help="Target directory on device to deploy Chrome into.",
771 )
772 parser.add_argument(
773 "-g",
774 "--gs-path",
775 type="gs_path",
776 help="GS path that contains the chrome to deploy.",
777 )
778 parser.add_argument(
779 "--private-key",
780 type="path",
781 default=None,
782 help="An ssh private key to use when deploying to " "a CrOS device.",
783 )
784 parser.add_argument(
785 "--nostartui",
786 action="store_false",
787 dest="startui",
788 default=True,
789 help="Don't restart the ui daemon after deployment.",
790 )
791 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700792 "--unlock-password",
793 default=None,
794 help="Password to use to unlock after deployment and restart.",
795 )
796 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600797 "--nostrip",
798 action="store_false",
799 dest="dostrip",
800 default=True,
801 help="Don't strip binaries during deployment. Warning: "
802 "the resulting binaries will be very large!",
803 )
804 parser.add_argument(
805 "-d",
806 "--device",
807 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
808 help="Device hostname or IP in the format hostname[:port].",
809 )
810 parser.add_argument(
811 "--mount-dir",
812 type="path",
813 default=None,
814 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000815 "to the directory specified by this flag. "
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 "--mount",
821 action="store_true",
822 default=False,
823 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000824 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600825 "Any existing mount on this directory will be "
826 "umounted first.",
827 )
828 parser.add_argument(
829 "--noremove-rootfs-verification",
830 action="store_true",
831 default=False,
832 help="Never remove rootfs verification.",
833 )
834 parser.add_argument(
835 "--deploy-test-binaries",
836 action="store_true",
837 default=False,
838 help="Also deploy any test binaries to %s. Useful for "
839 "running any Tast tests that execute these "
840 "binaries." % _CHROME_TEST_BIN_DIR,
841 )
842 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600843 "--use-external-config",
844 action="store_true",
845 help="When identifying the configuration for a board, "
846 "force usage of the external configuration if both "
847 "internal and external are available. This only "
848 "has an effect when stripping Chrome, i.e. when "
849 "--nostrip is not passed in.",
850 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700851
Georg Neis9b1ff192022-09-14 08:07:22 +0000852 group = parser.add_argument_group("Lacros Options")
853 group.add_argument(
854 "--lacros",
855 action="store_true",
856 default=False,
857 help="Deploys lacros-chrome rather than ash-chrome.",
858 )
859 group.add_argument(
860 "--reset-lacros",
861 action="store_true",
862 default=False,
863 help="Reset Lacros by deleting Lacros user data dir if it exists.",
864 )
865 group.add_argument(
Eriko Kurimotoe01cbdf2023-06-01 14:07:28 +0900866 "--skip-restart-ui",
867 action="store_true",
868 default=False,
869 help="Skip restarting ash-chrome on deploying lacros-chrome. Note "
870 "that this flag may cause ETXTBSY error on rsync, and also won't "
871 "reflect the /etc/chrome_dev.conf file updates as it won't restart.",
872 )
873 group.add_argument(
Georg Neis9b1ff192022-09-14 08:07:22 +0000874 "--skip-enabling-lacros-support",
875 action="store_false",
876 dest="enable_lacros_support",
877 help="By default, deploying lacros-chrome modifies the "
878 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
879 "and (2) set the Lacros path, which can interfere with automated "
880 "testing. With this flag, part (1) will be skipped. See the "
881 "--skip-modifying-config-file flag for skipping both parts.",
882 )
883 group.add_argument(
884 "--skip-modifying-config-file",
885 action="store_false",
886 dest="modify_config_file",
887 help="When deploying lacros-chrome, do not modify the "
888 "/etc/chrome_dev.conf file. See also the "
889 "--skip-enabling-lacros-support flag.",
890 )
891
Alex Klein1699fab2022-09-08 08:46:06 -0600892 group = parser.add_argument_group("Advanced Options")
893 group.add_argument(
894 "-l",
895 "--local-pkg-path",
896 type="path",
897 help="Path to local chrome prebuilt package to deploy.",
898 )
899 group.add_argument(
900 "--sloppy",
901 action="store_true",
902 default=False,
903 help="Ignore when mandatory artifacts are missing.",
904 )
905 group.add_argument(
906 "--staging-flags",
907 default=None,
908 type=ValidateStagingFlags,
909 help=(
910 "Extra flags to control staging. Valid flags are - "
911 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
912 ),
913 )
914 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
915 group.add_argument(
916 "--strict",
917 action="store_true",
918 default=False,
919 help='Deprecated. Default behavior is "strict". Use '
920 "--sloppy to omit warnings for missing optional "
921 "files.",
922 )
923 group.add_argument(
924 "--strip-flags",
925 default=None,
926 help="Flags to call the 'strip' binutil tool with. "
927 "Overrides the default arguments.",
928 )
929 group.add_argument(
930 "--ping",
931 action="store_true",
932 default=False,
933 help="Ping the device before connection attempt.",
934 )
935 group.add_argument(
936 "--process-timeout",
937 type=int,
938 default=KILL_PROC_MAX_WAIT,
939 help="Timeout for process shutdown.",
940 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700941
Alex Klein1699fab2022-09-08 08:46:06 -0600942 group = parser.add_argument_group(
943 "Metadata Overrides (Advanced)",
944 description="Provide all of these overrides in order to remove "
945 "dependencies on metadata.json existence.",
946 )
947 group.add_argument(
948 "--target-tc",
949 action="store",
950 default=None,
951 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
952 )
953 group.add_argument(
954 "--toolchain-url",
955 action="store",
956 default=None,
957 help="Override toolchain url format pattern, e.g. "
958 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
959 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700960
Alex Klein1699fab2022-09-08 08:46:06 -0600961 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
962 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
963 parser.add_argument(
964 "--gyp-defines",
965 default=None,
966 type=ValidateStagingFlags,
967 help=argparse.SUPPRESS,
968 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700969
Alex Klein1699fab2022-09-08 08:46:06 -0600970 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600971 # when --build-dir is set. Defaults to reading from the GN_ARGS env
972 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600973 parser.add_argument(
974 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
975 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700976
Alex Klein1699fab2022-09-08 08:46:06 -0600977 # Path of an empty directory to stage chrome artifacts to. Defaults to a
978 # temporary directory that is removed when the script finishes. If the path
979 # is specified, then it will not be removed.
980 parser.add_argument(
981 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
982 )
983 # Only prepare the staging directory, and skip deploying to the device.
984 parser.add_argument(
985 "--staging-only",
986 action="store_true",
987 default=False,
988 help=argparse.SUPPRESS,
989 )
990 # Uploads the compressed staging directory to the given gs:// path URI.
991 parser.add_argument(
992 "--staging-upload",
993 type="gs_path",
994 help="GS path to upload the compressed staging files to.",
995 )
996 # Used alongside --staging-upload to upload with public-read ACL.
997 parser.add_argument(
998 "--public-read",
999 action="store_true",
1000 default=False,
1001 help="GS path to upload the compressed staging files to.",
1002 )
1003 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
1004 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
1005 # fetching the SDK toolchain.
1006 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
1007 parser.add_argument(
1008 "--compress",
1009 action="store",
1010 default="auto",
1011 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001012 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001013 'is set to "auto", that disables compression if '
1014 "the target device has a gigabit ethernet port.",
1015 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001016 parser.add_argument(
1017 "--compressed-ash",
1018 action="store_true",
1019 default=False,
1020 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1021 "binary is stored on DUT in squashfs, mounted upon boot.",
1022 )
Alex Klein1699fab2022-09-08 08:46:06 -06001023 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001024
Ryan Cuie535b172012-10-19 18:25:03 -07001025
1026def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001027 """Parse args, and run environment-independent checks."""
1028 parser = _CreateParser()
1029 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001030
Alex Klein1699fab2022-09-08 08:46:06 -06001031 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1032 parser.error(
1033 "Need to specify either --gs-path, --local-pkg-path, or "
1034 "--build-dir"
1035 )
1036 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1037 parser.error(
1038 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1039 )
1040 if options.lacros:
1041 if options.dostrip and not options.board:
1042 parser.error("Please specify --board.")
1043 if options.mount_dir or options.mount:
1044 parser.error("--lacros does not support --mount or --mount-dir")
1045 if options.deploy_test_binaries:
1046 parser.error("--lacros does not support --deploy-test-binaries")
1047 if options.local_pkg_path:
1048 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001049 if options.compressed_ash:
1050 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001051 else:
1052 if not options.board and options.build_dir:
1053 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1054 if match:
1055 options.board = match.group(1)
1056 logging.info("--board is set to %s", options.board)
1057 if not options.board:
1058 parser.error("--board is required")
1059 if options.gs_path and options.local_pkg_path:
1060 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1061 if not (options.staging_only or options.device):
1062 parser.error("Need to specify --device")
1063 if options.staging_flags and not options.build_dir:
1064 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001065
Alex Klein1699fab2022-09-08 08:46:06 -06001066 if options.strict:
1067 logging.warning("--strict is deprecated.")
1068 if options.gyp_defines:
1069 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001070
Alex Klein1699fab2022-09-08 08:46:06 -06001071 if options.mount or options.mount_dir:
1072 if not options.target_dir:
1073 options.target_dir = _CHROME_DIR_MOUNT
1074 else:
1075 if not options.target_dir:
1076 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001077
Alex Klein1699fab2022-09-08 08:46:06 -06001078 if options.mount and not options.mount_dir:
1079 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001080
Alex Klein1699fab2022-09-08 08:46:06 -06001081 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001082
1083
Mike Frysingerc3061a62015-06-04 04:16:18 -04001084def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001085 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001086
Alex Klein1699fab2022-09-08 08:46:06 -06001087 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001088 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001089 """
1090 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1091 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 if not options.gn_args:
1094 gn_env = os.getenv("GN_ARGS")
1095 if gn_env is not None:
1096 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1097 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001098
Alex Klein1699fab2022-09-08 08:46:06 -06001099 if not options.staging_flags:
1100 use_env = os.getenv("USE")
1101 if use_env is not None:
1102 options.staging_flags = " ".join(
1103 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1104 )
1105 logging.info(
1106 "Staging flags taken from USE in environment: %s",
1107 options.staging_flags,
1108 )
Steven Bennetts60600462016-05-12 10:40:20 -07001109
Ryan Cuia56a71e2012-10-18 18:40:35 -07001110
Ryan Cui504db722013-01-22 11:48:01 -08001111def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001112 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001113
Alex Klein1699fab2022-09-08 08:46:06 -06001114 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001115 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001116 """
1117 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1118 files = gs_ctx.LS(gs_path)
1119 files = [
1120 found
1121 for found in files
1122 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1123 ]
1124 if not files:
1125 raise Exception("No chrome package found at %s" % gs_path)
1126 elif len(files) > 1:
1127 # - Users should provide us with a direct link to either a stripped or
1128 # unstripped chrome package.
1129 # - In the case of being provided with an archive directory, where both
1130 # stripped and unstripped chrome available, use the stripped chrome
1131 # package.
1132 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001133 # - Unstripped chrome pkg is
1134 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001135 files = [f for f in files if not "unstripped" in f]
1136 assert len(files) == 1
1137 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001138
Alex Klein1699fab2022-09-08 08:46:06 -06001139 filename = _UrlBaseName(files[0])
1140 logging.info("Fetching %s...", filename)
1141 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1142 chrome_path = os.path.join(tempdir, filename)
1143 assert os.path.exists(chrome_path)
1144 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001145
1146
Ryan Cuif890a3e2013-03-07 18:57:06 -08001147@contextlib.contextmanager
1148def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001149 if not options.dostrip:
1150 yield None
1151 elif options.strip_bin:
1152 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001153 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001154 sdk = cros_chrome_sdk.SDKFetcher(
1155 options.cache_dir,
1156 options.board,
1157 use_external_config=options.use_external_config,
1158 )
1159 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1160 with sdk.Prepare(
1161 components=components,
1162 target_tc=options.target_tc,
1163 toolchain_url=options.toolchain_url,
1164 ) as ctx:
1165 env_path = os.path.join(
1166 ctx.key_map[constants.CHROME_ENV_TAR].path,
1167 constants.CHROME_ENV_FILE,
1168 )
1169 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1170 strip_bin = os.path.join(
1171 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1172 "bin",
1173 os.path.basename(strip_bin),
1174 )
1175 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001176
Alex Klein1699fab2022-09-08 08:46:06 -06001177
1178def _UploadStagingDir(
1179 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1180) -> None:
1181 """Uploads the compressed staging directory.
1182
1183 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001184 options: options object.
1185 tempdir: Scratch space.
1186 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001187 """
1188 staging_tarball_path = os.path.join(
1189 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1190 )
1191 logging.info(
1192 "Compressing staging dir (%s) to (%s)",
1193 staging_dir,
1194 staging_tarball_path,
1195 )
1196 cros_build_lib.CreateTarball(
1197 staging_tarball_path,
1198 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001199 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001200 extra_env={"ZSTD_CLEVEL": "9"},
1201 )
1202 logging.info(
1203 "Uploading staging tarball (%s) into %s",
1204 staging_tarball_path,
1205 options.staging_upload,
1206 )
1207 ctx = gs.GSContext()
1208 ctx.Copy(
1209 staging_tarball_path,
1210 options.staging_upload,
1211 acl="public-read" if options.public_read else "",
1212 )
1213
1214
1215def _PrepareStagingDir(
1216 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1217):
1218 """Place the necessary files in the staging directory.
1219
Alex Klein68b270c2023-04-14 14:42:50 -06001220 The staging directory is the directory used to rsync the build artifacts
1221 over to the device. Only the necessary Chrome build artifacts are put into
1222 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001223 """
1224 if chrome_dir is None:
1225 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1226 osutils.SafeMakedirs(staging_dir)
1227 os.chmod(staging_dir, 0o755)
1228 if options.build_dir:
1229 with _StripBinContext(options) as strip_bin:
1230 strip_flags = (
1231 None
1232 if options.strip_flags is None
1233 else shlex.split(options.strip_flags)
1234 )
1235 chrome_util.StageChromeFromBuildDir(
1236 staging_dir,
1237 options.build_dir,
1238 strip_bin,
1239 sloppy=options.sloppy,
1240 gn_args=options.gn_args,
1241 staging_flags=options.staging_flags,
1242 strip_flags=strip_flags,
1243 copy_paths=copy_paths,
1244 )
1245 else:
1246 pkg_path = options.local_pkg_path
1247 if options.gs_path:
1248 pkg_path = _FetchChromePackage(
1249 options.cache_dir, tempdir, options.gs_path
1250 )
1251
1252 assert pkg_path
1253 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001254 # Extract only the ./opt/google/chrome contents, directly into the
1255 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001256 if pkg_path[-4:] == ".zip":
1257 cros_build_lib.dbg_run(
1258 [
1259 "unzip",
1260 "-X",
1261 pkg_path,
1262 _ANDROID_DIR_EXTRACT_PATH,
1263 "-d",
1264 staging_dir,
1265 ]
1266 )
1267 for filename in glob.glob(
1268 os.path.join(staging_dir, "system/chrome/*")
1269 ):
1270 shutil.move(filename, staging_dir)
1271 osutils.RmDir(
1272 os.path.join(staging_dir, "system"), ignore_missing=True
1273 )
1274 else:
1275 compression = cros_build_lib.CompressionDetectType(pkg_path)
1276 compressor = cros_build_lib.FindCompressor(compression)
1277 cros_build_lib.dbg_run(
1278 [
1279 "tar",
1280 "--strip-components",
1281 "4",
1282 "--extract",
1283 "-I",
1284 compressor,
1285 "--preserve-permissions",
1286 "--file",
1287 pkg_path,
1288 ".%s" % chrome_dir,
1289 ],
1290 cwd=staging_dir,
1291 )
1292
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001293 if options.compressed_ash:
1294 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1295 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001296 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1297 # this is in test, cut the known suffix of experimental overlays.
1298 sdk_orig_board = options.board
1299 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1300 sdk_orig_board = sdk_orig_board[
1301 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1302 ]
1303
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001304 sdk = cros_chrome_sdk.SDKFetcher(
1305 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001306 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001307 use_external_config=options.use_external_config,
1308 )
1309 with sdk.Prepare(
1310 components=[],
1311 target_tc=options.target_tc,
1312 toolchain_url=options.toolchain_url,
1313 ):
1314 cros_build_lib.dbg_run(
1315 [
1316 "mksquashfs",
1317 RAW_ASH_FILE,
1318 COMPRESSED_ASH_FILE,
1319 "-all-root",
1320 "-no-progress",
1321 "-comp",
1322 "zstd",
1323 ],
1324 cwd=staging_dir,
1325 )
1326 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1327
Alex Klein1699fab2022-09-08 08:46:06 -06001328 if options.staging_upload:
1329 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001330
Ryan Cui71aa8de2013-04-19 16:12:55 -07001331
Ryan Cui3045c5d2012-07-13 18:00:33 -07001332def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001333 options = _ParseCommandLine(argv)
1334 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001335
Alex Klein1699fab2022-09-08 08:46:06 -06001336 with osutils.TempDir(set_global=True) as tempdir:
1337 staging_dir = options.staging_dir
1338 if not staging_dir:
1339 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001340
Alex Klein1699fab2022-09-08 08:46:06 -06001341 deploy = DeployChrome(options, tempdir, staging_dir)
1342 try:
1343 deploy.Perform()
1344 except failures_lib.StepFailure as ex:
1345 raise SystemExit(str(ex).strip())
1346 deploy.Cleanup()