blob: 2f84a1c29f5406fd08c8dcd87b206a039112a066 [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:
144 options: options object.
145 tempdir: Scratch space for the class. Caller has responsibility to clean
146 it up.
147 staging_dir: Directory to stage the files to.
148 """
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 )
242 # Running in VMs cause make_dev_ssd's firmware confidence checks to fail.
243 # Use --force to bypass the checks.
244 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 Klein1699fab2022-09-08 08:46:06 -0600291 This method calls 'stop ui', and then also manually pkills both ash-chrome
292 and the session manager.
293 """
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:
346 check: See remote.RemoteAccess.RemoteSh for details.
347 """
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
358 # Any valid /opt directory should already exist so avoid the remote call.
359 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
360 return
361 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800362
Alex Klein1699fab2022-09-08 08:46:06 -0600363 def _GetDeviceInfo(self):
364 """Returns the disk space used and available for the target diectory."""
365 steps = [
366 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
367 functools.partial(
368 self._GetRemoteMountFree, self.options.target_dir
369 ),
370 ]
371 return_values = parallel.RunParallelSteps(steps, return_values=True)
372 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700373
Alex Klein1699fab2022-09-08 08:46:06 -0600374 def _CheckDeviceFreeSpace(self, device_info):
375 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700376
Alex Klein1699fab2022-09-08 08:46:06 -0600377 Args:
378 device_info: A DeviceInfo named tuple.
379 """
380 effective_free = (
381 device_info.target_dir_size + device_info.target_fs_free
382 )
383 staging_size = self._GetStagingDirSize()
384 if effective_free < staging_size:
385 raise DeployFailure(
386 "Not enough free space on the device. Required: %s MiB, "
387 "actual: %s MiB."
388 % (staging_size // 1024, effective_free // 1024)
389 )
390 if device_info.target_fs_free < (100 * 1024):
391 logging.warning(
392 "The device has less than 100MB free. deploy_chrome may "
393 "hang during the transfer."
394 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700395
Alex Klein1699fab2022-09-08 08:46:06 -0600396 def _ShouldUseCompression(self):
397 """Checks if compression should be used for rsync."""
398 if self.options.compress == "always":
399 return True
400 elif self.options.compress == "never":
401 return False
402 elif self.options.compress == "auto":
403 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900404
Alex Klein1699fab2022-09-08 08:46:06 -0600405 def _Deploy(self):
406 logging.info(
407 "Copying %s to %s on device...",
408 self._deployment_name,
409 self.options.target_dir,
410 )
411 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
412 # This does not work for deploy.
413 if not self.device.HasRsync():
414 raise DeployFailure(
415 "rsync is not found on the device.\n"
416 "Run dev_install on the device to get rsync installed."
417 )
418 self.device.CopyToDevice(
419 "%s/" % os.path.abspath(self.staging_dir),
420 self.options.target_dir,
421 mode="rsync",
422 inplace=True,
423 compress=self._ShouldUseCompression(),
424 debug_level=logging.INFO,
425 verbose=self.options.verbose,
426 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700427
Alex Klein1699fab2022-09-08 08:46:06 -0600428 # Set the security context on the default Chrome dir if that's where it's
429 # getting deployed, and only on SELinux supported devices.
430 if (
431 not self.options.lacros
432 and self.device.IsSELinuxAvailable()
433 and (
434 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
435 )
436 ):
437 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800438
Alex Klein1699fab2022-09-08 08:46:06 -0600439 for p in self.copy_paths:
440 if p.mode:
441 # Set mode if necessary.
442 self.device.run(
443 "chmod %o %s/%s"
444 % (
445 p.mode,
446 self.options.target_dir,
447 p.src if not p.dest else p.dest,
448 )
449 )
Steve Funge984a532013-11-25 17:09:25 -0800450
Alex Klein1699fab2022-09-08 08:46:06 -0600451 if self.options.lacros:
452 self.device.run(
453 ["chown", "-R", "chronos:chronos", self.options.target_dir]
454 )
Erik Chen75a2f492020-08-06 19:15:11 -0700455
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000456 if self.options.compressed_ash:
457 self.device.run(["start", COMPRESSED_ASH_SERVICE])
458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
460 # pick up major changes (bus type, logging, etc.), but all we care about is
461 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
462 # be authorized to take ownership of its service names.
463 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600464
Erik Chen75a2f492020-08-06 19:15:11 -0700465 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700466 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600467 logging.info("Starting UI...")
468 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800469
Joel Hockey764728e2023-03-14 17:10:04 -0700470 if self.options.unlock_password:
471 logging.info("Unlocking...")
472
473 @retry_util.WithRetry(max_retry=5, sleep=1)
474 def WaitForUnlockScreen():
475 if self._GetLastLogin() == last_login:
476 raise DeployFailure("Unlock screen not shown")
477
478 WaitForUnlockScreen()
Joel Hockeyee4fa302023-03-23 16:44:44 -0700479 time.sleep(POST_UNLOCK_WAIT)
Joel Hockey764728e2023-03-14 17:10:04 -0700480 self.device.run(
481 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
482 )
483
484 def _GetLastLogin(self):
485 """Returns last login time"""
486 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
487
Alex Klein1699fab2022-09-08 08:46:06 -0600488 def _DeployTestBinaries(self):
489 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 There could be several binaries located in the local build dir, so compare
492 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
493 over any that we also built ourselves.
494 """
495 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
496 if r.returncode != 0:
497 raise DeployFailure(
498 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
499 )
500 binaries_to_copy = []
501 for f in r.stdout.splitlines():
502 binaries_to_copy.append(
503 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
504 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700505
Alex Klein1699fab2022-09-08 08:46:06 -0600506 staging_dir = os.path.join(
507 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
508 )
509 _PrepareStagingDir(
510 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
511 )
512 # Deploying can occasionally run into issues with rsync getting a broken
513 # pipe, so retry several times. See crbug.com/1141618 for more
514 # information.
515 retry_util.RetryException(
516 None,
517 3,
518 self.device.CopyToDevice,
519 staging_dir,
520 os.path.dirname(_CHROME_TEST_BIN_DIR),
521 mode="rsync",
522 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700523
Alex Klein1699fab2022-09-08 08:46:06 -0600524 def _CheckConnection(self):
525 try:
526 logging.info("Testing connection to the device...")
527 self.device.run("true")
528 except cros_build_lib.RunCommandError as ex:
529 logging.error("Error connecting to the test device.")
530 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800531
Alex Klein1699fab2022-09-08 08:46:06 -0600532 def _CheckBoard(self):
533 """Check that the Chrome build is targeted for the device board."""
534 if self.options.board == self.device.board:
535 return
536 logging.warning(
537 "Device board is %s whereas target board is %s.",
538 self.device.board,
539 self.options.board,
540 )
541 if self.options.force:
542 return
543 if not cros_build_lib.BooleanPrompt(
544 "Continue despite board mismatch?", False
545 ):
546 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800547
Alex Klein1699fab2022-09-08 08:46:06 -0600548 def _CheckDeployType(self):
549 if self.options.build_dir:
550
551 def BinaryExists(filename):
552 """Checks if the passed-in file is present in the build directory."""
553 return os.path.exists(
554 os.path.join(self.options.build_dir, filename)
555 )
556
557 # In the future, lacros-chrome and ash-chrome will likely be named
558 # something other than 'chrome' to avoid confusion.
559 # Handle non-Chrome deployments.
560 if not BinaryExists("chrome"):
561 if BinaryExists("app_shell"):
562 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
563
564 def _PrepareStagingDir(self):
565 _PrepareStagingDir(
566 self.options,
567 self.tempdir,
568 self.staging_dir,
569 self.copy_paths,
570 self.chrome_dir,
571 )
572
573 def _MountTarget(self):
574 logging.info("Mounting Chrome...")
575
576 # Create directory if does not exist.
577 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
578 try:
579 # Umount the existing mount on mount_dir if present first.
580 self.device.run(
581 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
582 )
583 except cros_build_lib.RunCommandError as e:
584 logging.error("Failed to umount %s", self.options.mount_dir)
585 # If there is a failure, check if some processs is using the mount_dir.
586 result = self.device.run(
587 LSOF_COMMAND % (self.options.mount_dir,),
588 check=False,
589 capture_output=True,
590 encoding="utf-8",
591 )
592 logging.error("lsof %s -->", self.options.mount_dir)
593 logging.error(result.stdout)
594 raise e
595
596 self.device.run(
597 _BIND_TO_FINAL_DIR_CMD
598 % (self.options.target_dir, self.options.mount_dir)
599 )
600
601 # Chrome needs partition to have exec and suid flags set
602 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
603
604 def Cleanup(self):
605 """Clean up RemoteDevice."""
606 if not self.options.staging_only:
607 self.device.Cleanup()
608
609 def Perform(self):
610 self._CheckDeployType()
611
612 # If requested, just do the staging step.
613 if self.options.staging_only:
614 self._PrepareStagingDir()
615 return 0
616
617 # Check that the build matches the device. Lacros-chrome skips this check as
618 # it's currently board independent. This means that it's possible to deploy
619 # a build of lacros-chrome with a mismatched architecture. We don't try to
620 # prevent this developer foot-gun.
621 if not self.options.lacros:
622 self._CheckBoard()
623
624 # Ensure that the target directory exists before running parallel steps.
625 self._EnsureTargetDir()
626
627 logging.info("Preparing device")
628 steps = [
629 self._GetDeviceInfo,
630 self._CheckConnection,
631 self._MountRootfsAsWritable,
632 self._PrepareStagingDir,
633 ]
634
635 restart_ui = True
636 if self.options.lacros:
637 # If this is a lacros build, we only want to restart ash-chrome if needed.
638 restart_ui = False
639 steps.append(self._KillLacrosChrome)
640 if self.options.reset_lacros:
641 steps.append(self._ResetLacrosChrome)
642 if self.options.modify_config_file:
643 restart_ui = self._ModifyConfigFileIfNeededForLacros()
644
645 if restart_ui:
646 steps.append(self._KillAshChromeIfNeeded)
647 self._stopped_ui = True
648
649 ret = parallel.RunParallelSteps(
650 steps, halt_on_error=True, return_values=True
651 )
652 self._CheckDeviceFreeSpace(ret[0])
653
654 # If the root dir is not writable, try disabling rootfs verification.
655 # (We always do this by default so that developers can write to
656 # /etc/chriome_dev.conf and other directories in the rootfs).
657 if self._root_dir_is_still_readonly.is_set():
658 if self.options.noremove_rootfs_verification:
659 logging.warning("Skipping disable rootfs verification.")
660 elif not self._DisableRootfsVerification():
661 logging.warning("Failed to disable rootfs verification.")
662
663 # If the target dir is still not writable (i.e. the user opted out or the
664 # command failed), abort.
665 if not self.device.IsDirWritable(self.options.target_dir):
666 if self.options.startui and self._stopped_ui:
667 logging.info("Restarting Chrome...")
668 self.device.run("start ui")
669 raise DeployFailure(
670 "Target location is not writable. Aborting."
671 )
672
673 if self.options.mount_dir is not None:
674 self._MountTarget()
675
676 # Actually deploy Chrome to the device.
677 self._Deploy()
678 if self.options.deploy_test_binaries:
679 self._DeployTestBinaries()
680
681 def _ModifyConfigFileIfNeededForLacros(self):
682 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
683
684 Returns:
685 True if the file is modified, and the return value is usually used to
686 determine whether restarting ash-chrome is needed.
687 """
688 assert (
689 self.options.lacros
690 ), "Only deploying lacros-chrome needs to modify the config file."
691 # Update /etc/chrome_dev.conf to include appropriate flags.
692 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000693 if self.options.enable_lacros_support:
694 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
695 if result.stdout.strip() == MODIFIED_CONF_FILE:
696 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600697 result = self.device.run(
698 _SET_LACROS_PATH_VIA_CONF_COMMAND
699 % {
700 "conf_file": _CONF_FILE,
701 "lacros_path": self.options.target_dir,
702 "modified_conf_file": MODIFIED_CONF_FILE,
703 },
704 shell=True,
705 )
706 if result.stdout.strip() == MODIFIED_CONF_FILE:
707 modified = True
708
709 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800710
711
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700712def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600713 """Convert formatted string to dictionary."""
714 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700715
716
Steven Bennetts368c3e52016-09-23 13:05:21 -0700717def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600718 """Convert GN_ARGS-formatted string to dictionary."""
719 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700720
721
Ryan Cuie535b172012-10-19 18:25:03 -0700722def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600723 """Create our custom parser."""
724 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700725
Alex Klein1699fab2022-09-08 08:46:06 -0600726 # TODO(rcui): Have this use the UI-V2 format of having source and target
727 # device be specified as positional arguments.
728 parser.add_argument(
729 "--force",
730 action="store_true",
731 default=False,
732 help="Skip all prompts (such as the prompt for disabling "
733 "of rootfs verification). This may result in the "
734 "target machine being rebooted.",
735 )
736 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
737 parser.add_argument(
738 "--board",
739 default=sdk_board_env,
740 help="The board the Chrome build is targeted for. When "
741 "in a 'cros chrome-sdk' shell, defaults to the SDK "
742 "board.",
743 )
744 parser.add_argument(
745 "--build-dir",
746 type="path",
747 help="The directory with Chrome build artifacts to "
748 "deploy from. Typically of format "
749 "<chrome_root>/out/Debug. When this option is used, "
750 "the GN_ARGS environment variable must be set.",
751 )
752 parser.add_argument(
753 "--target-dir",
754 type="path",
755 default=None,
756 help="Target directory on device to deploy Chrome into.",
757 )
758 parser.add_argument(
759 "-g",
760 "--gs-path",
761 type="gs_path",
762 help="GS path that contains the chrome to deploy.",
763 )
764 parser.add_argument(
765 "--private-key",
766 type="path",
767 default=None,
768 help="An ssh private key to use when deploying to " "a CrOS device.",
769 )
770 parser.add_argument(
771 "--nostartui",
772 action="store_false",
773 dest="startui",
774 default=True,
775 help="Don't restart the ui daemon after deployment.",
776 )
777 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700778 "--unlock-password",
779 default=None,
780 help="Password to use to unlock after deployment and restart.",
781 )
782 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600783 "--nostrip",
784 action="store_false",
785 dest="dostrip",
786 default=True,
787 help="Don't strip binaries during deployment. Warning: "
788 "the resulting binaries will be very large!",
789 )
790 parser.add_argument(
791 "-d",
792 "--device",
793 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
794 help="Device hostname or IP in the format hostname[:port].",
795 )
796 parser.add_argument(
797 "--mount-dir",
798 type="path",
799 default=None,
800 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000801 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600802 "Any existing mount on this directory will be "
803 "umounted first.",
804 )
805 parser.add_argument(
806 "--mount",
807 action="store_true",
808 default=False,
809 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000810 "it to the default mount directory. "
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 "--noremove-rootfs-verification",
816 action="store_true",
817 default=False,
818 help="Never remove rootfs verification.",
819 )
820 parser.add_argument(
821 "--deploy-test-binaries",
822 action="store_true",
823 default=False,
824 help="Also deploy any test binaries to %s. Useful for "
825 "running any Tast tests that execute these "
826 "binaries." % _CHROME_TEST_BIN_DIR,
827 )
828 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600829 "--use-external-config",
830 action="store_true",
831 help="When identifying the configuration for a board, "
832 "force usage of the external configuration if both "
833 "internal and external are available. This only "
834 "has an effect when stripping Chrome, i.e. when "
835 "--nostrip is not passed in.",
836 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700837
Georg Neis9b1ff192022-09-14 08:07:22 +0000838 group = parser.add_argument_group("Lacros Options")
839 group.add_argument(
840 "--lacros",
841 action="store_true",
842 default=False,
843 help="Deploys lacros-chrome rather than ash-chrome.",
844 )
845 group.add_argument(
846 "--reset-lacros",
847 action="store_true",
848 default=False,
849 help="Reset Lacros by deleting Lacros user data dir if it exists.",
850 )
851 group.add_argument(
852 "--skip-enabling-lacros-support",
853 action="store_false",
854 dest="enable_lacros_support",
855 help="By default, deploying lacros-chrome modifies the "
856 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
857 "and (2) set the Lacros path, which can interfere with automated "
858 "testing. With this flag, part (1) will be skipped. See the "
859 "--skip-modifying-config-file flag for skipping both parts.",
860 )
861 group.add_argument(
862 "--skip-modifying-config-file",
863 action="store_false",
864 dest="modify_config_file",
865 help="When deploying lacros-chrome, do not modify the "
866 "/etc/chrome_dev.conf file. See also the "
867 "--skip-enabling-lacros-support flag.",
868 )
869
Alex Klein1699fab2022-09-08 08:46:06 -0600870 group = parser.add_argument_group("Advanced Options")
871 group.add_argument(
872 "-l",
873 "--local-pkg-path",
874 type="path",
875 help="Path to local chrome prebuilt package to deploy.",
876 )
877 group.add_argument(
878 "--sloppy",
879 action="store_true",
880 default=False,
881 help="Ignore when mandatory artifacts are missing.",
882 )
883 group.add_argument(
884 "--staging-flags",
885 default=None,
886 type=ValidateStagingFlags,
887 help=(
888 "Extra flags to control staging. Valid flags are - "
889 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
890 ),
891 )
892 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
893 group.add_argument(
894 "--strict",
895 action="store_true",
896 default=False,
897 help='Deprecated. Default behavior is "strict". Use '
898 "--sloppy to omit warnings for missing optional "
899 "files.",
900 )
901 group.add_argument(
902 "--strip-flags",
903 default=None,
904 help="Flags to call the 'strip' binutil tool with. "
905 "Overrides the default arguments.",
906 )
907 group.add_argument(
908 "--ping",
909 action="store_true",
910 default=False,
911 help="Ping the device before connection attempt.",
912 )
913 group.add_argument(
914 "--process-timeout",
915 type=int,
916 default=KILL_PROC_MAX_WAIT,
917 help="Timeout for process shutdown.",
918 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700919
Alex Klein1699fab2022-09-08 08:46:06 -0600920 group = parser.add_argument_group(
921 "Metadata Overrides (Advanced)",
922 description="Provide all of these overrides in order to remove "
923 "dependencies on metadata.json existence.",
924 )
925 group.add_argument(
926 "--target-tc",
927 action="store",
928 default=None,
929 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
930 )
931 group.add_argument(
932 "--toolchain-url",
933 action="store",
934 default=None,
935 help="Override toolchain url format pattern, e.g. "
936 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
937 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700938
Alex Klein1699fab2022-09-08 08:46:06 -0600939 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
940 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
941 parser.add_argument(
942 "--gyp-defines",
943 default=None,
944 type=ValidateStagingFlags,
945 help=argparse.SUPPRESS,
946 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700947
Alex Klein1699fab2022-09-08 08:46:06 -0600948 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
949 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
950 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
951 parser.add_argument(
952 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
953 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700954
Alex Klein1699fab2022-09-08 08:46:06 -0600955 # Path of an empty directory to stage chrome artifacts to. Defaults to a
956 # temporary directory that is removed when the script finishes. If the path
957 # is specified, then it will not be removed.
958 parser.add_argument(
959 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
960 )
961 # Only prepare the staging directory, and skip deploying to the device.
962 parser.add_argument(
963 "--staging-only",
964 action="store_true",
965 default=False,
966 help=argparse.SUPPRESS,
967 )
968 # Uploads the compressed staging directory to the given gs:// path URI.
969 parser.add_argument(
970 "--staging-upload",
971 type="gs_path",
972 help="GS path to upload the compressed staging files to.",
973 )
974 # Used alongside --staging-upload to upload with public-read ACL.
975 parser.add_argument(
976 "--public-read",
977 action="store_true",
978 default=False,
979 help="GS path to upload the compressed staging files to.",
980 )
981 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
982 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
983 # fetching the SDK toolchain.
984 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
985 parser.add_argument(
986 "--compress",
987 action="store",
988 default="auto",
989 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000990 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -0600991 'is set to "auto", that disables compression if '
992 "the target device has a gigabit ethernet port.",
993 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000994 parser.add_argument(
995 "--compressed-ash",
996 action="store_true",
997 default=False,
998 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
999 "binary is stored on DUT in squashfs, mounted upon boot.",
1000 )
Alex Klein1699fab2022-09-08 08:46:06 -06001001 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001002
Ryan Cuie535b172012-10-19 18:25:03 -07001003
1004def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001005 """Parse args, and run environment-independent checks."""
1006 parser = _CreateParser()
1007 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001008
Alex Klein1699fab2022-09-08 08:46:06 -06001009 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1010 parser.error(
1011 "Need to specify either --gs-path, --local-pkg-path, or "
1012 "--build-dir"
1013 )
1014 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1015 parser.error(
1016 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1017 )
1018 if options.lacros:
1019 if options.dostrip and not options.board:
1020 parser.error("Please specify --board.")
1021 if options.mount_dir or options.mount:
1022 parser.error("--lacros does not support --mount or --mount-dir")
1023 if options.deploy_test_binaries:
1024 parser.error("--lacros does not support --deploy-test-binaries")
1025 if options.local_pkg_path:
1026 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001027 if options.compressed_ash:
1028 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001029 else:
1030 if not options.board and options.build_dir:
1031 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1032 if match:
1033 options.board = match.group(1)
1034 logging.info("--board is set to %s", options.board)
1035 if not options.board:
1036 parser.error("--board is required")
1037 if options.gs_path and options.local_pkg_path:
1038 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1039 if not (options.staging_only or options.device):
1040 parser.error("Need to specify --device")
1041 if options.staging_flags and not options.build_dir:
1042 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001043
Alex Klein1699fab2022-09-08 08:46:06 -06001044 if options.strict:
1045 logging.warning("--strict is deprecated.")
1046 if options.gyp_defines:
1047 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001048
Alex Klein1699fab2022-09-08 08:46:06 -06001049 if options.mount or options.mount_dir:
1050 if not options.target_dir:
1051 options.target_dir = _CHROME_DIR_MOUNT
1052 else:
1053 if not options.target_dir:
1054 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001055
Alex Klein1699fab2022-09-08 08:46:06 -06001056 if options.mount and not options.mount_dir:
1057 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001058
Alex Klein1699fab2022-09-08 08:46:06 -06001059 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001060
1061
Mike Frysingerc3061a62015-06-04 04:16:18 -04001062def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001063 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001064
Alex Klein1699fab2022-09-08 08:46:06 -06001065 Args:
1066 options: The options object returned by the cli parser.
1067 """
1068 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1069 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001070
Alex Klein1699fab2022-09-08 08:46:06 -06001071 if not options.gn_args:
1072 gn_env = os.getenv("GN_ARGS")
1073 if gn_env is not None:
1074 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1075 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001076
Alex Klein1699fab2022-09-08 08:46:06 -06001077 if not options.staging_flags:
1078 use_env = os.getenv("USE")
1079 if use_env is not None:
1080 options.staging_flags = " ".join(
1081 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1082 )
1083 logging.info(
1084 "Staging flags taken from USE in environment: %s",
1085 options.staging_flags,
1086 )
Steven Bennetts60600462016-05-12 10:40:20 -07001087
Ryan Cuia56a71e2012-10-18 18:40:35 -07001088
Ryan Cui504db722013-01-22 11:48:01 -08001089def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001090 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001091
Alex Klein1699fab2022-09-08 08:46:06 -06001092 Returns:
1093 Path to the fetched chrome tarball.
1094 """
1095 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1096 files = gs_ctx.LS(gs_path)
1097 files = [
1098 found
1099 for found in files
1100 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1101 ]
1102 if not files:
1103 raise Exception("No chrome package found at %s" % gs_path)
1104 elif len(files) > 1:
1105 # - Users should provide us with a direct link to either a stripped or
1106 # unstripped chrome package.
1107 # - In the case of being provided with an archive directory, where both
1108 # stripped and unstripped chrome available, use the stripped chrome
1109 # package.
1110 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
1111 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
1112 files = [f for f in files if not "unstripped" in f]
1113 assert len(files) == 1
1114 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001115
Alex Klein1699fab2022-09-08 08:46:06 -06001116 filename = _UrlBaseName(files[0])
1117 logging.info("Fetching %s...", filename)
1118 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1119 chrome_path = os.path.join(tempdir, filename)
1120 assert os.path.exists(chrome_path)
1121 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001122
1123
Ryan Cuif890a3e2013-03-07 18:57:06 -08001124@contextlib.contextmanager
1125def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001126 if not options.dostrip:
1127 yield None
1128 elif options.strip_bin:
1129 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001130 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001131 sdk = cros_chrome_sdk.SDKFetcher(
1132 options.cache_dir,
1133 options.board,
1134 use_external_config=options.use_external_config,
1135 )
1136 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1137 with sdk.Prepare(
1138 components=components,
1139 target_tc=options.target_tc,
1140 toolchain_url=options.toolchain_url,
1141 ) as ctx:
1142 env_path = os.path.join(
1143 ctx.key_map[constants.CHROME_ENV_TAR].path,
1144 constants.CHROME_ENV_FILE,
1145 )
1146 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1147 strip_bin = os.path.join(
1148 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1149 "bin",
1150 os.path.basename(strip_bin),
1151 )
1152 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001153
Alex Klein1699fab2022-09-08 08:46:06 -06001154
1155def _UploadStagingDir(
1156 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1157) -> None:
1158 """Uploads the compressed staging directory.
1159
1160 Args:
1161 options: options object.
1162 tempdir: Scratch space.
1163 staging_dir: Directory staging chrome files.
1164 """
1165 staging_tarball_path = os.path.join(
1166 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1167 )
1168 logging.info(
1169 "Compressing staging dir (%s) to (%s)",
1170 staging_dir,
1171 staging_tarball_path,
1172 )
1173 cros_build_lib.CreateTarball(
1174 staging_tarball_path,
1175 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001176 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001177 extra_env={"ZSTD_CLEVEL": "9"},
1178 )
1179 logging.info(
1180 "Uploading staging tarball (%s) into %s",
1181 staging_tarball_path,
1182 options.staging_upload,
1183 )
1184 ctx = gs.GSContext()
1185 ctx.Copy(
1186 staging_tarball_path,
1187 options.staging_upload,
1188 acl="public-read" if options.public_read else "",
1189 )
1190
1191
1192def _PrepareStagingDir(
1193 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1194):
1195 """Place the necessary files in the staging directory.
1196
1197 The staging directory is the directory used to rsync the build artifacts over
1198 to the device. Only the necessary Chrome build artifacts are put into the
1199 staging directory.
1200 """
1201 if chrome_dir is None:
1202 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1203 osutils.SafeMakedirs(staging_dir)
1204 os.chmod(staging_dir, 0o755)
1205 if options.build_dir:
1206 with _StripBinContext(options) as strip_bin:
1207 strip_flags = (
1208 None
1209 if options.strip_flags is None
1210 else shlex.split(options.strip_flags)
1211 )
1212 chrome_util.StageChromeFromBuildDir(
1213 staging_dir,
1214 options.build_dir,
1215 strip_bin,
1216 sloppy=options.sloppy,
1217 gn_args=options.gn_args,
1218 staging_flags=options.staging_flags,
1219 strip_flags=strip_flags,
1220 copy_paths=copy_paths,
1221 )
1222 else:
1223 pkg_path = options.local_pkg_path
1224 if options.gs_path:
1225 pkg_path = _FetchChromePackage(
1226 options.cache_dir, tempdir, options.gs_path
1227 )
1228
1229 assert pkg_path
1230 logging.info("Extracting %s...", pkg_path)
1231 # Extract only the ./opt/google/chrome contents, directly into the staging
1232 # dir, collapsing the directory hierarchy.
1233 if pkg_path[-4:] == ".zip":
1234 cros_build_lib.dbg_run(
1235 [
1236 "unzip",
1237 "-X",
1238 pkg_path,
1239 _ANDROID_DIR_EXTRACT_PATH,
1240 "-d",
1241 staging_dir,
1242 ]
1243 )
1244 for filename in glob.glob(
1245 os.path.join(staging_dir, "system/chrome/*")
1246 ):
1247 shutil.move(filename, staging_dir)
1248 osutils.RmDir(
1249 os.path.join(staging_dir, "system"), ignore_missing=True
1250 )
1251 else:
1252 compression = cros_build_lib.CompressionDetectType(pkg_path)
1253 compressor = cros_build_lib.FindCompressor(compression)
1254 cros_build_lib.dbg_run(
1255 [
1256 "tar",
1257 "--strip-components",
1258 "4",
1259 "--extract",
1260 "-I",
1261 compressor,
1262 "--preserve-permissions",
1263 "--file",
1264 pkg_path,
1265 ".%s" % chrome_dir,
1266 ],
1267 cwd=staging_dir,
1268 )
1269
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001270 if options.compressed_ash:
1271 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1272 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001273 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1274 # this is in test, cut the known suffix of experimental overlays.
1275 sdk_orig_board = options.board
1276 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1277 sdk_orig_board = sdk_orig_board[
1278 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1279 ]
1280
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001281 sdk = cros_chrome_sdk.SDKFetcher(
1282 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001283 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001284 use_external_config=options.use_external_config,
1285 )
1286 with sdk.Prepare(
1287 components=[],
1288 target_tc=options.target_tc,
1289 toolchain_url=options.toolchain_url,
1290 ):
1291 cros_build_lib.dbg_run(
1292 [
1293 "mksquashfs",
1294 RAW_ASH_FILE,
1295 COMPRESSED_ASH_FILE,
1296 "-all-root",
1297 "-no-progress",
1298 "-comp",
1299 "zstd",
1300 ],
1301 cwd=staging_dir,
1302 )
1303 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1304
Alex Klein1699fab2022-09-08 08:46:06 -06001305 if options.staging_upload:
1306 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001307
Ryan Cui71aa8de2013-04-19 16:12:55 -07001308
Ryan Cui3045c5d2012-07-13 18:00:33 -07001309def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001310 options = _ParseCommandLine(argv)
1311 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001312
Alex Klein1699fab2022-09-08 08:46:06 -06001313 with osutils.TempDir(set_global=True) as tempdir:
1314 staging_dir = options.staging_dir
1315 if not staging_dir:
1316 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001317
Alex Klein1699fab2022-09-08 08:46:06 -06001318 deploy = DeployChrome(options, tempdir, staging_dir)
1319 try:
1320 deploy.Perform()
1321 except failures_lib.StepFailure as ex:
1322 raise SystemExit(str(ex).strip())
1323 deploy.Cleanup()