blob: 6eb1410eba9f7dbde63d51dc832fccb870e96ff4 [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
96# This command checks if "--enable-features=LacrosSupport" is present in
97# /etc/chrome_dev.conf. If it is not, then it is added.
98# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
99# to modify chrome_dev.conf. Either revisit this policy or find another
100# mechanism to pass configuration to ash-chrome.
101ENABLE_LACROS_VIA_CONF_COMMAND = f"""
102 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
103 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
104 echo {MODIFIED_CONF_FILE};
105 fi
106"""
107
108# This command checks if "--lacros-chrome-path=" is present with the right value
109# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
110# and the new one is added.
111# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
112# to modify chrome_dev.conf. Either revisit this policy or find another
113# mechanism to pass configuration to ash-chrome.
114_SET_LACROS_PATH_VIA_CONF_COMMAND = """
115 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
116 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
117 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
118 echo %(modified_conf_file)s;
119 fi
120"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500121
Alex Klein1699fab2022-09-08 08:46:06 -0600122
Ryan Cui3045c5d2012-07-13 18:00:33 -0700123def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600124 """Return the last component of the URL."""
125 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700126
127
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700128class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600129 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800130
131
Ryan Cui7193a7e2013-04-26 14:15:19 -0700132DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600133 "DeviceInfo", ["target_dir_size", "target_fs_free"]
134)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700135
136
Ryan Cui3045c5d2012-07-13 18:00:33 -0700137class DeployChrome(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600138 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 def __init__(self, options, tempdir, staging_dir):
141 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600144 options: options object.
145 tempdir: Scratch space for the class. Caller has responsibility to
146 clean it up.
147 staging_dir: Directory to stage the files to.
Alex Klein1699fab2022-09-08 08:46:06 -0600148 """
149 self.tempdir = tempdir
150 self.options = options
151 self.staging_dir = staging_dir
152 if not self.options.staging_only:
153 hostname = options.device.hostname
154 port = options.device.port
155 self.device = remote.ChromiumOSDevice(
156 hostname,
157 port=port,
158 ping=options.ping,
159 private_key=options.private_key,
160 include_dev_paths=False,
161 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000162 if self._ShouldUseCompressedAsh():
163 self.options.compressed_ash = True
164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000166
Alex Klein1699fab2022-09-08 08:46:06 -0600167 self._deployment_name = "lacros" if options.lacros else "chrome"
168 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700169
Alex Klein1699fab2022-09-08 08:46:06 -0600170 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 # Whether UI was stopped during setup.
173 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800174
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000175 def _ShouldUseCompressedAsh(self):
176 """Detects if the DUT uses compressed-ash setup."""
177 if self.options.lacros:
178 return False
179
180 return self.device.IfFileExists(COMPRESSED_ASH_PATH)
181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 def _GetRemoteMountFree(self, remote_dir):
183 result = self.device.run(DF_COMMAND % remote_dir)
184 line = result.stdout.splitlines()[1]
185 value = line.split()[3]
186 multipliers = {
187 "G": 1024 * 1024 * 1024,
188 "M": 1024 * 1024,
189 "K": 1024,
190 }
191 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 def _GetRemoteDirSize(self, remote_dir):
194 result = self.device.run(
195 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
196 )
197 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700198
Alex Klein1699fab2022-09-08 08:46:06 -0600199 def _GetStagingDirSize(self):
200 result = cros_build_lib.dbg_run(
201 ["du", "-ks", self.staging_dir],
202 capture_output=True,
203 encoding="utf-8",
204 )
205 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700206
Alex Klein1699fab2022-09-08 08:46:06 -0600207 def _ChromeFileInUse(self):
208 result = self.device.run(
209 LSOF_COMMAND_CHROME % (self.options.target_dir,),
210 check=False,
211 capture_output=True,
212 )
213 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700214
Alex Klein1699fab2022-09-08 08:46:06 -0600215 def _Reboot(self):
216 # A reboot in developer mode takes a while (and has delays), so the user
217 # will have time to read and act on the USB boot instructions below.
218 logging.info(
219 "Please remember to press Ctrl-U if you are booting from USB."
220 )
221 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600222
Alex Klein1699fab2022-09-08 08:46:06 -0600223 def _DisableRootfsVerification(self):
224 if not self.options.force:
225 logging.error(
226 "Detected that the device has rootfs verification enabled."
227 )
228 logging.info(
229 "This script can automatically remove the rootfs "
230 "verification, which requires it to reboot the device."
231 )
232 logging.info("Make sure the device is in developer mode!")
233 logging.info("Skip this prompt by specifying --force.")
234 if not cros_build_lib.BooleanPrompt(
235 "Remove rootfs verification?", False
236 ):
237 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700238
Alex Klein1699fab2022-09-08 08:46:06 -0600239 logging.info(
240 "Removing rootfs verification from %s", self.options.device
241 )
Alex Klein68b270c2023-04-14 14:42:50 -0600242 # Running in VMs cause make_dev_ssd's firmware confidence checks to
243 # fail. Use --force to bypass the checks.
Alex Klein1699fab2022-09-08 08:46:06 -0600244 cmd = (
245 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
246 "--remove_rootfs_verification --force"
247 )
248 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
249 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 # Now that the machine has been rebooted, we need to kill Chrome again.
254 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 # Make sure the rootfs is writable now.
257 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 def _CheckUiJobStarted(self):
262 # status output is in the format:
263 # <job_name> <status> ['process' <pid>].
264 # <status> is in the format <goal>/<state>.
265 try:
266 result = self.device.run(
267 "status ui", capture_output=True, encoding="utf-8"
268 )
269 except cros_build_lib.RunCommandError as e:
270 if "Unknown job" in e.stderr:
271 return False
272 else:
273 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800274
Alex Klein1699fab2022-09-08 08:46:06 -0600275 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700276
Alex Klein1699fab2022-09-08 08:46:06 -0600277 def _KillLacrosChrome(self):
278 """This method kills lacros-chrome on the device, if it's running."""
279 self.device.run(
280 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
281 check=False,
282 )
Erik Chen75a2f492020-08-06 19:15:11 -0700283
Alex Klein1699fab2022-09-08 08:46:06 -0600284 def _ResetLacrosChrome(self):
285 """Reset Lacros to fresh state by deleting user data dir."""
286 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 def _KillAshChromeIfNeeded(self):
289 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700290
Alex Klein68b270c2023-04-14 14:42:50 -0600291 This method calls 'stop ui', and then also manually pkills both
292 ash-chrome and the session manager.
Alex Klein1699fab2022-09-08 08:46:06 -0600293 """
294 if self._CheckUiJobStarted():
295 logging.info("Shutting down Chrome...")
296 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700297
Alex Klein1699fab2022-09-08 08:46:06 -0600298 # Developers sometimes run session_manager manually, in which case we'll
299 # need to help shut the chrome processes down.
300 try:
301 with timeout_util.Timeout(self.options.process_timeout):
302 while self._ChromeFileInUse():
303 logging.warning(
304 "The chrome binary on the device is in use."
305 )
306 logging.warning(
307 "Killing chrome and session_manager processes...\n"
308 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 self.device.run(
311 "pkill 'chrome|session_manager'", check=False
312 )
313 # Wait for processes to actually terminate
314 time.sleep(POST_KILL_WAIT)
315 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000316 if self.options.compressed_ash:
317 result = self.device.run(
318 ["umount", RAW_ASH_PATH],
319 check=False,
320 capture_output=True,
321 )
322 if result.returncode and not (
323 result.returncode == 32
324 and "not mounted" in result.stderr
325 ):
326 raise DeployFailure(
327 "Could not unmount compressed ash. "
328 f"Error Code: {result.returncode}, "
329 f"Error Message: {result.stderr}"
330 )
Alex Klein1699fab2022-09-08 08:46:06 -0600331 except timeout_util.TimeoutError:
332 msg = (
333 "Could not kill processes after %s seconds. Please exit any "
334 "running chrome processes and try again."
335 % self.options.process_timeout
336 )
337 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 def _MountRootfsAsWritable(self, check=False):
340 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700341
Alex Klein1699fab2022-09-08 08:46:06 -0600342 If the command fails and the root dir is not writable then this function
343 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700344
Alex Klein1699fab2022-09-08 08:46:06 -0600345 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600346 check: See remote.RemoteAccess.RemoteSh for details.
Alex Klein1699fab2022-09-08 08:46:06 -0600347 """
348 # TODO: Should migrate to use the remount functions in remote_access.
349 result = self.device.run(
350 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
351 )
352 if result.returncode and not self.device.IsDirWritable("/"):
353 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700354
Alex Klein1699fab2022-09-08 08:46:06 -0600355 def _EnsureTargetDir(self):
356 """Ensures that the target directory exists on the remote device."""
357 target_dir = self.options.target_dir
Alex Klein68b270c2023-04-14 14:42:50 -0600358 # Any valid /opt directory should already exist so avoid the remote
359 # call.
Alex Klein1699fab2022-09-08 08:46:06 -0600360 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
361 return
362 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 def _GetDeviceInfo(self):
Alex Klein68b270c2023-04-14 14:42:50 -0600365 """Get the disk space used and available for the target directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600366 steps = [
367 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
368 functools.partial(
369 self._GetRemoteMountFree, self.options.target_dir
370 ),
371 ]
372 return_values = parallel.RunParallelSteps(steps, return_values=True)
373 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 def _CheckDeviceFreeSpace(self, device_info):
376 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700377
Alex Klein1699fab2022-09-08 08:46:06 -0600378 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600379 device_info: A DeviceInfo named tuple.
Alex Klein1699fab2022-09-08 08:46:06 -0600380 """
381 effective_free = (
382 device_info.target_dir_size + device_info.target_fs_free
383 )
384 staging_size = self._GetStagingDirSize()
385 if effective_free < staging_size:
386 raise DeployFailure(
387 "Not enough free space on the device. Required: %s MiB, "
388 "actual: %s MiB."
389 % (staging_size // 1024, effective_free // 1024)
390 )
391 if device_info.target_fs_free < (100 * 1024):
392 logging.warning(
393 "The device has less than 100MB free. deploy_chrome may "
394 "hang during the transfer."
395 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700396
Alex Klein1699fab2022-09-08 08:46:06 -0600397 def _ShouldUseCompression(self):
398 """Checks if compression should be used for rsync."""
399 if self.options.compress == "always":
400 return True
401 elif self.options.compress == "never":
402 return False
403 elif self.options.compress == "auto":
404 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900405
Alex Klein1699fab2022-09-08 08:46:06 -0600406 def _Deploy(self):
407 logging.info(
408 "Copying %s to %s on device...",
409 self._deployment_name,
410 self.options.target_dir,
411 )
412 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
413 # This does not work for deploy.
414 if not self.device.HasRsync():
415 raise DeployFailure(
416 "rsync is not found on the device.\n"
417 "Run dev_install on the device to get rsync installed."
418 )
419 self.device.CopyToDevice(
420 "%s/" % os.path.abspath(self.staging_dir),
421 self.options.target_dir,
422 mode="rsync",
423 inplace=True,
424 compress=self._ShouldUseCompression(),
425 debug_level=logging.INFO,
426 verbose=self.options.verbose,
427 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700428
Alex Klein68b270c2023-04-14 14:42:50 -0600429 # Set the security context on the default Chrome dir if that's where
430 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600431 if (
432 not self.options.lacros
433 and self.device.IsSELinuxAvailable()
434 and (
435 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
436 )
437 ):
438 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800439
Alex Klein1699fab2022-09-08 08:46:06 -0600440 for p in self.copy_paths:
441 if p.mode:
442 # Set mode if necessary.
443 self.device.run(
444 "chmod %o %s/%s"
445 % (
446 p.mode,
447 self.options.target_dir,
448 p.src if not p.dest else p.dest,
449 )
450 )
Steve Funge984a532013-11-25 17:09:25 -0800451
Alex Klein1699fab2022-09-08 08:46:06 -0600452 if self.options.lacros:
453 self.device.run(
454 ["chown", "-R", "chronos:chronos", self.options.target_dir]
455 )
Erik Chen75a2f492020-08-06 19:15:11 -0700456
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000457 if self.options.compressed_ash:
458 self.device.run(["start", COMPRESSED_ASH_SERVICE])
459
Alex Klein68b270c2023-04-14 14:42:50 -0600460 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
461 # won't pick up major changes (bus type, logging, etc.), but all we care
462 # about is getting the latest policy from /opt/google/chrome/dbus so
463 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600464 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600465
Erik Chen75a2f492020-08-06 19:15:11 -0700466 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700467 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600468 logging.info("Starting UI...")
469 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800470
Joel Hockey764728e2023-03-14 17:10:04 -0700471 if self.options.unlock_password:
472 logging.info("Unlocking...")
473
474 @retry_util.WithRetry(max_retry=5, sleep=1)
475 def WaitForUnlockScreen():
476 if self._GetLastLogin() == last_login:
477 raise DeployFailure("Unlock screen not shown")
478
479 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700480 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700481 self.device.run(
482 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
483 )
484
485 def _GetLastLogin(self):
486 """Returns last login time"""
487 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 def _DeployTestBinaries(self):
490 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700491
Alex Klein68b270c2023-04-14 14:42:50 -0600492 There could be several binaries located in the local build dir, so
493 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
494 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600495 """
496 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
497 if r.returncode != 0:
498 raise DeployFailure(
499 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
500 )
501 binaries_to_copy = []
502 for f in r.stdout.splitlines():
503 binaries_to_copy.append(
504 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
505 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700506
Alex Klein1699fab2022-09-08 08:46:06 -0600507 staging_dir = os.path.join(
508 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
509 )
510 _PrepareStagingDir(
511 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
512 )
513 # Deploying can occasionally run into issues with rsync getting a broken
514 # pipe, so retry several times. See crbug.com/1141618 for more
515 # information.
516 retry_util.RetryException(
517 None,
518 3,
519 self.device.CopyToDevice,
520 staging_dir,
521 os.path.dirname(_CHROME_TEST_BIN_DIR),
522 mode="rsync",
523 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700524
Alex Klein1699fab2022-09-08 08:46:06 -0600525 def _CheckConnection(self):
526 try:
527 logging.info("Testing connection to the device...")
528 self.device.run("true")
529 except cros_build_lib.RunCommandError as ex:
530 logging.error("Error connecting to the test device.")
531 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 def _CheckBoard(self):
534 """Check that the Chrome build is targeted for the device board."""
535 if self.options.board == self.device.board:
536 return
537 logging.warning(
538 "Device board is %s whereas target board is %s.",
539 self.device.board,
540 self.options.board,
541 )
542 if self.options.force:
543 return
544 if not cros_build_lib.BooleanPrompt(
545 "Continue despite board mismatch?", False
546 ):
547 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 def _CheckDeployType(self):
550 if self.options.build_dir:
551
552 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600553 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600554 return os.path.exists(
555 os.path.join(self.options.build_dir, filename)
556 )
557
558 # In the future, lacros-chrome and ash-chrome will likely be named
559 # something other than 'chrome' to avoid confusion.
560 # Handle non-Chrome deployments.
561 if not BinaryExists("chrome"):
562 if BinaryExists("app_shell"):
563 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
564
565 def _PrepareStagingDir(self):
566 _PrepareStagingDir(
567 self.options,
568 self.tempdir,
569 self.staging_dir,
570 self.copy_paths,
571 self.chrome_dir,
572 )
573
574 def _MountTarget(self):
575 logging.info("Mounting Chrome...")
576
577 # Create directory if does not exist.
578 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
579 try:
580 # Umount the existing mount on mount_dir if present first.
581 self.device.run(
582 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
583 )
584 except cros_build_lib.RunCommandError as e:
585 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600586 # If there is a failure, check if some process is using the
587 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600588 result = self.device.run(
589 LSOF_COMMAND % (self.options.mount_dir,),
590 check=False,
591 capture_output=True,
592 encoding="utf-8",
593 )
594 logging.error("lsof %s -->", self.options.mount_dir)
595 logging.error(result.stdout)
596 raise e
597
598 self.device.run(
599 _BIND_TO_FINAL_DIR_CMD
600 % (self.options.target_dir, self.options.mount_dir)
601 )
602
603 # Chrome needs partition to have exec and suid flags set
604 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
605
606 def Cleanup(self):
607 """Clean up RemoteDevice."""
608 if not self.options.staging_only:
609 self.device.Cleanup()
610
611 def Perform(self):
612 self._CheckDeployType()
613
614 # If requested, just do the staging step.
615 if self.options.staging_only:
616 self._PrepareStagingDir()
617 return 0
618
Alex Klein68b270c2023-04-14 14:42:50 -0600619 # Check that the build matches the device. Lacros-chrome skips this
620 # check as it's currently board independent. This means that it's
621 # possible to deploy a build of lacros-chrome with a mismatched
622 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600623 if not self.options.lacros:
624 self._CheckBoard()
625
626 # Ensure that the target directory exists before running parallel steps.
627 self._EnsureTargetDir()
628
629 logging.info("Preparing device")
630 steps = [
631 self._GetDeviceInfo,
632 self._CheckConnection,
633 self._MountRootfsAsWritable,
634 self._PrepareStagingDir,
635 ]
636
637 restart_ui = True
638 if self.options.lacros:
Alex Klein68b270c2023-04-14 14:42:50 -0600639 # If this is a lacros build, we only want to restart ash-chrome if
640 # needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600641 restart_ui = False
642 steps.append(self._KillLacrosChrome)
643 if self.options.reset_lacros:
644 steps.append(self._ResetLacrosChrome)
645 if self.options.modify_config_file:
646 restart_ui = self._ModifyConfigFileIfNeededForLacros()
647
648 if restart_ui:
649 steps.append(self._KillAshChromeIfNeeded)
650 self._stopped_ui = True
651
652 ret = parallel.RunParallelSteps(
653 steps, halt_on_error=True, return_values=True
654 )
655 self._CheckDeviceFreeSpace(ret[0])
656
657 # If the root dir is not writable, try disabling rootfs verification.
658 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600659 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600660 if self._root_dir_is_still_readonly.is_set():
661 if self.options.noremove_rootfs_verification:
662 logging.warning("Skipping disable rootfs verification.")
663 elif not self._DisableRootfsVerification():
664 logging.warning("Failed to disable rootfs verification.")
665
Alex Klein68b270c2023-04-14 14:42:50 -0600666 # If the target dir is still not writable (i.e. the user opted out
667 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600668 if not self.device.IsDirWritable(self.options.target_dir):
669 if self.options.startui and self._stopped_ui:
670 logging.info("Restarting Chrome...")
671 self.device.run("start ui")
672 raise DeployFailure(
673 "Target location is not writable. Aborting."
674 )
675
676 if self.options.mount_dir is not None:
677 self._MountTarget()
678
679 # Actually deploy Chrome to the device.
680 self._Deploy()
681 if self.options.deploy_test_binaries:
682 self._DeployTestBinaries()
683
684 def _ModifyConfigFileIfNeededForLacros(self):
685 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
686
687 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600688 True if the file is modified, and the return value is usually used
689 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600690 """
691 assert (
692 self.options.lacros
693 ), "Only deploying lacros-chrome needs to modify the config file."
694 # Update /etc/chrome_dev.conf to include appropriate flags.
695 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000696 if self.options.enable_lacros_support:
697 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
698 if result.stdout.strip() == MODIFIED_CONF_FILE:
699 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600700 result = self.device.run(
701 _SET_LACROS_PATH_VIA_CONF_COMMAND
702 % {
703 "conf_file": _CONF_FILE,
704 "lacros_path": self.options.target_dir,
705 "modified_conf_file": MODIFIED_CONF_FILE,
706 },
707 shell=True,
708 )
709 if result.stdout.strip() == MODIFIED_CONF_FILE:
710 modified = True
711
712 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800713
714
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700715def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600716 """Convert formatted string to dictionary."""
717 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700718
719
Steven Bennetts368c3e52016-09-23 13:05:21 -0700720def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600721 """Convert GN_ARGS-formatted string to dictionary."""
722 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700723
724
Ryan Cuie535b172012-10-19 18:25:03 -0700725def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600726 """Create our custom parser."""
727 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700728
Alex Klein1699fab2022-09-08 08:46:06 -0600729 # TODO(rcui): Have this use the UI-V2 format of having source and target
730 # device be specified as positional arguments.
731 parser.add_argument(
732 "--force",
733 action="store_true",
734 default=False,
735 help="Skip all prompts (such as the prompt for disabling "
736 "of rootfs verification). This may result in the "
737 "target machine being rebooted.",
738 )
739 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
740 parser.add_argument(
741 "--board",
742 default=sdk_board_env,
743 help="The board the Chrome build is targeted for. When "
744 "in a 'cros chrome-sdk' shell, defaults to the SDK "
745 "board.",
746 )
747 parser.add_argument(
748 "--build-dir",
749 type="path",
750 help="The directory with Chrome build artifacts to "
751 "deploy from. Typically of format "
752 "<chrome_root>/out/Debug. When this option is used, "
753 "the GN_ARGS environment variable must be set.",
754 )
755 parser.add_argument(
756 "--target-dir",
757 type="path",
758 default=None,
759 help="Target directory on device to deploy Chrome into.",
760 )
761 parser.add_argument(
762 "-g",
763 "--gs-path",
764 type="gs_path",
765 help="GS path that contains the chrome to deploy.",
766 )
767 parser.add_argument(
768 "--private-key",
769 type="path",
770 default=None,
771 help="An ssh private key to use when deploying to " "a CrOS device.",
772 )
773 parser.add_argument(
774 "--nostartui",
775 action="store_false",
776 dest="startui",
777 default=True,
778 help="Don't restart the ui daemon after deployment.",
779 )
780 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700781 "--unlock-password",
782 default=None,
783 help="Password to use to unlock after deployment and restart.",
784 )
785 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600786 "--nostrip",
787 action="store_false",
788 dest="dostrip",
789 default=True,
790 help="Don't strip binaries during deployment. Warning: "
791 "the resulting binaries will be very large!",
792 )
793 parser.add_argument(
794 "-d",
795 "--device",
796 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
797 help="Device hostname or IP in the format hostname[:port].",
798 )
799 parser.add_argument(
800 "--mount-dir",
801 type="path",
802 default=None,
803 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000804 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600805 "Any existing mount on this directory will be "
806 "umounted first.",
807 )
808 parser.add_argument(
809 "--mount",
810 action="store_true",
811 default=False,
812 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000813 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600814 "Any existing mount on this directory will be "
815 "umounted first.",
816 )
817 parser.add_argument(
818 "--noremove-rootfs-verification",
819 action="store_true",
820 default=False,
821 help="Never remove rootfs verification.",
822 )
823 parser.add_argument(
824 "--deploy-test-binaries",
825 action="store_true",
826 default=False,
827 help="Also deploy any test binaries to %s. Useful for "
828 "running any Tast tests that execute these "
829 "binaries." % _CHROME_TEST_BIN_DIR,
830 )
831 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600832 "--use-external-config",
833 action="store_true",
834 help="When identifying the configuration for a board, "
835 "force usage of the external configuration if both "
836 "internal and external are available. This only "
837 "has an effect when stripping Chrome, i.e. when "
838 "--nostrip is not passed in.",
839 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700840
Georg Neis9b1ff192022-09-14 08:07:22 +0000841 group = parser.add_argument_group("Lacros Options")
842 group.add_argument(
843 "--lacros",
844 action="store_true",
845 default=False,
846 help="Deploys lacros-chrome rather than ash-chrome.",
847 )
848 group.add_argument(
849 "--reset-lacros",
850 action="store_true",
851 default=False,
852 help="Reset Lacros by deleting Lacros user data dir if it exists.",
853 )
854 group.add_argument(
855 "--skip-enabling-lacros-support",
856 action="store_false",
857 dest="enable_lacros_support",
858 help="By default, deploying lacros-chrome modifies the "
859 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
860 "and (2) set the Lacros path, which can interfere with automated "
861 "testing. With this flag, part (1) will be skipped. See the "
862 "--skip-modifying-config-file flag for skipping both parts.",
863 )
864 group.add_argument(
865 "--skip-modifying-config-file",
866 action="store_false",
867 dest="modify_config_file",
868 help="When deploying lacros-chrome, do not modify the "
869 "/etc/chrome_dev.conf file. See also the "
870 "--skip-enabling-lacros-support flag.",
871 )
872
Alex Klein1699fab2022-09-08 08:46:06 -0600873 group = parser.add_argument_group("Advanced Options")
874 group.add_argument(
875 "-l",
876 "--local-pkg-path",
877 type="path",
878 help="Path to local chrome prebuilt package to deploy.",
879 )
880 group.add_argument(
881 "--sloppy",
882 action="store_true",
883 default=False,
884 help="Ignore when mandatory artifacts are missing.",
885 )
886 group.add_argument(
887 "--staging-flags",
888 default=None,
889 type=ValidateStagingFlags,
890 help=(
891 "Extra flags to control staging. Valid flags are - "
892 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
893 ),
894 )
895 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
896 group.add_argument(
897 "--strict",
898 action="store_true",
899 default=False,
900 help='Deprecated. Default behavior is "strict". Use '
901 "--sloppy to omit warnings for missing optional "
902 "files.",
903 )
904 group.add_argument(
905 "--strip-flags",
906 default=None,
907 help="Flags to call the 'strip' binutil tool with. "
908 "Overrides the default arguments.",
909 )
910 group.add_argument(
911 "--ping",
912 action="store_true",
913 default=False,
914 help="Ping the device before connection attempt.",
915 )
916 group.add_argument(
917 "--process-timeout",
918 type=int,
919 default=KILL_PROC_MAX_WAIT,
920 help="Timeout for process shutdown.",
921 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700922
Alex Klein1699fab2022-09-08 08:46:06 -0600923 group = parser.add_argument_group(
924 "Metadata Overrides (Advanced)",
925 description="Provide all of these overrides in order to remove "
926 "dependencies on metadata.json existence.",
927 )
928 group.add_argument(
929 "--target-tc",
930 action="store",
931 default=None,
932 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
933 )
934 group.add_argument(
935 "--toolchain-url",
936 action="store",
937 default=None,
938 help="Override toolchain url format pattern, e.g. "
939 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
940 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700941
Alex Klein1699fab2022-09-08 08:46:06 -0600942 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
943 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
944 parser.add_argument(
945 "--gyp-defines",
946 default=None,
947 type=ValidateStagingFlags,
948 help=argparse.SUPPRESS,
949 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700950
Alex Klein1699fab2022-09-08 08:46:06 -0600951 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600952 # when --build-dir is set. Defaults to reading from the GN_ARGS env
953 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600954 parser.add_argument(
955 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
956 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700957
Alex Klein1699fab2022-09-08 08:46:06 -0600958 # Path of an empty directory to stage chrome artifacts to. Defaults to a
959 # temporary directory that is removed when the script finishes. If the path
960 # is specified, then it will not be removed.
961 parser.add_argument(
962 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
963 )
964 # Only prepare the staging directory, and skip deploying to the device.
965 parser.add_argument(
966 "--staging-only",
967 action="store_true",
968 default=False,
969 help=argparse.SUPPRESS,
970 )
971 # Uploads the compressed staging directory to the given gs:// path URI.
972 parser.add_argument(
973 "--staging-upload",
974 type="gs_path",
975 help="GS path to upload the compressed staging files to.",
976 )
977 # Used alongside --staging-upload to upload with public-read ACL.
978 parser.add_argument(
979 "--public-read",
980 action="store_true",
981 default=False,
982 help="GS path to upload the compressed staging files to.",
983 )
984 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
985 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
986 # fetching the SDK toolchain.
987 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
988 parser.add_argument(
989 "--compress",
990 action="store",
991 default="auto",
992 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000993 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -0600994 'is set to "auto", that disables compression if '
995 "the target device has a gigabit ethernet port.",
996 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000997 parser.add_argument(
998 "--compressed-ash",
999 action="store_true",
1000 default=False,
1001 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1002 "binary is stored on DUT in squashfs, mounted upon boot.",
1003 )
Alex Klein1699fab2022-09-08 08:46:06 -06001004 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001005
Ryan Cuie535b172012-10-19 18:25:03 -07001006
1007def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001008 """Parse args, and run environment-independent checks."""
1009 parser = _CreateParser()
1010 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001011
Alex Klein1699fab2022-09-08 08:46:06 -06001012 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1013 parser.error(
1014 "Need to specify either --gs-path, --local-pkg-path, or "
1015 "--build-dir"
1016 )
1017 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1018 parser.error(
1019 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1020 )
1021 if options.lacros:
1022 if options.dostrip and not options.board:
1023 parser.error("Please specify --board.")
1024 if options.mount_dir or options.mount:
1025 parser.error("--lacros does not support --mount or --mount-dir")
1026 if options.deploy_test_binaries:
1027 parser.error("--lacros does not support --deploy-test-binaries")
1028 if options.local_pkg_path:
1029 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001030 if options.compressed_ash:
1031 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001032 else:
1033 if not options.board and options.build_dir:
1034 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1035 if match:
1036 options.board = match.group(1)
1037 logging.info("--board is set to %s", options.board)
1038 if not options.board:
1039 parser.error("--board is required")
1040 if options.gs_path and options.local_pkg_path:
1041 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1042 if not (options.staging_only or options.device):
1043 parser.error("Need to specify --device")
1044 if options.staging_flags and not options.build_dir:
1045 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001046
Alex Klein1699fab2022-09-08 08:46:06 -06001047 if options.strict:
1048 logging.warning("--strict is deprecated.")
1049 if options.gyp_defines:
1050 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001051
Alex Klein1699fab2022-09-08 08:46:06 -06001052 if options.mount or options.mount_dir:
1053 if not options.target_dir:
1054 options.target_dir = _CHROME_DIR_MOUNT
1055 else:
1056 if not options.target_dir:
1057 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001058
Alex Klein1699fab2022-09-08 08:46:06 -06001059 if options.mount and not options.mount_dir:
1060 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001061
Alex Klein1699fab2022-09-08 08:46:06 -06001062 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001063
1064
Mike Frysingerc3061a62015-06-04 04:16:18 -04001065def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001066 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001069 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001070 """
1071 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1072 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001073
Alex Klein1699fab2022-09-08 08:46:06 -06001074 if not options.gn_args:
1075 gn_env = os.getenv("GN_ARGS")
1076 if gn_env is not None:
1077 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1078 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001079
Alex Klein1699fab2022-09-08 08:46:06 -06001080 if not options.staging_flags:
1081 use_env = os.getenv("USE")
1082 if use_env is not None:
1083 options.staging_flags = " ".join(
1084 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1085 )
1086 logging.info(
1087 "Staging flags taken from USE in environment: %s",
1088 options.staging_flags,
1089 )
Steven Bennetts60600462016-05-12 10:40:20 -07001090
Ryan Cuia56a71e2012-10-18 18:40:35 -07001091
Ryan Cui504db722013-01-22 11:48:01 -08001092def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001093 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001094
Alex Klein1699fab2022-09-08 08:46:06 -06001095 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001096 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001097 """
1098 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1099 files = gs_ctx.LS(gs_path)
1100 files = [
1101 found
1102 for found in files
1103 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1104 ]
1105 if not files:
1106 raise Exception("No chrome package found at %s" % gs_path)
1107 elif len(files) > 1:
1108 # - Users should provide us with a direct link to either a stripped or
1109 # unstripped chrome package.
1110 # - In the case of being provided with an archive directory, where both
1111 # stripped and unstripped chrome available, use the stripped chrome
1112 # package.
1113 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001114 # - Unstripped chrome pkg is
1115 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001116 files = [f for f in files if not "unstripped" in f]
1117 assert len(files) == 1
1118 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001119
Alex Klein1699fab2022-09-08 08:46:06 -06001120 filename = _UrlBaseName(files[0])
1121 logging.info("Fetching %s...", filename)
1122 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1123 chrome_path = os.path.join(tempdir, filename)
1124 assert os.path.exists(chrome_path)
1125 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001126
1127
Ryan Cuif890a3e2013-03-07 18:57:06 -08001128@contextlib.contextmanager
1129def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001130 if not options.dostrip:
1131 yield None
1132 elif options.strip_bin:
1133 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001134 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001135 sdk = cros_chrome_sdk.SDKFetcher(
1136 options.cache_dir,
1137 options.board,
1138 use_external_config=options.use_external_config,
1139 )
1140 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1141 with sdk.Prepare(
1142 components=components,
1143 target_tc=options.target_tc,
1144 toolchain_url=options.toolchain_url,
1145 ) as ctx:
1146 env_path = os.path.join(
1147 ctx.key_map[constants.CHROME_ENV_TAR].path,
1148 constants.CHROME_ENV_FILE,
1149 )
1150 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1151 strip_bin = os.path.join(
1152 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1153 "bin",
1154 os.path.basename(strip_bin),
1155 )
1156 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001157
Alex Klein1699fab2022-09-08 08:46:06 -06001158
1159def _UploadStagingDir(
1160 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1161) -> None:
1162 """Uploads the compressed staging directory.
1163
1164 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001165 options: options object.
1166 tempdir: Scratch space.
1167 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001168 """
1169 staging_tarball_path = os.path.join(
1170 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1171 )
1172 logging.info(
1173 "Compressing staging dir (%s) to (%s)",
1174 staging_dir,
1175 staging_tarball_path,
1176 )
1177 cros_build_lib.CreateTarball(
1178 staging_tarball_path,
1179 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001180 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001181 extra_env={"ZSTD_CLEVEL": "9"},
1182 )
1183 logging.info(
1184 "Uploading staging tarball (%s) into %s",
1185 staging_tarball_path,
1186 options.staging_upload,
1187 )
1188 ctx = gs.GSContext()
1189 ctx.Copy(
1190 staging_tarball_path,
1191 options.staging_upload,
1192 acl="public-read" if options.public_read else "",
1193 )
1194
1195
1196def _PrepareStagingDir(
1197 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1198):
1199 """Place the necessary files in the staging directory.
1200
Alex Klein68b270c2023-04-14 14:42:50 -06001201 The staging directory is the directory used to rsync the build artifacts
1202 over to the device. Only the necessary Chrome build artifacts are put into
1203 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001204 """
1205 if chrome_dir is None:
1206 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1207 osutils.SafeMakedirs(staging_dir)
1208 os.chmod(staging_dir, 0o755)
1209 if options.build_dir:
1210 with _StripBinContext(options) as strip_bin:
1211 strip_flags = (
1212 None
1213 if options.strip_flags is None
1214 else shlex.split(options.strip_flags)
1215 )
1216 chrome_util.StageChromeFromBuildDir(
1217 staging_dir,
1218 options.build_dir,
1219 strip_bin,
1220 sloppy=options.sloppy,
1221 gn_args=options.gn_args,
1222 staging_flags=options.staging_flags,
1223 strip_flags=strip_flags,
1224 copy_paths=copy_paths,
1225 )
1226 else:
1227 pkg_path = options.local_pkg_path
1228 if options.gs_path:
1229 pkg_path = _FetchChromePackage(
1230 options.cache_dir, tempdir, options.gs_path
1231 )
1232
1233 assert pkg_path
1234 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001235 # Extract only the ./opt/google/chrome contents, directly into the
1236 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001237 if pkg_path[-4:] == ".zip":
1238 cros_build_lib.dbg_run(
1239 [
1240 "unzip",
1241 "-X",
1242 pkg_path,
1243 _ANDROID_DIR_EXTRACT_PATH,
1244 "-d",
1245 staging_dir,
1246 ]
1247 )
1248 for filename in glob.glob(
1249 os.path.join(staging_dir, "system/chrome/*")
1250 ):
1251 shutil.move(filename, staging_dir)
1252 osutils.RmDir(
1253 os.path.join(staging_dir, "system"), ignore_missing=True
1254 )
1255 else:
1256 compression = cros_build_lib.CompressionDetectType(pkg_path)
1257 compressor = cros_build_lib.FindCompressor(compression)
1258 cros_build_lib.dbg_run(
1259 [
1260 "tar",
1261 "--strip-components",
1262 "4",
1263 "--extract",
1264 "-I",
1265 compressor,
1266 "--preserve-permissions",
1267 "--file",
1268 pkg_path,
1269 ".%s" % chrome_dir,
1270 ],
1271 cwd=staging_dir,
1272 )
1273
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001274 if options.compressed_ash:
1275 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1276 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001277 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1278 # this is in test, cut the known suffix of experimental overlays.
1279 sdk_orig_board = options.board
1280 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1281 sdk_orig_board = sdk_orig_board[
1282 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1283 ]
1284
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001285 sdk = cros_chrome_sdk.SDKFetcher(
1286 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001287 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001288 use_external_config=options.use_external_config,
1289 )
1290 with sdk.Prepare(
1291 components=[],
1292 target_tc=options.target_tc,
1293 toolchain_url=options.toolchain_url,
1294 ):
1295 cros_build_lib.dbg_run(
1296 [
1297 "mksquashfs",
1298 RAW_ASH_FILE,
1299 COMPRESSED_ASH_FILE,
1300 "-all-root",
1301 "-no-progress",
1302 "-comp",
1303 "zstd",
1304 ],
1305 cwd=staging_dir,
1306 )
1307 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1308
Alex Klein1699fab2022-09-08 08:46:06 -06001309 if options.staging_upload:
1310 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001311
Ryan Cui71aa8de2013-04-19 16:12:55 -07001312
Ryan Cui3045c5d2012-07-13 18:00:33 -07001313def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001314 options = _ParseCommandLine(argv)
1315 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001316
Alex Klein1699fab2022-09-08 08:46:06 -06001317 with osutils.TempDir(set_global=True) as tempdir:
1318 staging_dir = options.staging_dir
1319 if not staging_dir:
1320 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001321
Alex Klein1699fab2022-09-08 08:46:06 -06001322 deploy = DeployChrome(options, tempdir, staging_dir)
1323 try:
1324 deploy.Perform()
1325 except failures_lib.StepFailure as ex:
1326 raise SystemExit(str(ex).strip())
1327 deploy.Cleanup()