blob: f84aa58ca60dd74bfc9e944e7d0db4390ca86136 [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
52
Alex Klein1699fab2022-09-08 08:46:06 -060053MOUNT_RW_COMMAND = "mount -o remount,rw /"
54LSOF_COMMAND_CHROME = "lsof %s/chrome"
55LSOF_COMMAND = "lsof %s"
56DBUS_RELOAD_COMMAND = "killall -HUP dbus-daemon"
Ryan Cui3045c5d2012-07-13 18:00:33 -070057
Alex Klein1699fab2022-09-08 08:46:06 -060058_ANDROID_DIR = "/system/chrome"
59_ANDROID_DIR_EXTRACT_PATH = "system/chrome/*"
Steve Funge984a532013-11-25 17:09:25 -080060
Alex Klein1699fab2022-09-08 08:46:06 -060061_CHROME_DIR = "/opt/google/chrome"
62_CHROME_DIR_MOUNT = "/mnt/stateful_partition/deploy_rootfs/opt/google/chrome"
63_CHROME_DIR_STAGING_TARBALL_ZSTD = "chrome.tar.zst"
64_CHROME_TEST_BIN_DIR = "/usr/local/libexec/chrome-binary-tests"
David James2cb34002013-03-01 18:42:40 -080065
David Haddock3151d912017-10-24 03:50:32 +000066_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
Alex Klein1699fab2022-09-08 08:46:06 -060067 "if mountpoint -q %(dir)s; then umount %(dir)s; fi"
68)
69_BIND_TO_FINAL_DIR_CMD = "mount --rbind %s %s"
70_SET_MOUNT_FLAGS_CMD = "mount -o remount,exec,suid %s"
71_MKDIR_P_CMD = "mkdir -p --mode 0775 %s"
72_FIND_TEST_BIN_CMD = "find %s -maxdepth 1 -executable -type f" % (
73 _CHROME_TEST_BIN_DIR
74)
David Haddock3151d912017-10-24 03:50:32 +000075
Alex Klein1699fab2022-09-08 08:46:06 -060076DF_COMMAND = "df -k %s"
David Haddock3151d912017-10-24 03:50:32 +000077
Daniil Lunev0c4f65c2022-09-19 11:01:34 +100078# This constants are related to an experiment of running compressed ash chrome
79# to save rootfs space. See b/247397013
80COMPRESSED_ASH_SERVICE = "mount-ash-chrome"
81COMPRESSED_ASH_FILE = "chrome.squashfs"
82RAW_ASH_FILE = "chrome"
83COMPRESSED_ASH_PATH = os.path.join(_CHROME_DIR, COMPRESSED_ASH_FILE)
84RAW_ASH_PATH = os.path.join(_CHROME_DIR, RAW_ASH_FILE)
Daniil Luneve2954832022-10-11 11:38:51 +110085COMPRESSED_ASH_OVERLAY_SUFFIX = "-compressed-ash"
Daniil Lunev0c4f65c2022-09-19 11:01:34 +100086
Alex Klein1699fab2022-09-08 08:46:06 -060087LACROS_DIR = "/usr/local/lacros-chrome"
88_CONF_FILE = "/etc/chrome_dev.conf"
89_KILL_LACROS_CHROME_CMD = "pkill -f %(lacros_dir)s/chrome"
90_RESET_LACROS_CHROME_CMD = "rm -rf /home/chronos/user/lacros"
91MODIFIED_CONF_FILE = f"modified {_CONF_FILE}"
Erik Chen75a2f492020-08-06 19:15:11 -070092
93# This command checks if "--enable-features=LacrosSupport" is present in
94# /etc/chrome_dev.conf. If it is not, then it is added.
95# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
96# to modify chrome_dev.conf. Either revisit this policy or find another
97# mechanism to pass configuration to ash-chrome.
98ENABLE_LACROS_VIA_CONF_COMMAND = f"""
99 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
100 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
101 echo {MODIFIED_CONF_FILE};
102 fi
103"""
104
105# This command checks if "--lacros-chrome-path=" is present with the right value
106# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
107# and the new one is added.
108# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
109# to modify chrome_dev.conf. Either revisit this policy or find another
110# mechanism to pass configuration to ash-chrome.
111_SET_LACROS_PATH_VIA_CONF_COMMAND = """
112 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
113 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
114 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
115 echo %(modified_conf_file)s;
116 fi
117"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500118
Alex Klein1699fab2022-09-08 08:46:06 -0600119
Ryan Cui3045c5d2012-07-13 18:00:33 -0700120def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600121 """Return the last component of the URL."""
122 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700123
124
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700125class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600126 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800127
128
Ryan Cui7193a7e2013-04-26 14:15:19 -0700129DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600130 "DeviceInfo", ["target_dir_size", "target_fs_free"]
131)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700132
133
Ryan Cui3045c5d2012-07-13 18:00:33 -0700134class DeployChrome(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600135 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500136
Alex Klein1699fab2022-09-08 08:46:06 -0600137 def __init__(self, options, tempdir, staging_dir):
138 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 Args:
141 options: options object.
142 tempdir: Scratch space for the class. Caller has responsibility to clean
143 it up.
144 staging_dir: Directory to stage the files to.
145 """
146 self.tempdir = tempdir
147 self.options = options
148 self.staging_dir = staging_dir
149 if not self.options.staging_only:
150 hostname = options.device.hostname
151 port = options.device.port
152 self.device = remote.ChromiumOSDevice(
153 hostname,
154 port=port,
155 ping=options.ping,
156 private_key=options.private_key,
157 include_dev_paths=False,
158 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000159 if self._ShouldUseCompressedAsh():
160 self.options.compressed_ash = True
161
Alex Klein1699fab2022-09-08 08:46:06 -0600162 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000163
Alex Klein1699fab2022-09-08 08:46:06 -0600164 self._deployment_name = "lacros" if options.lacros else "chrome"
165 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700166
Alex Klein1699fab2022-09-08 08:46:06 -0600167 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700168
Alex Klein1699fab2022-09-08 08:46:06 -0600169 # Whether UI was stopped during setup.
170 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800171
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000172 def _ShouldUseCompressedAsh(self):
173 """Detects if the DUT uses compressed-ash setup."""
174 if self.options.lacros:
175 return False
176
177 return self.device.IfFileExists(COMPRESSED_ASH_PATH)
178
Alex Klein1699fab2022-09-08 08:46:06 -0600179 def _GetRemoteMountFree(self, remote_dir):
180 result = self.device.run(DF_COMMAND % remote_dir)
181 line = result.stdout.splitlines()[1]
182 value = line.split()[3]
183 multipliers = {
184 "G": 1024 * 1024 * 1024,
185 "M": 1024 * 1024,
186 "K": 1024,
187 }
188 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 def _GetRemoteDirSize(self, remote_dir):
191 result = self.device.run(
192 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
193 )
194 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 def _GetStagingDirSize(self):
197 result = cros_build_lib.dbg_run(
198 ["du", "-ks", self.staging_dir],
199 capture_output=True,
200 encoding="utf-8",
201 )
202 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700203
Alex Klein1699fab2022-09-08 08:46:06 -0600204 def _ChromeFileInUse(self):
205 result = self.device.run(
206 LSOF_COMMAND_CHROME % (self.options.target_dir,),
207 check=False,
208 capture_output=True,
209 )
210 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700211
Alex Klein1699fab2022-09-08 08:46:06 -0600212 def _Reboot(self):
213 # A reboot in developer mode takes a while (and has delays), so the user
214 # will have time to read and act on the USB boot instructions below.
215 logging.info(
216 "Please remember to press Ctrl-U if you are booting from USB."
217 )
218 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600219
Alex Klein1699fab2022-09-08 08:46:06 -0600220 def _DisableRootfsVerification(self):
221 if not self.options.force:
222 logging.error(
223 "Detected that the device has rootfs verification enabled."
224 )
225 logging.info(
226 "This script can automatically remove the rootfs "
227 "verification, which requires it to reboot the device."
228 )
229 logging.info("Make sure the device is in developer mode!")
230 logging.info("Skip this prompt by specifying --force.")
231 if not cros_build_lib.BooleanPrompt(
232 "Remove rootfs verification?", False
233 ):
234 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700235
Alex Klein1699fab2022-09-08 08:46:06 -0600236 logging.info(
237 "Removing rootfs verification from %s", self.options.device
238 )
239 # Running in VMs cause make_dev_ssd's firmware confidence checks to fail.
240 # Use --force to bypass the checks.
241 cmd = (
242 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
243 "--remove_rootfs_verification --force"
244 )
245 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
246 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700249
Alex Klein1699fab2022-09-08 08:46:06 -0600250 # Now that the machine has been rebooted, we need to kill Chrome again.
251 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 # Make sure the rootfs is writable now.
254 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700257
Alex Klein1699fab2022-09-08 08:46:06 -0600258 def _CheckUiJobStarted(self):
259 # status output is in the format:
260 # <job_name> <status> ['process' <pid>].
261 # <status> is in the format <goal>/<state>.
262 try:
263 result = self.device.run(
264 "status ui", capture_output=True, encoding="utf-8"
265 )
266 except cros_build_lib.RunCommandError as e:
267 if "Unknown job" in e.stderr:
268 return False
269 else:
270 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800271
Alex Klein1699fab2022-09-08 08:46:06 -0600272 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 def _KillLacrosChrome(self):
275 """This method kills lacros-chrome on the device, if it's running."""
276 self.device.run(
277 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
278 check=False,
279 )
Erik Chen75a2f492020-08-06 19:15:11 -0700280
Alex Klein1699fab2022-09-08 08:46:06 -0600281 def _ResetLacrosChrome(self):
282 """Reset Lacros to fresh state by deleting user data dir."""
283 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000284
Alex Klein1699fab2022-09-08 08:46:06 -0600285 def _KillAshChromeIfNeeded(self):
286 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 This method calls 'stop ui', and then also manually pkills both ash-chrome
289 and the session manager.
290 """
291 if self._CheckUiJobStarted():
292 logging.info("Shutting down Chrome...")
293 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 # Developers sometimes run session_manager manually, in which case we'll
296 # need to help shut the chrome processes down.
297 try:
298 with timeout_util.Timeout(self.options.process_timeout):
299 while self._ChromeFileInUse():
300 logging.warning(
301 "The chrome binary on the device is in use."
302 )
303 logging.warning(
304 "Killing chrome and session_manager processes...\n"
305 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 self.device.run(
308 "pkill 'chrome|session_manager'", check=False
309 )
310 # Wait for processes to actually terminate
311 time.sleep(POST_KILL_WAIT)
312 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000313 if self.options.compressed_ash:
314 result = self.device.run(
315 ["umount", RAW_ASH_PATH],
316 check=False,
317 capture_output=True,
318 )
319 if result.returncode and not (
320 result.returncode == 32
321 and "not mounted" in result.stderr
322 ):
323 raise DeployFailure(
324 "Could not unmount compressed ash. "
325 f"Error Code: {result.returncode}, "
326 f"Error Message: {result.stderr}"
327 )
Alex Klein1699fab2022-09-08 08:46:06 -0600328 except timeout_util.TimeoutError:
329 msg = (
330 "Could not kill processes after %s seconds. Please exit any "
331 "running chrome processes and try again."
332 % self.options.process_timeout
333 )
334 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700335
Alex Klein1699fab2022-09-08 08:46:06 -0600336 def _MountRootfsAsWritable(self, check=False):
337 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 If the command fails and the root dir is not writable then this function
340 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700341
Alex Klein1699fab2022-09-08 08:46:06 -0600342 Args:
343 check: See remote.RemoteAccess.RemoteSh for details.
344 """
345 # TODO: Should migrate to use the remount functions in remote_access.
346 result = self.device.run(
347 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
348 )
349 if result.returncode and not self.device.IsDirWritable("/"):
350 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700351
Alex Klein1699fab2022-09-08 08:46:06 -0600352 def _EnsureTargetDir(self):
353 """Ensures that the target directory exists on the remote device."""
354 target_dir = self.options.target_dir
355 # Any valid /opt directory should already exist so avoid the remote call.
356 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
357 return
358 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800359
Alex Klein1699fab2022-09-08 08:46:06 -0600360 def _GetDeviceInfo(self):
361 """Returns the disk space used and available for the target diectory."""
362 steps = [
363 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
364 functools.partial(
365 self._GetRemoteMountFree, self.options.target_dir
366 ),
367 ]
368 return_values = parallel.RunParallelSteps(steps, return_values=True)
369 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700370
Alex Klein1699fab2022-09-08 08:46:06 -0600371 def _CheckDeviceFreeSpace(self, device_info):
372 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700373
Alex Klein1699fab2022-09-08 08:46:06 -0600374 Args:
375 device_info: A DeviceInfo named tuple.
376 """
377 effective_free = (
378 device_info.target_dir_size + device_info.target_fs_free
379 )
380 staging_size = self._GetStagingDirSize()
381 if effective_free < staging_size:
382 raise DeployFailure(
383 "Not enough free space on the device. Required: %s MiB, "
384 "actual: %s MiB."
385 % (staging_size // 1024, effective_free // 1024)
386 )
387 if device_info.target_fs_free < (100 * 1024):
388 logging.warning(
389 "The device has less than 100MB free. deploy_chrome may "
390 "hang during the transfer."
391 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700392
Alex Klein1699fab2022-09-08 08:46:06 -0600393 def _ShouldUseCompression(self):
394 """Checks if compression should be used for rsync."""
395 if self.options.compress == "always":
396 return True
397 elif self.options.compress == "never":
398 return False
399 elif self.options.compress == "auto":
400 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 def _Deploy(self):
403 logging.info(
404 "Copying %s to %s on device...",
405 self._deployment_name,
406 self.options.target_dir,
407 )
408 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
409 # This does not work for deploy.
410 if not self.device.HasRsync():
411 raise DeployFailure(
412 "rsync is not found on the device.\n"
413 "Run dev_install on the device to get rsync installed."
414 )
415 self.device.CopyToDevice(
416 "%s/" % os.path.abspath(self.staging_dir),
417 self.options.target_dir,
418 mode="rsync",
419 inplace=True,
420 compress=self._ShouldUseCompression(),
421 debug_level=logging.INFO,
422 verbose=self.options.verbose,
423 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 # Set the security context on the default Chrome dir if that's where it's
426 # getting deployed, and only on SELinux supported devices.
427 if (
428 not self.options.lacros
429 and self.device.IsSELinuxAvailable()
430 and (
431 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
432 )
433 ):
434 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800435
Alex Klein1699fab2022-09-08 08:46:06 -0600436 for p in self.copy_paths:
437 if p.mode:
438 # Set mode if necessary.
439 self.device.run(
440 "chmod %o %s/%s"
441 % (
442 p.mode,
443 self.options.target_dir,
444 p.src if not p.dest else p.dest,
445 )
446 )
Steve Funge984a532013-11-25 17:09:25 -0800447
Alex Klein1699fab2022-09-08 08:46:06 -0600448 if self.options.lacros:
449 self.device.run(
450 ["chown", "-R", "chronos:chronos", self.options.target_dir]
451 )
Erik Chen75a2f492020-08-06 19:15:11 -0700452
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000453 if self.options.compressed_ash:
454 self.device.run(["start", COMPRESSED_ASH_SERVICE])
455
Alex Klein1699fab2022-09-08 08:46:06 -0600456 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
457 # pick up major changes (bus type, logging, etc.), but all we care about is
458 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
459 # be authorized to take ownership of its service names.
460 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600461
Erik Chen75a2f492020-08-06 19:15:11 -0700462 if self.options.startui and self._stopped_ui:
Alex Klein1699fab2022-09-08 08:46:06 -0600463 logging.info("Starting UI...")
464 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800465
Alex Klein1699fab2022-09-08 08:46:06 -0600466 def _DeployTestBinaries(self):
467 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700468
Alex Klein1699fab2022-09-08 08:46:06 -0600469 There could be several binaries located in the local build dir, so compare
470 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
471 over any that we also built ourselves.
472 """
473 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
474 if r.returncode != 0:
475 raise DeployFailure(
476 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
477 )
478 binaries_to_copy = []
479 for f in r.stdout.splitlines():
480 binaries_to_copy.append(
481 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
482 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700483
Alex Klein1699fab2022-09-08 08:46:06 -0600484 staging_dir = os.path.join(
485 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
486 )
487 _PrepareStagingDir(
488 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
489 )
490 # Deploying can occasionally run into issues with rsync getting a broken
491 # pipe, so retry several times. See crbug.com/1141618 for more
492 # information.
493 retry_util.RetryException(
494 None,
495 3,
496 self.device.CopyToDevice,
497 staging_dir,
498 os.path.dirname(_CHROME_TEST_BIN_DIR),
499 mode="rsync",
500 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700501
Alex Klein1699fab2022-09-08 08:46:06 -0600502 def _CheckConnection(self):
503 try:
504 logging.info("Testing connection to the device...")
505 self.device.run("true")
506 except cros_build_lib.RunCommandError as ex:
507 logging.error("Error connecting to the test device.")
508 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800509
Alex Klein1699fab2022-09-08 08:46:06 -0600510 def _CheckBoard(self):
511 """Check that the Chrome build is targeted for the device board."""
512 if self.options.board == self.device.board:
513 return
514 logging.warning(
515 "Device board is %s whereas target board is %s.",
516 self.device.board,
517 self.options.board,
518 )
519 if self.options.force:
520 return
521 if not cros_build_lib.BooleanPrompt(
522 "Continue despite board mismatch?", False
523 ):
524 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800525
Alex Klein1699fab2022-09-08 08:46:06 -0600526 def _CheckDeployType(self):
527 if self.options.build_dir:
528
529 def BinaryExists(filename):
530 """Checks if the passed-in file is present in the build directory."""
531 return os.path.exists(
532 os.path.join(self.options.build_dir, filename)
533 )
534
535 # In the future, lacros-chrome and ash-chrome will likely be named
536 # something other than 'chrome' to avoid confusion.
537 # Handle non-Chrome deployments.
538 if not BinaryExists("chrome"):
539 if BinaryExists("app_shell"):
540 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
541
542 def _PrepareStagingDir(self):
543 _PrepareStagingDir(
544 self.options,
545 self.tempdir,
546 self.staging_dir,
547 self.copy_paths,
548 self.chrome_dir,
549 )
550
551 def _MountTarget(self):
552 logging.info("Mounting Chrome...")
553
554 # Create directory if does not exist.
555 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
556 try:
557 # Umount the existing mount on mount_dir if present first.
558 self.device.run(
559 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
560 )
561 except cros_build_lib.RunCommandError as e:
562 logging.error("Failed to umount %s", self.options.mount_dir)
563 # If there is a failure, check if some processs is using the mount_dir.
564 result = self.device.run(
565 LSOF_COMMAND % (self.options.mount_dir,),
566 check=False,
567 capture_output=True,
568 encoding="utf-8",
569 )
570 logging.error("lsof %s -->", self.options.mount_dir)
571 logging.error(result.stdout)
572 raise e
573
574 self.device.run(
575 _BIND_TO_FINAL_DIR_CMD
576 % (self.options.target_dir, self.options.mount_dir)
577 )
578
579 # Chrome needs partition to have exec and suid flags set
580 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
581
582 def Cleanup(self):
583 """Clean up RemoteDevice."""
584 if not self.options.staging_only:
585 self.device.Cleanup()
586
587 def Perform(self):
588 self._CheckDeployType()
589
590 # If requested, just do the staging step.
591 if self.options.staging_only:
592 self._PrepareStagingDir()
593 return 0
594
595 # Check that the build matches the device. Lacros-chrome skips this check as
596 # it's currently board independent. This means that it's possible to deploy
597 # a build of lacros-chrome with a mismatched architecture. We don't try to
598 # prevent this developer foot-gun.
599 if not self.options.lacros:
600 self._CheckBoard()
601
602 # Ensure that the target directory exists before running parallel steps.
603 self._EnsureTargetDir()
604
605 logging.info("Preparing device")
606 steps = [
607 self._GetDeviceInfo,
608 self._CheckConnection,
609 self._MountRootfsAsWritable,
610 self._PrepareStagingDir,
611 ]
612
613 restart_ui = True
614 if self.options.lacros:
615 # If this is a lacros build, we only want to restart ash-chrome if needed.
616 restart_ui = False
617 steps.append(self._KillLacrosChrome)
618 if self.options.reset_lacros:
619 steps.append(self._ResetLacrosChrome)
620 if self.options.modify_config_file:
621 restart_ui = self._ModifyConfigFileIfNeededForLacros()
622
623 if restart_ui:
624 steps.append(self._KillAshChromeIfNeeded)
625 self._stopped_ui = True
626
627 ret = parallel.RunParallelSteps(
628 steps, halt_on_error=True, return_values=True
629 )
630 self._CheckDeviceFreeSpace(ret[0])
631
632 # If the root dir is not writable, try disabling rootfs verification.
633 # (We always do this by default so that developers can write to
634 # /etc/chriome_dev.conf and other directories in the rootfs).
635 if self._root_dir_is_still_readonly.is_set():
636 if self.options.noremove_rootfs_verification:
637 logging.warning("Skipping disable rootfs verification.")
638 elif not self._DisableRootfsVerification():
639 logging.warning("Failed to disable rootfs verification.")
640
641 # If the target dir is still not writable (i.e. the user opted out or the
642 # command failed), abort.
643 if not self.device.IsDirWritable(self.options.target_dir):
644 if self.options.startui and self._stopped_ui:
645 logging.info("Restarting Chrome...")
646 self.device.run("start ui")
647 raise DeployFailure(
648 "Target location is not writable. Aborting."
649 )
650
651 if self.options.mount_dir is not None:
652 self._MountTarget()
653
654 # Actually deploy Chrome to the device.
655 self._Deploy()
656 if self.options.deploy_test_binaries:
657 self._DeployTestBinaries()
658
659 def _ModifyConfigFileIfNeededForLacros(self):
660 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
661
662 Returns:
663 True if the file is modified, and the return value is usually used to
664 determine whether restarting ash-chrome is needed.
665 """
666 assert (
667 self.options.lacros
668 ), "Only deploying lacros-chrome needs to modify the config file."
669 # Update /etc/chrome_dev.conf to include appropriate flags.
670 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000671 if self.options.enable_lacros_support:
672 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
673 if result.stdout.strip() == MODIFIED_CONF_FILE:
674 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600675 result = self.device.run(
676 _SET_LACROS_PATH_VIA_CONF_COMMAND
677 % {
678 "conf_file": _CONF_FILE,
679 "lacros_path": self.options.target_dir,
680 "modified_conf_file": MODIFIED_CONF_FILE,
681 },
682 shell=True,
683 )
684 if result.stdout.strip() == MODIFIED_CONF_FILE:
685 modified = True
686
687 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800688
689
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700690def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600691 """Convert formatted string to dictionary."""
692 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700693
694
Steven Bennetts368c3e52016-09-23 13:05:21 -0700695def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600696 """Convert GN_ARGS-formatted string to dictionary."""
697 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700698
699
Ryan Cuie535b172012-10-19 18:25:03 -0700700def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600701 """Create our custom parser."""
702 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700703
Alex Klein1699fab2022-09-08 08:46:06 -0600704 # TODO(rcui): Have this use the UI-V2 format of having source and target
705 # device be specified as positional arguments.
706 parser.add_argument(
707 "--force",
708 action="store_true",
709 default=False,
710 help="Skip all prompts (such as the prompt for disabling "
711 "of rootfs verification). This may result in the "
712 "target machine being rebooted.",
713 )
714 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
715 parser.add_argument(
716 "--board",
717 default=sdk_board_env,
718 help="The board the Chrome build is targeted for. When "
719 "in a 'cros chrome-sdk' shell, defaults to the SDK "
720 "board.",
721 )
722 parser.add_argument(
723 "--build-dir",
724 type="path",
725 help="The directory with Chrome build artifacts to "
726 "deploy from. Typically of format "
727 "<chrome_root>/out/Debug. When this option is used, "
728 "the GN_ARGS environment variable must be set.",
729 )
730 parser.add_argument(
731 "--target-dir",
732 type="path",
733 default=None,
734 help="Target directory on device to deploy Chrome into.",
735 )
736 parser.add_argument(
737 "-g",
738 "--gs-path",
739 type="gs_path",
740 help="GS path that contains the chrome to deploy.",
741 )
742 parser.add_argument(
743 "--private-key",
744 type="path",
745 default=None,
746 help="An ssh private key to use when deploying to " "a CrOS device.",
747 )
748 parser.add_argument(
749 "--nostartui",
750 action="store_false",
751 dest="startui",
752 default=True,
753 help="Don't restart the ui daemon after deployment.",
754 )
755 parser.add_argument(
756 "--nostrip",
757 action="store_false",
758 dest="dostrip",
759 default=True,
760 help="Don't strip binaries during deployment. Warning: "
761 "the resulting binaries will be very large!",
762 )
763 parser.add_argument(
764 "-d",
765 "--device",
766 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
767 help="Device hostname or IP in the format hostname[:port].",
768 )
769 parser.add_argument(
770 "--mount-dir",
771 type="path",
772 default=None,
773 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000774 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600775 "Any existing mount on this directory will be "
776 "umounted first.",
777 )
778 parser.add_argument(
779 "--mount",
780 action="store_true",
781 default=False,
782 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000783 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600784 "Any existing mount on this directory will be "
785 "umounted first.",
786 )
787 parser.add_argument(
788 "--noremove-rootfs-verification",
789 action="store_true",
790 default=False,
791 help="Never remove rootfs verification.",
792 )
793 parser.add_argument(
794 "--deploy-test-binaries",
795 action="store_true",
796 default=False,
797 help="Also deploy any test binaries to %s. Useful for "
798 "running any Tast tests that execute these "
799 "binaries." % _CHROME_TEST_BIN_DIR,
800 )
801 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600802 "--use-external-config",
803 action="store_true",
804 help="When identifying the configuration for a board, "
805 "force usage of the external configuration if both "
806 "internal and external are available. This only "
807 "has an effect when stripping Chrome, i.e. when "
808 "--nostrip is not passed in.",
809 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700810
Georg Neis9b1ff192022-09-14 08:07:22 +0000811 group = parser.add_argument_group("Lacros Options")
812 group.add_argument(
813 "--lacros",
814 action="store_true",
815 default=False,
816 help="Deploys lacros-chrome rather than ash-chrome.",
817 )
818 group.add_argument(
819 "--reset-lacros",
820 action="store_true",
821 default=False,
822 help="Reset Lacros by deleting Lacros user data dir if it exists.",
823 )
824 group.add_argument(
825 "--skip-enabling-lacros-support",
826 action="store_false",
827 dest="enable_lacros_support",
828 help="By default, deploying lacros-chrome modifies the "
829 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
830 "and (2) set the Lacros path, which can interfere with automated "
831 "testing. With this flag, part (1) will be skipped. See the "
832 "--skip-modifying-config-file flag for skipping both parts.",
833 )
834 group.add_argument(
835 "--skip-modifying-config-file",
836 action="store_false",
837 dest="modify_config_file",
838 help="When deploying lacros-chrome, do not modify the "
839 "/etc/chrome_dev.conf file. See also the "
840 "--skip-enabling-lacros-support flag.",
841 )
842
Alex Klein1699fab2022-09-08 08:46:06 -0600843 group = parser.add_argument_group("Advanced Options")
844 group.add_argument(
845 "-l",
846 "--local-pkg-path",
847 type="path",
848 help="Path to local chrome prebuilt package to deploy.",
849 )
850 group.add_argument(
851 "--sloppy",
852 action="store_true",
853 default=False,
854 help="Ignore when mandatory artifacts are missing.",
855 )
856 group.add_argument(
857 "--staging-flags",
858 default=None,
859 type=ValidateStagingFlags,
860 help=(
861 "Extra flags to control staging. Valid flags are - "
862 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
863 ),
864 )
865 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
866 group.add_argument(
867 "--strict",
868 action="store_true",
869 default=False,
870 help='Deprecated. Default behavior is "strict". Use '
871 "--sloppy to omit warnings for missing optional "
872 "files.",
873 )
874 group.add_argument(
875 "--strip-flags",
876 default=None,
877 help="Flags to call the 'strip' binutil tool with. "
878 "Overrides the default arguments.",
879 )
880 group.add_argument(
881 "--ping",
882 action="store_true",
883 default=False,
884 help="Ping the device before connection attempt.",
885 )
886 group.add_argument(
887 "--process-timeout",
888 type=int,
889 default=KILL_PROC_MAX_WAIT,
890 help="Timeout for process shutdown.",
891 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700892
Alex Klein1699fab2022-09-08 08:46:06 -0600893 group = parser.add_argument_group(
894 "Metadata Overrides (Advanced)",
895 description="Provide all of these overrides in order to remove "
896 "dependencies on metadata.json existence.",
897 )
898 group.add_argument(
899 "--target-tc",
900 action="store",
901 default=None,
902 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
903 )
904 group.add_argument(
905 "--toolchain-url",
906 action="store",
907 default=None,
908 help="Override toolchain url format pattern, e.g. "
909 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
910 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700911
Alex Klein1699fab2022-09-08 08:46:06 -0600912 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
913 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
914 parser.add_argument(
915 "--gyp-defines",
916 default=None,
917 type=ValidateStagingFlags,
918 help=argparse.SUPPRESS,
919 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700920
Alex Klein1699fab2022-09-08 08:46:06 -0600921 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
922 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
923 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
924 parser.add_argument(
925 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
926 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700927
Alex Klein1699fab2022-09-08 08:46:06 -0600928 # Path of an empty directory to stage chrome artifacts to. Defaults to a
929 # temporary directory that is removed when the script finishes. If the path
930 # is specified, then it will not be removed.
931 parser.add_argument(
932 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
933 )
934 # Only prepare the staging directory, and skip deploying to the device.
935 parser.add_argument(
936 "--staging-only",
937 action="store_true",
938 default=False,
939 help=argparse.SUPPRESS,
940 )
941 # Uploads the compressed staging directory to the given gs:// path URI.
942 parser.add_argument(
943 "--staging-upload",
944 type="gs_path",
945 help="GS path to upload the compressed staging files to.",
946 )
947 # Used alongside --staging-upload to upload with public-read ACL.
948 parser.add_argument(
949 "--public-read",
950 action="store_true",
951 default=False,
952 help="GS path to upload the compressed staging files to.",
953 )
954 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
955 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
956 # fetching the SDK toolchain.
957 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
958 parser.add_argument(
959 "--compress",
960 action="store",
961 default="auto",
962 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000963 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -0600964 'is set to "auto", that disables compression if '
965 "the target device has a gigabit ethernet port.",
966 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000967 parser.add_argument(
968 "--compressed-ash",
969 action="store_true",
970 default=False,
971 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
972 "binary is stored on DUT in squashfs, mounted upon boot.",
973 )
Alex Klein1699fab2022-09-08 08:46:06 -0600974 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700975
Ryan Cuie535b172012-10-19 18:25:03 -0700976
977def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600978 """Parse args, and run environment-independent checks."""
979 parser = _CreateParser()
980 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700981
Alex Klein1699fab2022-09-08 08:46:06 -0600982 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
983 parser.error(
984 "Need to specify either --gs-path, --local-pkg-path, or "
985 "--build-dir"
986 )
987 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
988 parser.error(
989 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
990 )
991 if options.lacros:
992 if options.dostrip and not options.board:
993 parser.error("Please specify --board.")
994 if options.mount_dir or options.mount:
995 parser.error("--lacros does not support --mount or --mount-dir")
996 if options.deploy_test_binaries:
997 parser.error("--lacros does not support --deploy-test-binaries")
998 if options.local_pkg_path:
999 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001000 if options.compressed_ash:
1001 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001002 else:
1003 if not options.board and options.build_dir:
1004 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1005 if match:
1006 options.board = match.group(1)
1007 logging.info("--board is set to %s", options.board)
1008 if not options.board:
1009 parser.error("--board is required")
1010 if options.gs_path and options.local_pkg_path:
1011 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1012 if not (options.staging_only or options.device):
1013 parser.error("Need to specify --device")
1014 if options.staging_flags and not options.build_dir:
1015 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001016
Alex Klein1699fab2022-09-08 08:46:06 -06001017 if options.strict:
1018 logging.warning("--strict is deprecated.")
1019 if options.gyp_defines:
1020 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001021
Alex Klein1699fab2022-09-08 08:46:06 -06001022 if options.mount or options.mount_dir:
1023 if not options.target_dir:
1024 options.target_dir = _CHROME_DIR_MOUNT
1025 else:
1026 if not options.target_dir:
1027 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001028
Alex Klein1699fab2022-09-08 08:46:06 -06001029 if options.mount and not options.mount_dir:
1030 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001031
Alex Klein1699fab2022-09-08 08:46:06 -06001032 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001033
1034
Mike Frysingerc3061a62015-06-04 04:16:18 -04001035def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001036 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001037
Alex Klein1699fab2022-09-08 08:46:06 -06001038 Args:
1039 options: The options object returned by the cli parser.
1040 """
1041 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1042 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001043
Alex Klein1699fab2022-09-08 08:46:06 -06001044 if not options.gn_args:
1045 gn_env = os.getenv("GN_ARGS")
1046 if gn_env is not None:
1047 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1048 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001049
Alex Klein1699fab2022-09-08 08:46:06 -06001050 if not options.staging_flags:
1051 use_env = os.getenv("USE")
1052 if use_env is not None:
1053 options.staging_flags = " ".join(
1054 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1055 )
1056 logging.info(
1057 "Staging flags taken from USE in environment: %s",
1058 options.staging_flags,
1059 )
Steven Bennetts60600462016-05-12 10:40:20 -07001060
Ryan Cuia56a71e2012-10-18 18:40:35 -07001061
Ryan Cui504db722013-01-22 11:48:01 -08001062def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001063 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001064
Alex Klein1699fab2022-09-08 08:46:06 -06001065 Returns:
1066 Path to the fetched chrome tarball.
1067 """
1068 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1069 files = gs_ctx.LS(gs_path)
1070 files = [
1071 found
1072 for found in files
1073 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1074 ]
1075 if not files:
1076 raise Exception("No chrome package found at %s" % gs_path)
1077 elif len(files) > 1:
1078 # - Users should provide us with a direct link to either a stripped or
1079 # unstripped chrome package.
1080 # - In the case of being provided with an archive directory, where both
1081 # stripped and unstripped chrome available, use the stripped chrome
1082 # package.
1083 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
1084 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
1085 files = [f for f in files if not "unstripped" in f]
1086 assert len(files) == 1
1087 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001088
Alex Klein1699fab2022-09-08 08:46:06 -06001089 filename = _UrlBaseName(files[0])
1090 logging.info("Fetching %s...", filename)
1091 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1092 chrome_path = os.path.join(tempdir, filename)
1093 assert os.path.exists(chrome_path)
1094 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001095
1096
Ryan Cuif890a3e2013-03-07 18:57:06 -08001097@contextlib.contextmanager
1098def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001099 if not options.dostrip:
1100 yield None
1101 elif options.strip_bin:
1102 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001103 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001104 sdk = cros_chrome_sdk.SDKFetcher(
1105 options.cache_dir,
1106 options.board,
1107 use_external_config=options.use_external_config,
1108 )
1109 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1110 with sdk.Prepare(
1111 components=components,
1112 target_tc=options.target_tc,
1113 toolchain_url=options.toolchain_url,
1114 ) as ctx:
1115 env_path = os.path.join(
1116 ctx.key_map[constants.CHROME_ENV_TAR].path,
1117 constants.CHROME_ENV_FILE,
1118 )
1119 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1120 strip_bin = os.path.join(
1121 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1122 "bin",
1123 os.path.basename(strip_bin),
1124 )
1125 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001126
Alex Klein1699fab2022-09-08 08:46:06 -06001127
1128def _UploadStagingDir(
1129 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1130) -> None:
1131 """Uploads the compressed staging directory.
1132
1133 Args:
1134 options: options object.
1135 tempdir: Scratch space.
1136 staging_dir: Directory staging chrome files.
1137 """
1138 staging_tarball_path = os.path.join(
1139 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1140 )
1141 logging.info(
1142 "Compressing staging dir (%s) to (%s)",
1143 staging_dir,
1144 staging_tarball_path,
1145 )
1146 cros_build_lib.CreateTarball(
1147 staging_tarball_path,
1148 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001149 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001150 extra_env={"ZSTD_CLEVEL": "9"},
1151 )
1152 logging.info(
1153 "Uploading staging tarball (%s) into %s",
1154 staging_tarball_path,
1155 options.staging_upload,
1156 )
1157 ctx = gs.GSContext()
1158 ctx.Copy(
1159 staging_tarball_path,
1160 options.staging_upload,
1161 acl="public-read" if options.public_read else "",
1162 )
1163
1164
1165def _PrepareStagingDir(
1166 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1167):
1168 """Place the necessary files in the staging directory.
1169
1170 The staging directory is the directory used to rsync the build artifacts over
1171 to the device. Only the necessary Chrome build artifacts are put into the
1172 staging directory.
1173 """
1174 if chrome_dir is None:
1175 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1176 osutils.SafeMakedirs(staging_dir)
1177 os.chmod(staging_dir, 0o755)
1178 if options.build_dir:
1179 with _StripBinContext(options) as strip_bin:
1180 strip_flags = (
1181 None
1182 if options.strip_flags is None
1183 else shlex.split(options.strip_flags)
1184 )
1185 chrome_util.StageChromeFromBuildDir(
1186 staging_dir,
1187 options.build_dir,
1188 strip_bin,
1189 sloppy=options.sloppy,
1190 gn_args=options.gn_args,
1191 staging_flags=options.staging_flags,
1192 strip_flags=strip_flags,
1193 copy_paths=copy_paths,
1194 )
1195 else:
1196 pkg_path = options.local_pkg_path
1197 if options.gs_path:
1198 pkg_path = _FetchChromePackage(
1199 options.cache_dir, tempdir, options.gs_path
1200 )
1201
1202 assert pkg_path
1203 logging.info("Extracting %s...", pkg_path)
1204 # Extract only the ./opt/google/chrome contents, directly into the staging
1205 # dir, collapsing the directory hierarchy.
1206 if pkg_path[-4:] == ".zip":
1207 cros_build_lib.dbg_run(
1208 [
1209 "unzip",
1210 "-X",
1211 pkg_path,
1212 _ANDROID_DIR_EXTRACT_PATH,
1213 "-d",
1214 staging_dir,
1215 ]
1216 )
1217 for filename in glob.glob(
1218 os.path.join(staging_dir, "system/chrome/*")
1219 ):
1220 shutil.move(filename, staging_dir)
1221 osutils.RmDir(
1222 os.path.join(staging_dir, "system"), ignore_missing=True
1223 )
1224 else:
1225 compression = cros_build_lib.CompressionDetectType(pkg_path)
1226 compressor = cros_build_lib.FindCompressor(compression)
1227 cros_build_lib.dbg_run(
1228 [
1229 "tar",
1230 "--strip-components",
1231 "4",
1232 "--extract",
1233 "-I",
1234 compressor,
1235 "--preserve-permissions",
1236 "--file",
1237 pkg_path,
1238 ".%s" % chrome_dir,
1239 ],
1240 cwd=staging_dir,
1241 )
1242
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001243 if options.compressed_ash:
1244 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1245 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001246 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1247 # this is in test, cut the known suffix of experimental overlays.
1248 sdk_orig_board = options.board
1249 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1250 sdk_orig_board = sdk_orig_board[
1251 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1252 ]
1253
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001254 sdk = cros_chrome_sdk.SDKFetcher(
1255 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001256 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001257 use_external_config=options.use_external_config,
1258 )
1259 with sdk.Prepare(
1260 components=[],
1261 target_tc=options.target_tc,
1262 toolchain_url=options.toolchain_url,
1263 ):
1264 cros_build_lib.dbg_run(
1265 [
1266 "mksquashfs",
1267 RAW_ASH_FILE,
1268 COMPRESSED_ASH_FILE,
1269 "-all-root",
1270 "-no-progress",
1271 "-comp",
1272 "zstd",
1273 ],
1274 cwd=staging_dir,
1275 )
1276 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1277
Alex Klein1699fab2022-09-08 08:46:06 -06001278 if options.staging_upload:
1279 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001280
Ryan Cui71aa8de2013-04-19 16:12:55 -07001281
Ryan Cui3045c5d2012-07-13 18:00:33 -07001282def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001283 options = _ParseCommandLine(argv)
1284 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001285
Alex Klein1699fab2022-09-08 08:46:06 -06001286 with osutils.TempDir(set_global=True) as tempdir:
1287 staging_dir = options.staging_dir
1288 if not staging_dir:
1289 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001290
Alex Klein1699fab2022-09-08 08:46:06 -06001291 deploy = DeployChrome(options, tempdir, staging_dir)
1292 try:
1293 deploy.Perform()
1294 except failures_lib.StepFailure as ex:
1295 raise SystemExit(str(ex).strip())
1296 deploy.Cleanup()