blob: 09b9f3949890f736fc0a445f7bd40b97faaf0a9f [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():
Sergiy Belozorovc867c7e2023-05-26 18:11:15 +0200415 # This assumes that rsync is part of the bootstrap package. In the
416 # future, this might change and we'll have to install it separately.
417 if not cros_build_lib.BooleanPrompt(
418 "Run dev_install on the device to install rsync?", True
419 ):
420 raise DeployFailure("rsync is not found on the device.")
421 self.device.BootstrapDevTools()
422 if not self.device.HasRsync():
423 raise DeployFailure("Failed to install rsync")
424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 self.device.CopyToDevice(
426 "%s/" % os.path.abspath(self.staging_dir),
427 self.options.target_dir,
428 mode="rsync",
429 inplace=True,
430 compress=self._ShouldUseCompression(),
431 debug_level=logging.INFO,
432 verbose=self.options.verbose,
433 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700434
Alex Klein68b270c2023-04-14 14:42:50 -0600435 # Set the security context on the default Chrome dir if that's where
436 # it's getting deployed, and only on SELinux supported devices.
Alex Klein1699fab2022-09-08 08:46:06 -0600437 if (
438 not self.options.lacros
439 and self.device.IsSELinuxAvailable()
440 and (
441 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
442 )
443 ):
444 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800445
Alex Klein1699fab2022-09-08 08:46:06 -0600446 for p in self.copy_paths:
447 if p.mode:
448 # Set mode if necessary.
449 self.device.run(
450 "chmod %o %s/%s"
451 % (
452 p.mode,
453 self.options.target_dir,
454 p.src if not p.dest else p.dest,
455 )
456 )
Steve Funge984a532013-11-25 17:09:25 -0800457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 if self.options.lacros:
459 self.device.run(
460 ["chown", "-R", "chronos:chronos", self.options.target_dir]
461 )
Erik Chen75a2f492020-08-06 19:15:11 -0700462
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000463 if self.options.compressed_ash:
464 self.device.run(["start", COMPRESSED_ASH_SERVICE])
465
Alex Klein68b270c2023-04-14 14:42:50 -0600466 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This
467 # won't pick up major changes (bus type, logging, etc.), but all we care
468 # about is getting the latest policy from /opt/google/chrome/dbus so
469 # that Chrome will be authorized to take ownership of its service names.
Alex Klein1699fab2022-09-08 08:46:06 -0600470 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600471
Erik Chen75a2f492020-08-06 19:15:11 -0700472 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700473 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600474 logging.info("Starting UI...")
475 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800476
Joel Hockey764728e2023-03-14 17:10:04 -0700477 if self.options.unlock_password:
478 logging.info("Unlocking...")
479
480 @retry_util.WithRetry(max_retry=5, sleep=1)
481 def WaitForUnlockScreen():
482 if self._GetLastLogin() == last_login:
483 raise DeployFailure("Unlock screen not shown")
484
485 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700486 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700487 self.device.run(
488 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
489 )
490
491 def _GetLastLogin(self):
492 """Returns last login time"""
493 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
494
Alex Klein1699fab2022-09-08 08:46:06 -0600495 def _DeployTestBinaries(self):
496 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700497
Alex Klein68b270c2023-04-14 14:42:50 -0600498 There could be several binaries located in the local build dir, so
499 compare what's already present on the device in _CHROME_TEST_BIN_DIR ,
500 and copy over any that we also built ourselves.
Alex Klein1699fab2022-09-08 08:46:06 -0600501 """
502 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
503 if r.returncode != 0:
504 raise DeployFailure(
505 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
506 )
507 binaries_to_copy = []
508 for f in r.stdout.splitlines():
509 binaries_to_copy.append(
510 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
511 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700512
Alex Klein1699fab2022-09-08 08:46:06 -0600513 staging_dir = os.path.join(
514 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
515 )
516 _PrepareStagingDir(
517 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
518 )
519 # Deploying can occasionally run into issues with rsync getting a broken
520 # pipe, so retry several times. See crbug.com/1141618 for more
521 # information.
522 retry_util.RetryException(
523 None,
524 3,
525 self.device.CopyToDevice,
526 staging_dir,
527 os.path.dirname(_CHROME_TEST_BIN_DIR),
528 mode="rsync",
529 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700530
Alex Klein1699fab2022-09-08 08:46:06 -0600531 def _CheckConnection(self):
532 try:
533 logging.info("Testing connection to the device...")
534 self.device.run("true")
535 except cros_build_lib.RunCommandError as ex:
536 logging.error("Error connecting to the test device.")
537 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 def _CheckBoard(self):
540 """Check that the Chrome build is targeted for the device board."""
541 if self.options.board == self.device.board:
542 return
543 logging.warning(
544 "Device board is %s whereas target board is %s.",
545 self.device.board,
546 self.options.board,
547 )
548 if self.options.force:
549 return
550 if not cros_build_lib.BooleanPrompt(
551 "Continue despite board mismatch?", False
552 ):
553 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800554
Alex Klein1699fab2022-09-08 08:46:06 -0600555 def _CheckDeployType(self):
556 if self.options.build_dir:
557
558 def BinaryExists(filename):
Alex Klein68b270c2023-04-14 14:42:50 -0600559 """Checks if |filename| is present in the build directory."""
Alex Klein1699fab2022-09-08 08:46:06 -0600560 return os.path.exists(
561 os.path.join(self.options.build_dir, filename)
562 )
563
564 # In the future, lacros-chrome and ash-chrome will likely be named
565 # something other than 'chrome' to avoid confusion.
566 # Handle non-Chrome deployments.
567 if not BinaryExists("chrome"):
568 if BinaryExists("app_shell"):
569 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
570
571 def _PrepareStagingDir(self):
572 _PrepareStagingDir(
573 self.options,
574 self.tempdir,
575 self.staging_dir,
576 self.copy_paths,
577 self.chrome_dir,
578 )
579
580 def _MountTarget(self):
581 logging.info("Mounting Chrome...")
582
583 # Create directory if does not exist.
584 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
585 try:
586 # Umount the existing mount on mount_dir if present first.
587 self.device.run(
588 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
589 )
590 except cros_build_lib.RunCommandError as e:
591 logging.error("Failed to umount %s", self.options.mount_dir)
Alex Klein68b270c2023-04-14 14:42:50 -0600592 # If there is a failure, check if some process is using the
593 # mount_dir.
Alex Klein1699fab2022-09-08 08:46:06 -0600594 result = self.device.run(
595 LSOF_COMMAND % (self.options.mount_dir,),
596 check=False,
597 capture_output=True,
598 encoding="utf-8",
599 )
600 logging.error("lsof %s -->", self.options.mount_dir)
601 logging.error(result.stdout)
602 raise e
603
604 self.device.run(
605 _BIND_TO_FINAL_DIR_CMD
606 % (self.options.target_dir, self.options.mount_dir)
607 )
608
609 # Chrome needs partition to have exec and suid flags set
610 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
611
612 def Cleanup(self):
613 """Clean up RemoteDevice."""
614 if not self.options.staging_only:
615 self.device.Cleanup()
616
617 def Perform(self):
618 self._CheckDeployType()
619
620 # If requested, just do the staging step.
621 if self.options.staging_only:
622 self._PrepareStagingDir()
623 return 0
624
Alex Klein68b270c2023-04-14 14:42:50 -0600625 # Check that the build matches the device. Lacros-chrome skips this
626 # check as it's currently board independent. This means that it's
627 # possible to deploy a build of lacros-chrome with a mismatched
628 # architecture. We don't try to prevent this developer foot-gun.
Alex Klein1699fab2022-09-08 08:46:06 -0600629 if not self.options.lacros:
630 self._CheckBoard()
631
632 # Ensure that the target directory exists before running parallel steps.
633 self._EnsureTargetDir()
634
635 logging.info("Preparing device")
636 steps = [
637 self._GetDeviceInfo,
638 self._CheckConnection,
639 self._MountRootfsAsWritable,
640 self._PrepareStagingDir,
641 ]
642
643 restart_ui = True
644 if self.options.lacros:
Alex Klein68b270c2023-04-14 14:42:50 -0600645 # If this is a lacros build, we only want to restart ash-chrome if
646 # needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600647 restart_ui = False
648 steps.append(self._KillLacrosChrome)
649 if self.options.reset_lacros:
650 steps.append(self._ResetLacrosChrome)
651 if self.options.modify_config_file:
652 restart_ui = self._ModifyConfigFileIfNeededForLacros()
653
654 if restart_ui:
655 steps.append(self._KillAshChromeIfNeeded)
656 self._stopped_ui = True
657
658 ret = parallel.RunParallelSteps(
659 steps, halt_on_error=True, return_values=True
660 )
661 self._CheckDeviceFreeSpace(ret[0])
662
663 # If the root dir is not writable, try disabling rootfs verification.
664 # (We always do this by default so that developers can write to
Alex Klein68b270c2023-04-14 14:42:50 -0600665 # /etc/chrome_dev.conf and other directories in the rootfs).
Alex Klein1699fab2022-09-08 08:46:06 -0600666 if self._root_dir_is_still_readonly.is_set():
667 if self.options.noremove_rootfs_verification:
668 logging.warning("Skipping disable rootfs verification.")
669 elif not self._DisableRootfsVerification():
670 logging.warning("Failed to disable rootfs verification.")
671
Alex Klein68b270c2023-04-14 14:42:50 -0600672 # If the target dir is still not writable (i.e. the user opted out
673 # or the command failed), abort.
Alex Klein1699fab2022-09-08 08:46:06 -0600674 if not self.device.IsDirWritable(self.options.target_dir):
675 if self.options.startui and self._stopped_ui:
676 logging.info("Restarting Chrome...")
677 self.device.run("start ui")
678 raise DeployFailure(
679 "Target location is not writable. Aborting."
680 )
681
682 if self.options.mount_dir is not None:
683 self._MountTarget()
684
685 # Actually deploy Chrome to the device.
686 self._Deploy()
687 if self.options.deploy_test_binaries:
688 self._DeployTestBinaries()
689
690 def _ModifyConfigFileIfNeededForLacros(self):
691 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
692
693 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600694 True if the file is modified, and the return value is usually used
695 to determine whether restarting ash-chrome is needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600696 """
697 assert (
698 self.options.lacros
699 ), "Only deploying lacros-chrome needs to modify the config file."
700 # Update /etc/chrome_dev.conf to include appropriate flags.
701 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000702 if self.options.enable_lacros_support:
703 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
704 if result.stdout.strip() == MODIFIED_CONF_FILE:
705 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600706 result = self.device.run(
707 _SET_LACROS_PATH_VIA_CONF_COMMAND
708 % {
709 "conf_file": _CONF_FILE,
710 "lacros_path": self.options.target_dir,
711 "modified_conf_file": MODIFIED_CONF_FILE,
712 },
713 shell=True,
714 )
715 if result.stdout.strip() == MODIFIED_CONF_FILE:
716 modified = True
717
718 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800719
720
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700721def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600722 """Convert formatted string to dictionary."""
723 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700724
725
Steven Bennetts368c3e52016-09-23 13:05:21 -0700726def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600727 """Convert GN_ARGS-formatted string to dictionary."""
728 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700729
730
Ryan Cuie535b172012-10-19 18:25:03 -0700731def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600732 """Create our custom parser."""
733 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700734
Alex Klein1699fab2022-09-08 08:46:06 -0600735 # TODO(rcui): Have this use the UI-V2 format of having source and target
736 # device be specified as positional arguments.
737 parser.add_argument(
738 "--force",
739 action="store_true",
740 default=False,
741 help="Skip all prompts (such as the prompt for disabling "
742 "of rootfs verification). This may result in the "
743 "target machine being rebooted.",
744 )
745 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
746 parser.add_argument(
747 "--board",
748 default=sdk_board_env,
749 help="The board the Chrome build is targeted for. When "
750 "in a 'cros chrome-sdk' shell, defaults to the SDK "
751 "board.",
752 )
753 parser.add_argument(
754 "--build-dir",
755 type="path",
756 help="The directory with Chrome build artifacts to "
757 "deploy from. Typically of format "
758 "<chrome_root>/out/Debug. When this option is used, "
759 "the GN_ARGS environment variable must be set.",
760 )
761 parser.add_argument(
762 "--target-dir",
763 type="path",
764 default=None,
765 help="Target directory on device to deploy Chrome into.",
766 )
767 parser.add_argument(
768 "-g",
769 "--gs-path",
770 type="gs_path",
771 help="GS path that contains the chrome to deploy.",
772 )
773 parser.add_argument(
774 "--private-key",
775 type="path",
776 default=None,
777 help="An ssh private key to use when deploying to " "a CrOS device.",
778 )
779 parser.add_argument(
780 "--nostartui",
781 action="store_false",
782 dest="startui",
783 default=True,
784 help="Don't restart the ui daemon after deployment.",
785 )
786 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700787 "--unlock-password",
788 default=None,
789 help="Password to use to unlock after deployment and restart.",
790 )
791 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600792 "--nostrip",
793 action="store_false",
794 dest="dostrip",
795 default=True,
796 help="Don't strip binaries during deployment. Warning: "
797 "the resulting binaries will be very large!",
798 )
799 parser.add_argument(
800 "-d",
801 "--device",
802 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
803 help="Device hostname or IP in the format hostname[:port].",
804 )
805 parser.add_argument(
806 "--mount-dir",
807 type="path",
808 default=None,
809 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000810 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600811 "Any existing mount on this directory will be "
812 "umounted first.",
813 )
814 parser.add_argument(
815 "--mount",
816 action="store_true",
817 default=False,
818 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000819 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600820 "Any existing mount on this directory will be "
821 "umounted first.",
822 )
823 parser.add_argument(
824 "--noremove-rootfs-verification",
825 action="store_true",
826 default=False,
827 help="Never remove rootfs verification.",
828 )
829 parser.add_argument(
830 "--deploy-test-binaries",
831 action="store_true",
832 default=False,
833 help="Also deploy any test binaries to %s. Useful for "
834 "running any Tast tests that execute these "
835 "binaries." % _CHROME_TEST_BIN_DIR,
836 )
837 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600838 "--use-external-config",
839 action="store_true",
840 help="When identifying the configuration for a board, "
841 "force usage of the external configuration if both "
842 "internal and external are available. This only "
843 "has an effect when stripping Chrome, i.e. when "
844 "--nostrip is not passed in.",
845 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700846
Georg Neis9b1ff192022-09-14 08:07:22 +0000847 group = parser.add_argument_group("Lacros Options")
848 group.add_argument(
849 "--lacros",
850 action="store_true",
851 default=False,
852 help="Deploys lacros-chrome rather than ash-chrome.",
853 )
854 group.add_argument(
855 "--reset-lacros",
856 action="store_true",
857 default=False,
858 help="Reset Lacros by deleting Lacros user data dir if it exists.",
859 )
860 group.add_argument(
861 "--skip-enabling-lacros-support",
862 action="store_false",
863 dest="enable_lacros_support",
864 help="By default, deploying lacros-chrome modifies the "
865 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
866 "and (2) set the Lacros path, which can interfere with automated "
867 "testing. With this flag, part (1) will be skipped. See the "
868 "--skip-modifying-config-file flag for skipping both parts.",
869 )
870 group.add_argument(
871 "--skip-modifying-config-file",
872 action="store_false",
873 dest="modify_config_file",
874 help="When deploying lacros-chrome, do not modify the "
875 "/etc/chrome_dev.conf file. See also the "
876 "--skip-enabling-lacros-support flag.",
877 )
878
Alex Klein1699fab2022-09-08 08:46:06 -0600879 group = parser.add_argument_group("Advanced Options")
880 group.add_argument(
881 "-l",
882 "--local-pkg-path",
883 type="path",
884 help="Path to local chrome prebuilt package to deploy.",
885 )
886 group.add_argument(
887 "--sloppy",
888 action="store_true",
889 default=False,
890 help="Ignore when mandatory artifacts are missing.",
891 )
892 group.add_argument(
893 "--staging-flags",
894 default=None,
895 type=ValidateStagingFlags,
896 help=(
897 "Extra flags to control staging. Valid flags are - "
898 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
899 ),
900 )
901 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
902 group.add_argument(
903 "--strict",
904 action="store_true",
905 default=False,
906 help='Deprecated. Default behavior is "strict". Use '
907 "--sloppy to omit warnings for missing optional "
908 "files.",
909 )
910 group.add_argument(
911 "--strip-flags",
912 default=None,
913 help="Flags to call the 'strip' binutil tool with. "
914 "Overrides the default arguments.",
915 )
916 group.add_argument(
917 "--ping",
918 action="store_true",
919 default=False,
920 help="Ping the device before connection attempt.",
921 )
922 group.add_argument(
923 "--process-timeout",
924 type=int,
925 default=KILL_PROC_MAX_WAIT,
926 help="Timeout for process shutdown.",
927 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700928
Alex Klein1699fab2022-09-08 08:46:06 -0600929 group = parser.add_argument_group(
930 "Metadata Overrides (Advanced)",
931 description="Provide all of these overrides in order to remove "
932 "dependencies on metadata.json existence.",
933 )
934 group.add_argument(
935 "--target-tc",
936 action="store",
937 default=None,
938 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
939 )
940 group.add_argument(
941 "--toolchain-url",
942 action="store",
943 default=None,
944 help="Override toolchain url format pattern, e.g. "
945 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
946 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700947
Alex Klein1699fab2022-09-08 08:46:06 -0600948 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
949 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
950 parser.add_argument(
951 "--gyp-defines",
952 default=None,
953 type=ValidateStagingFlags,
954 help=argparse.SUPPRESS,
955 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700956
Alex Klein1699fab2022-09-08 08:46:06 -0600957 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
Alex Klein68b270c2023-04-14 14:42:50 -0600958 # when --build-dir is set. Defaults to reading from the GN_ARGS env
959 # variable. CURRENTLY IGNORED, ADDED FOR FORWARD COMPATIBILITY.
Alex Klein1699fab2022-09-08 08:46:06 -0600960 parser.add_argument(
961 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
962 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700963
Alex Klein1699fab2022-09-08 08:46:06 -0600964 # Path of an empty directory to stage chrome artifacts to. Defaults to a
965 # temporary directory that is removed when the script finishes. If the path
966 # is specified, then it will not be removed.
967 parser.add_argument(
968 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
969 )
970 # Only prepare the staging directory, and skip deploying to the device.
971 parser.add_argument(
972 "--staging-only",
973 action="store_true",
974 default=False,
975 help=argparse.SUPPRESS,
976 )
977 # Uploads the compressed staging directory to the given gs:// path URI.
978 parser.add_argument(
979 "--staging-upload",
980 type="gs_path",
981 help="GS path to upload the compressed staging files to.",
982 )
983 # Used alongside --staging-upload to upload with public-read ACL.
984 parser.add_argument(
985 "--public-read",
986 action="store_true",
987 default=False,
988 help="GS path to upload the compressed staging files to.",
989 )
990 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
991 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
992 # fetching the SDK toolchain.
993 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
994 parser.add_argument(
995 "--compress",
996 action="store",
997 default="auto",
998 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000999 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -06001000 'is set to "auto", that disables compression if '
1001 "the target device has a gigabit ethernet port.",
1002 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001003 parser.add_argument(
1004 "--compressed-ash",
1005 action="store_true",
1006 default=False,
1007 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
1008 "binary is stored on DUT in squashfs, mounted upon boot.",
1009 )
Alex Klein1699fab2022-09-08 08:46:06 -06001010 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001011
Ryan Cuie535b172012-10-19 18:25:03 -07001012
1013def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001014 """Parse args, and run environment-independent checks."""
1015 parser = _CreateParser()
1016 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001017
Alex Klein1699fab2022-09-08 08:46:06 -06001018 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1019 parser.error(
1020 "Need to specify either --gs-path, --local-pkg-path, or "
1021 "--build-dir"
1022 )
1023 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1024 parser.error(
1025 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1026 )
1027 if options.lacros:
1028 if options.dostrip and not options.board:
1029 parser.error("Please specify --board.")
1030 if options.mount_dir or options.mount:
1031 parser.error("--lacros does not support --mount or --mount-dir")
1032 if options.deploy_test_binaries:
1033 parser.error("--lacros does not support --deploy-test-binaries")
1034 if options.local_pkg_path:
1035 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001036 if options.compressed_ash:
1037 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001038 else:
1039 if not options.board and options.build_dir:
1040 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1041 if match:
1042 options.board = match.group(1)
1043 logging.info("--board is set to %s", options.board)
1044 if not options.board:
1045 parser.error("--board is required")
1046 if options.gs_path and options.local_pkg_path:
1047 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1048 if not (options.staging_only or options.device):
1049 parser.error("Need to specify --device")
1050 if options.staging_flags and not options.build_dir:
1051 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001052
Alex Klein1699fab2022-09-08 08:46:06 -06001053 if options.strict:
1054 logging.warning("--strict is deprecated.")
1055 if options.gyp_defines:
1056 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001057
Alex Klein1699fab2022-09-08 08:46:06 -06001058 if options.mount or options.mount_dir:
1059 if not options.target_dir:
1060 options.target_dir = _CHROME_DIR_MOUNT
1061 else:
1062 if not options.target_dir:
1063 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001064
Alex Klein1699fab2022-09-08 08:46:06 -06001065 if options.mount and not options.mount_dir:
1066 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001069
1070
Mike Frysingerc3061a62015-06-04 04:16:18 -04001071def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001072 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001073
Alex Klein1699fab2022-09-08 08:46:06 -06001074 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001075 options: The options object returned by the cli parser.
Alex Klein1699fab2022-09-08 08:46:06 -06001076 """
1077 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1078 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001079
Alex Klein1699fab2022-09-08 08:46:06 -06001080 if not options.gn_args:
1081 gn_env = os.getenv("GN_ARGS")
1082 if gn_env is not None:
1083 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1084 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001085
Alex Klein1699fab2022-09-08 08:46:06 -06001086 if not options.staging_flags:
1087 use_env = os.getenv("USE")
1088 if use_env is not None:
1089 options.staging_flags = " ".join(
1090 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1091 )
1092 logging.info(
1093 "Staging flags taken from USE in environment: %s",
1094 options.staging_flags,
1095 )
Steven Bennetts60600462016-05-12 10:40:20 -07001096
Ryan Cuia56a71e2012-10-18 18:40:35 -07001097
Ryan Cui504db722013-01-22 11:48:01 -08001098def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001099 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001100
Alex Klein1699fab2022-09-08 08:46:06 -06001101 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -06001102 Path to the fetched chrome tarball.
Alex Klein1699fab2022-09-08 08:46:06 -06001103 """
1104 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1105 files = gs_ctx.LS(gs_path)
1106 files = [
1107 found
1108 for found in files
1109 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1110 ]
1111 if not files:
1112 raise Exception("No chrome package found at %s" % gs_path)
1113 elif len(files) > 1:
1114 # - Users should provide us with a direct link to either a stripped or
1115 # unstripped chrome package.
1116 # - In the case of being provided with an archive directory, where both
1117 # stripped and unstripped chrome available, use the stripped chrome
1118 # package.
1119 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
Alex Klein68b270c2023-04-14 14:42:50 -06001120 # - Unstripped chrome pkg is
1121 # chromeos-chrome-<version>-unstripped.tar.gz.
Alex Klein1699fab2022-09-08 08:46:06 -06001122 files = [f for f in files if not "unstripped" in f]
1123 assert len(files) == 1
1124 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001125
Alex Klein1699fab2022-09-08 08:46:06 -06001126 filename = _UrlBaseName(files[0])
1127 logging.info("Fetching %s...", filename)
1128 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1129 chrome_path = os.path.join(tempdir, filename)
1130 assert os.path.exists(chrome_path)
1131 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001132
1133
Ryan Cuif890a3e2013-03-07 18:57:06 -08001134@contextlib.contextmanager
1135def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001136 if not options.dostrip:
1137 yield None
1138 elif options.strip_bin:
1139 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001140 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001141 sdk = cros_chrome_sdk.SDKFetcher(
1142 options.cache_dir,
1143 options.board,
1144 use_external_config=options.use_external_config,
1145 )
1146 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1147 with sdk.Prepare(
1148 components=components,
1149 target_tc=options.target_tc,
1150 toolchain_url=options.toolchain_url,
1151 ) as ctx:
1152 env_path = os.path.join(
1153 ctx.key_map[constants.CHROME_ENV_TAR].path,
1154 constants.CHROME_ENV_FILE,
1155 )
1156 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1157 strip_bin = os.path.join(
1158 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1159 "bin",
1160 os.path.basename(strip_bin),
1161 )
1162 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001163
Alex Klein1699fab2022-09-08 08:46:06 -06001164
1165def _UploadStagingDir(
1166 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1167) -> None:
1168 """Uploads the compressed staging directory.
1169
1170 Args:
Alex Klein68b270c2023-04-14 14:42:50 -06001171 options: options object.
1172 tempdir: Scratch space.
1173 staging_dir: Directory staging chrome files.
Alex Klein1699fab2022-09-08 08:46:06 -06001174 """
1175 staging_tarball_path = os.path.join(
1176 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1177 )
1178 logging.info(
1179 "Compressing staging dir (%s) to (%s)",
1180 staging_dir,
1181 staging_tarball_path,
1182 )
1183 cros_build_lib.CreateTarball(
1184 staging_tarball_path,
1185 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001186 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001187 extra_env={"ZSTD_CLEVEL": "9"},
1188 )
1189 logging.info(
1190 "Uploading staging tarball (%s) into %s",
1191 staging_tarball_path,
1192 options.staging_upload,
1193 )
1194 ctx = gs.GSContext()
1195 ctx.Copy(
1196 staging_tarball_path,
1197 options.staging_upload,
1198 acl="public-read" if options.public_read else "",
1199 )
1200
1201
1202def _PrepareStagingDir(
1203 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1204):
1205 """Place the necessary files in the staging directory.
1206
Alex Klein68b270c2023-04-14 14:42:50 -06001207 The staging directory is the directory used to rsync the build artifacts
1208 over to the device. Only the necessary Chrome build artifacts are put into
1209 the staging directory.
Alex Klein1699fab2022-09-08 08:46:06 -06001210 """
1211 if chrome_dir is None:
1212 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1213 osutils.SafeMakedirs(staging_dir)
1214 os.chmod(staging_dir, 0o755)
1215 if options.build_dir:
1216 with _StripBinContext(options) as strip_bin:
1217 strip_flags = (
1218 None
1219 if options.strip_flags is None
1220 else shlex.split(options.strip_flags)
1221 )
1222 chrome_util.StageChromeFromBuildDir(
1223 staging_dir,
1224 options.build_dir,
1225 strip_bin,
1226 sloppy=options.sloppy,
1227 gn_args=options.gn_args,
1228 staging_flags=options.staging_flags,
1229 strip_flags=strip_flags,
1230 copy_paths=copy_paths,
1231 )
1232 else:
1233 pkg_path = options.local_pkg_path
1234 if options.gs_path:
1235 pkg_path = _FetchChromePackage(
1236 options.cache_dir, tempdir, options.gs_path
1237 )
1238
1239 assert pkg_path
1240 logging.info("Extracting %s...", pkg_path)
Alex Klein68b270c2023-04-14 14:42:50 -06001241 # Extract only the ./opt/google/chrome contents, directly into the
1242 # staging dir, collapsing the directory hierarchy.
Alex Klein1699fab2022-09-08 08:46:06 -06001243 if pkg_path[-4:] == ".zip":
1244 cros_build_lib.dbg_run(
1245 [
1246 "unzip",
1247 "-X",
1248 pkg_path,
1249 _ANDROID_DIR_EXTRACT_PATH,
1250 "-d",
1251 staging_dir,
1252 ]
1253 )
1254 for filename in glob.glob(
1255 os.path.join(staging_dir, "system/chrome/*")
1256 ):
1257 shutil.move(filename, staging_dir)
1258 osutils.RmDir(
1259 os.path.join(staging_dir, "system"), ignore_missing=True
1260 )
1261 else:
1262 compression = cros_build_lib.CompressionDetectType(pkg_path)
1263 compressor = cros_build_lib.FindCompressor(compression)
1264 cros_build_lib.dbg_run(
1265 [
1266 "tar",
1267 "--strip-components",
1268 "4",
1269 "--extract",
1270 "-I",
1271 compressor,
1272 "--preserve-permissions",
1273 "--file",
1274 pkg_path,
1275 ".%s" % chrome_dir,
1276 ],
1277 cwd=staging_dir,
1278 )
1279
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001280 if options.compressed_ash:
1281 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1282 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001283 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1284 # this is in test, cut the known suffix of experimental overlays.
1285 sdk_orig_board = options.board
1286 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1287 sdk_orig_board = sdk_orig_board[
1288 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1289 ]
1290
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001291 sdk = cros_chrome_sdk.SDKFetcher(
1292 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001293 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001294 use_external_config=options.use_external_config,
1295 )
1296 with sdk.Prepare(
1297 components=[],
1298 target_tc=options.target_tc,
1299 toolchain_url=options.toolchain_url,
1300 ):
1301 cros_build_lib.dbg_run(
1302 [
1303 "mksquashfs",
1304 RAW_ASH_FILE,
1305 COMPRESSED_ASH_FILE,
1306 "-all-root",
1307 "-no-progress",
1308 "-comp",
1309 "zstd",
1310 ],
1311 cwd=staging_dir,
1312 )
1313 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1314
Alex Klein1699fab2022-09-08 08:46:06 -06001315 if options.staging_upload:
1316 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001317
Ryan Cui71aa8de2013-04-19 16:12:55 -07001318
Ryan Cui3045c5d2012-07-13 18:00:33 -07001319def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001320 options = _ParseCommandLine(argv)
1321 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001322
Alex Klein1699fab2022-09-08 08:46:06 -06001323 with osutils.TempDir(set_global=True) as tempdir:
1324 staging_dir = options.staging_dir
1325 if not staging_dir:
1326 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001327
Alex Klein1699fab2022-09-08 08:46:06 -06001328 deploy = DeployChrome(options, tempdir, staging_dir)
1329 try:
1330 deploy.Perform()
1331 except failures_lib.StepFailure as ex:
1332 raise SystemExit(str(ex).strip())
1333 deploy.Cleanup()