blob: 1c30a974bb7dbb49d9728a355828369d13ed26b9 [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)
85
Alex Klein1699fab2022-09-08 08:46:06 -060086LACROS_DIR = "/usr/local/lacros-chrome"
87_CONF_FILE = "/etc/chrome_dev.conf"
88_KILL_LACROS_CHROME_CMD = "pkill -f %(lacros_dir)s/chrome"
89_RESET_LACROS_CHROME_CMD = "rm -rf /home/chronos/user/lacros"
90MODIFIED_CONF_FILE = f"modified {_CONF_FILE}"
Erik Chen75a2f492020-08-06 19:15:11 -070091
92# This command checks if "--enable-features=LacrosSupport" is present in
93# /etc/chrome_dev.conf. If it is not, then it is added.
94# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
95# to modify chrome_dev.conf. Either revisit this policy or find another
96# mechanism to pass configuration to ash-chrome.
97ENABLE_LACROS_VIA_CONF_COMMAND = f"""
98 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
99 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
100 echo {MODIFIED_CONF_FILE};
101 fi
102"""
103
104# This command checks if "--lacros-chrome-path=" is present with the right value
105# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
106# and the new one is added.
107# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
108# to modify chrome_dev.conf. Either revisit this policy or find another
109# mechanism to pass configuration to ash-chrome.
110_SET_LACROS_PATH_VIA_CONF_COMMAND = """
111 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
112 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
113 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
114 echo %(modified_conf_file)s;
115 fi
116"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500117
Alex Klein1699fab2022-09-08 08:46:06 -0600118
Ryan Cui3045c5d2012-07-13 18:00:33 -0700119def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600120 """Return the last component of the URL."""
121 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700122
123
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700124class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600125 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800126
127
Ryan Cui7193a7e2013-04-26 14:15:19 -0700128DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600129 "DeviceInfo", ["target_dir_size", "target_fs_free"]
130)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700131
132
Ryan Cui3045c5d2012-07-13 18:00:33 -0700133class DeployChrome(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600134 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500135
Alex Klein1699fab2022-09-08 08:46:06 -0600136 def __init__(self, options, tempdir, staging_dir):
137 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 Args:
140 options: options object.
141 tempdir: Scratch space for the class. Caller has responsibility to clean
142 it up.
143 staging_dir: Directory to stage the files to.
144 """
145 self.tempdir = tempdir
146 self.options = options
147 self.staging_dir = staging_dir
148 if not self.options.staging_only:
149 hostname = options.device.hostname
150 port = options.device.port
151 self.device = remote.ChromiumOSDevice(
152 hostname,
153 port=port,
154 ping=options.ping,
155 private_key=options.private_key,
156 include_dev_paths=False,
157 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000158 if self._ShouldUseCompressedAsh():
159 self.options.compressed_ash = True
160
Alex Klein1699fab2022-09-08 08:46:06 -0600161 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 self._deployment_name = "lacros" if options.lacros else "chrome"
164 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700167
Alex Klein1699fab2022-09-08 08:46:06 -0600168 # Whether UI was stopped during setup.
169 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800170
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000171 def _ShouldUseCompressedAsh(self):
172 """Detects if the DUT uses compressed-ash setup."""
173 if self.options.lacros:
174 return False
175
176 return self.device.IfFileExists(COMPRESSED_ASH_PATH)
177
Alex Klein1699fab2022-09-08 08:46:06 -0600178 def _GetRemoteMountFree(self, remote_dir):
179 result = self.device.run(DF_COMMAND % remote_dir)
180 line = result.stdout.splitlines()[1]
181 value = line.split()[3]
182 multipliers = {
183 "G": 1024 * 1024 * 1024,
184 "M": 1024 * 1024,
185 "K": 1024,
186 }
187 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700188
Alex Klein1699fab2022-09-08 08:46:06 -0600189 def _GetRemoteDirSize(self, remote_dir):
190 result = self.device.run(
191 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
192 )
193 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 def _GetStagingDirSize(self):
196 result = cros_build_lib.dbg_run(
197 ["du", "-ks", self.staging_dir],
198 capture_output=True,
199 encoding="utf-8",
200 )
201 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 def _ChromeFileInUse(self):
204 result = self.device.run(
205 LSOF_COMMAND_CHROME % (self.options.target_dir,),
206 check=False,
207 capture_output=True,
208 )
209 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700210
Alex Klein1699fab2022-09-08 08:46:06 -0600211 def _Reboot(self):
212 # A reboot in developer mode takes a while (and has delays), so the user
213 # will have time to read and act on the USB boot instructions below.
214 logging.info(
215 "Please remember to press Ctrl-U if you are booting from USB."
216 )
217 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600218
Alex Klein1699fab2022-09-08 08:46:06 -0600219 def _DisableRootfsVerification(self):
220 if not self.options.force:
221 logging.error(
222 "Detected that the device has rootfs verification enabled."
223 )
224 logging.info(
225 "This script can automatically remove the rootfs "
226 "verification, which requires it to reboot the device."
227 )
228 logging.info("Make sure the device is in developer mode!")
229 logging.info("Skip this prompt by specifying --force.")
230 if not cros_build_lib.BooleanPrompt(
231 "Remove rootfs verification?", False
232 ):
233 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700234
Alex Klein1699fab2022-09-08 08:46:06 -0600235 logging.info(
236 "Removing rootfs verification from %s", self.options.device
237 )
238 # Running in VMs cause make_dev_ssd's firmware confidence checks to fail.
239 # Use --force to bypass the checks.
240 cmd = (
241 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
242 "--remove_rootfs_verification --force"
243 )
244 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
245 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700246
Alex Klein1699fab2022-09-08 08:46:06 -0600247 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 # Now that the machine has been rebooted, we need to kill Chrome again.
250 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 # Make sure the rootfs is writable now.
253 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700254
Alex Klein1699fab2022-09-08 08:46:06 -0600255 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 def _CheckUiJobStarted(self):
258 # status output is in the format:
259 # <job_name> <status> ['process' <pid>].
260 # <status> is in the format <goal>/<state>.
261 try:
262 result = self.device.run(
263 "status ui", capture_output=True, encoding="utf-8"
264 )
265 except cros_build_lib.RunCommandError as e:
266 if "Unknown job" in e.stderr:
267 return False
268 else:
269 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 def _KillLacrosChrome(self):
274 """This method kills lacros-chrome on the device, if it's running."""
275 self.device.run(
276 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
277 check=False,
278 )
Erik Chen75a2f492020-08-06 19:15:11 -0700279
Alex Klein1699fab2022-09-08 08:46:06 -0600280 def _ResetLacrosChrome(self):
281 """Reset Lacros to fresh state by deleting user data dir."""
282 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000283
Alex Klein1699fab2022-09-08 08:46:06 -0600284 def _KillAshChromeIfNeeded(self):
285 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 This method calls 'stop ui', and then also manually pkills both ash-chrome
288 and the session manager.
289 """
290 if self._CheckUiJobStarted():
291 logging.info("Shutting down Chrome...")
292 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700293
Alex Klein1699fab2022-09-08 08:46:06 -0600294 # Developers sometimes run session_manager manually, in which case we'll
295 # need to help shut the chrome processes down.
296 try:
297 with timeout_util.Timeout(self.options.process_timeout):
298 while self._ChromeFileInUse():
299 logging.warning(
300 "The chrome binary on the device is in use."
301 )
302 logging.warning(
303 "Killing chrome and session_manager processes...\n"
304 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700305
Alex Klein1699fab2022-09-08 08:46:06 -0600306 self.device.run(
307 "pkill 'chrome|session_manager'", check=False
308 )
309 # Wait for processes to actually terminate
310 time.sleep(POST_KILL_WAIT)
311 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000312 if self.options.compressed_ash:
313 result = self.device.run(
314 ["umount", RAW_ASH_PATH],
315 check=False,
316 capture_output=True,
317 )
318 if result.returncode and not (
319 result.returncode == 32
320 and "not mounted" in result.stderr
321 ):
322 raise DeployFailure(
323 "Could not unmount compressed ash. "
324 f"Error Code: {result.returncode}, "
325 f"Error Message: {result.stderr}"
326 )
Alex Klein1699fab2022-09-08 08:46:06 -0600327 except timeout_util.TimeoutError:
328 msg = (
329 "Could not kill processes after %s seconds. Please exit any "
330 "running chrome processes and try again."
331 % self.options.process_timeout
332 )
333 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700334
Alex Klein1699fab2022-09-08 08:46:06 -0600335 def _MountRootfsAsWritable(self, check=False):
336 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700337
Alex Klein1699fab2022-09-08 08:46:06 -0600338 If the command fails and the root dir is not writable then this function
339 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700340
Alex Klein1699fab2022-09-08 08:46:06 -0600341 Args:
342 check: See remote.RemoteAccess.RemoteSh for details.
343 """
344 # TODO: Should migrate to use the remount functions in remote_access.
345 result = self.device.run(
346 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
347 )
348 if result.returncode and not self.device.IsDirWritable("/"):
349 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700350
Alex Klein1699fab2022-09-08 08:46:06 -0600351 def _EnsureTargetDir(self):
352 """Ensures that the target directory exists on the remote device."""
353 target_dir = self.options.target_dir
354 # Any valid /opt directory should already exist so avoid the remote call.
355 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
356 return
357 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 def _GetDeviceInfo(self):
360 """Returns the disk space used and available for the target diectory."""
361 steps = [
362 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
363 functools.partial(
364 self._GetRemoteMountFree, self.options.target_dir
365 ),
366 ]
367 return_values = parallel.RunParallelSteps(steps, return_values=True)
368 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700369
Alex Klein1699fab2022-09-08 08:46:06 -0600370 def _CheckDeviceFreeSpace(self, device_info):
371 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 Args:
374 device_info: A DeviceInfo named tuple.
375 """
376 effective_free = (
377 device_info.target_dir_size + device_info.target_fs_free
378 )
379 staging_size = self._GetStagingDirSize()
380 if effective_free < staging_size:
381 raise DeployFailure(
382 "Not enough free space on the device. Required: %s MiB, "
383 "actual: %s MiB."
384 % (staging_size // 1024, effective_free // 1024)
385 )
386 if device_info.target_fs_free < (100 * 1024):
387 logging.warning(
388 "The device has less than 100MB free. deploy_chrome may "
389 "hang during the transfer."
390 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700391
Alex Klein1699fab2022-09-08 08:46:06 -0600392 def _ShouldUseCompression(self):
393 """Checks if compression should be used for rsync."""
394 if self.options.compress == "always":
395 return True
396 elif self.options.compress == "never":
397 return False
398 elif self.options.compress == "auto":
399 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900400
Alex Klein1699fab2022-09-08 08:46:06 -0600401 def _Deploy(self):
402 logging.info(
403 "Copying %s to %s on device...",
404 self._deployment_name,
405 self.options.target_dir,
406 )
407 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
408 # This does not work for deploy.
409 if not self.device.HasRsync():
410 raise DeployFailure(
411 "rsync is not found on the device.\n"
412 "Run dev_install on the device to get rsync installed."
413 )
414 self.device.CopyToDevice(
415 "%s/" % os.path.abspath(self.staging_dir),
416 self.options.target_dir,
417 mode="rsync",
418 inplace=True,
419 compress=self._ShouldUseCompression(),
420 debug_level=logging.INFO,
421 verbose=self.options.verbose,
422 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700423
Alex Klein1699fab2022-09-08 08:46:06 -0600424 # Set the security context on the default Chrome dir if that's where it's
425 # getting deployed, and only on SELinux supported devices.
426 if (
427 not self.options.lacros
428 and self.device.IsSELinuxAvailable()
429 and (
430 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
431 )
432 ):
433 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800434
Alex Klein1699fab2022-09-08 08:46:06 -0600435 for p in self.copy_paths:
436 if p.mode:
437 # Set mode if necessary.
438 self.device.run(
439 "chmod %o %s/%s"
440 % (
441 p.mode,
442 self.options.target_dir,
443 p.src if not p.dest else p.dest,
444 )
445 )
Steve Funge984a532013-11-25 17:09:25 -0800446
Alex Klein1699fab2022-09-08 08:46:06 -0600447 if self.options.lacros:
448 self.device.run(
449 ["chown", "-R", "chronos:chronos", self.options.target_dir]
450 )
Erik Chen75a2f492020-08-06 19:15:11 -0700451
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000452 if self.options.compressed_ash:
453 self.device.run(["start", COMPRESSED_ASH_SERVICE])
454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
456 # pick up major changes (bus type, logging, etc.), but all we care about is
457 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
458 # be authorized to take ownership of its service names.
459 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600460
Erik Chen75a2f492020-08-06 19:15:11 -0700461 if self.options.startui and self._stopped_ui:
Alex Klein1699fab2022-09-08 08:46:06 -0600462 logging.info("Starting UI...")
463 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800464
Alex Klein1699fab2022-09-08 08:46:06 -0600465 def _DeployTestBinaries(self):
466 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 There could be several binaries located in the local build dir, so compare
469 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
470 over any that we also built ourselves.
471 """
472 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
473 if r.returncode != 0:
474 raise DeployFailure(
475 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
476 )
477 binaries_to_copy = []
478 for f in r.stdout.splitlines():
479 binaries_to_copy.append(
480 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
481 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700482
Alex Klein1699fab2022-09-08 08:46:06 -0600483 staging_dir = os.path.join(
484 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
485 )
486 _PrepareStagingDir(
487 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
488 )
489 # Deploying can occasionally run into issues with rsync getting a broken
490 # pipe, so retry several times. See crbug.com/1141618 for more
491 # information.
492 retry_util.RetryException(
493 None,
494 3,
495 self.device.CopyToDevice,
496 staging_dir,
497 os.path.dirname(_CHROME_TEST_BIN_DIR),
498 mode="rsync",
499 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700500
Alex Klein1699fab2022-09-08 08:46:06 -0600501 def _CheckConnection(self):
502 try:
503 logging.info("Testing connection to the device...")
504 self.device.run("true")
505 except cros_build_lib.RunCommandError as ex:
506 logging.error("Error connecting to the test device.")
507 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 def _CheckBoard(self):
510 """Check that the Chrome build is targeted for the device board."""
511 if self.options.board == self.device.board:
512 return
513 logging.warning(
514 "Device board is %s whereas target board is %s.",
515 self.device.board,
516 self.options.board,
517 )
518 if self.options.force:
519 return
520 if not cros_build_lib.BooleanPrompt(
521 "Continue despite board mismatch?", False
522 ):
523 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800524
Alex Klein1699fab2022-09-08 08:46:06 -0600525 def _CheckDeployType(self):
526 if self.options.build_dir:
527
528 def BinaryExists(filename):
529 """Checks if the passed-in file is present in the build directory."""
530 return os.path.exists(
531 os.path.join(self.options.build_dir, filename)
532 )
533
534 # In the future, lacros-chrome and ash-chrome will likely be named
535 # something other than 'chrome' to avoid confusion.
536 # Handle non-Chrome deployments.
537 if not BinaryExists("chrome"):
538 if BinaryExists("app_shell"):
539 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
540
541 def _PrepareStagingDir(self):
542 _PrepareStagingDir(
543 self.options,
544 self.tempdir,
545 self.staging_dir,
546 self.copy_paths,
547 self.chrome_dir,
548 )
549
550 def _MountTarget(self):
551 logging.info("Mounting Chrome...")
552
553 # Create directory if does not exist.
554 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
555 try:
556 # Umount the existing mount on mount_dir if present first.
557 self.device.run(
558 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
559 )
560 except cros_build_lib.RunCommandError as e:
561 logging.error("Failed to umount %s", self.options.mount_dir)
562 # If there is a failure, check if some processs is using the mount_dir.
563 result = self.device.run(
564 LSOF_COMMAND % (self.options.mount_dir,),
565 check=False,
566 capture_output=True,
567 encoding="utf-8",
568 )
569 logging.error("lsof %s -->", self.options.mount_dir)
570 logging.error(result.stdout)
571 raise e
572
573 self.device.run(
574 _BIND_TO_FINAL_DIR_CMD
575 % (self.options.target_dir, self.options.mount_dir)
576 )
577
578 # Chrome needs partition to have exec and suid flags set
579 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
580
581 def Cleanup(self):
582 """Clean up RemoteDevice."""
583 if not self.options.staging_only:
584 self.device.Cleanup()
585
586 def Perform(self):
587 self._CheckDeployType()
588
589 # If requested, just do the staging step.
590 if self.options.staging_only:
591 self._PrepareStagingDir()
592 return 0
593
594 # Check that the build matches the device. Lacros-chrome skips this check as
595 # it's currently board independent. This means that it's possible to deploy
596 # a build of lacros-chrome with a mismatched architecture. We don't try to
597 # prevent this developer foot-gun.
598 if not self.options.lacros:
599 self._CheckBoard()
600
601 # Ensure that the target directory exists before running parallel steps.
602 self._EnsureTargetDir()
603
604 logging.info("Preparing device")
605 steps = [
606 self._GetDeviceInfo,
607 self._CheckConnection,
608 self._MountRootfsAsWritable,
609 self._PrepareStagingDir,
610 ]
611
612 restart_ui = True
613 if self.options.lacros:
614 # If this is a lacros build, we only want to restart ash-chrome if needed.
615 restart_ui = False
616 steps.append(self._KillLacrosChrome)
617 if self.options.reset_lacros:
618 steps.append(self._ResetLacrosChrome)
619 if self.options.modify_config_file:
620 restart_ui = self._ModifyConfigFileIfNeededForLacros()
621
622 if restart_ui:
623 steps.append(self._KillAshChromeIfNeeded)
624 self._stopped_ui = True
625
626 ret = parallel.RunParallelSteps(
627 steps, halt_on_error=True, return_values=True
628 )
629 self._CheckDeviceFreeSpace(ret[0])
630
631 # If the root dir is not writable, try disabling rootfs verification.
632 # (We always do this by default so that developers can write to
633 # /etc/chriome_dev.conf and other directories in the rootfs).
634 if self._root_dir_is_still_readonly.is_set():
635 if self.options.noremove_rootfs_verification:
636 logging.warning("Skipping disable rootfs verification.")
637 elif not self._DisableRootfsVerification():
638 logging.warning("Failed to disable rootfs verification.")
639
640 # If the target dir is still not writable (i.e. the user opted out or the
641 # command failed), abort.
642 if not self.device.IsDirWritable(self.options.target_dir):
643 if self.options.startui and self._stopped_ui:
644 logging.info("Restarting Chrome...")
645 self.device.run("start ui")
646 raise DeployFailure(
647 "Target location is not writable. Aborting."
648 )
649
650 if self.options.mount_dir is not None:
651 self._MountTarget()
652
653 # Actually deploy Chrome to the device.
654 self._Deploy()
655 if self.options.deploy_test_binaries:
656 self._DeployTestBinaries()
657
658 def _ModifyConfigFileIfNeededForLacros(self):
659 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
660
661 Returns:
662 True if the file is modified, and the return value is usually used to
663 determine whether restarting ash-chrome is needed.
664 """
665 assert (
666 self.options.lacros
667 ), "Only deploying lacros-chrome needs to modify the config file."
668 # Update /etc/chrome_dev.conf to include appropriate flags.
669 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000670 if self.options.enable_lacros_support:
671 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
672 if result.stdout.strip() == MODIFIED_CONF_FILE:
673 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600674 result = self.device.run(
675 _SET_LACROS_PATH_VIA_CONF_COMMAND
676 % {
677 "conf_file": _CONF_FILE,
678 "lacros_path": self.options.target_dir,
679 "modified_conf_file": MODIFIED_CONF_FILE,
680 },
681 shell=True,
682 )
683 if result.stdout.strip() == MODIFIED_CONF_FILE:
684 modified = True
685
686 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800687
688
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700689def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600690 """Convert formatted string to dictionary."""
691 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700692
693
Steven Bennetts368c3e52016-09-23 13:05:21 -0700694def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600695 """Convert GN_ARGS-formatted string to dictionary."""
696 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700697
698
Ryan Cuie535b172012-10-19 18:25:03 -0700699def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600700 """Create our custom parser."""
701 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700702
Alex Klein1699fab2022-09-08 08:46:06 -0600703 # TODO(rcui): Have this use the UI-V2 format of having source and target
704 # device be specified as positional arguments.
705 parser.add_argument(
706 "--force",
707 action="store_true",
708 default=False,
709 help="Skip all prompts (such as the prompt for disabling "
710 "of rootfs verification). This may result in the "
711 "target machine being rebooted.",
712 )
713 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
714 parser.add_argument(
715 "--board",
716 default=sdk_board_env,
717 help="The board the Chrome build is targeted for. When "
718 "in a 'cros chrome-sdk' shell, defaults to the SDK "
719 "board.",
720 )
721 parser.add_argument(
722 "--build-dir",
723 type="path",
724 help="The directory with Chrome build artifacts to "
725 "deploy from. Typically of format "
726 "<chrome_root>/out/Debug. When this option is used, "
727 "the GN_ARGS environment variable must be set.",
728 )
729 parser.add_argument(
730 "--target-dir",
731 type="path",
732 default=None,
733 help="Target directory on device to deploy Chrome into.",
734 )
735 parser.add_argument(
736 "-g",
737 "--gs-path",
738 type="gs_path",
739 help="GS path that contains the chrome to deploy.",
740 )
741 parser.add_argument(
742 "--private-key",
743 type="path",
744 default=None,
745 help="An ssh private key to use when deploying to " "a CrOS device.",
746 )
747 parser.add_argument(
748 "--nostartui",
749 action="store_false",
750 dest="startui",
751 default=True,
752 help="Don't restart the ui daemon after deployment.",
753 )
754 parser.add_argument(
755 "--nostrip",
756 action="store_false",
757 dest="dostrip",
758 default=True,
759 help="Don't strip binaries during deployment. Warning: "
760 "the resulting binaries will be very large!",
761 )
762 parser.add_argument(
763 "-d",
764 "--device",
765 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
766 help="Device hostname or IP in the format hostname[:port].",
767 )
768 parser.add_argument(
769 "--mount-dir",
770 type="path",
771 default=None,
772 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000773 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600774 "Any existing mount on this directory will be "
775 "umounted first.",
776 )
777 parser.add_argument(
778 "--mount",
779 action="store_true",
780 default=False,
781 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000782 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600783 "Any existing mount on this directory will be "
784 "umounted first.",
785 )
786 parser.add_argument(
787 "--noremove-rootfs-verification",
788 action="store_true",
789 default=False,
790 help="Never remove rootfs verification.",
791 )
792 parser.add_argument(
793 "--deploy-test-binaries",
794 action="store_true",
795 default=False,
796 help="Also deploy any test binaries to %s. Useful for "
797 "running any Tast tests that execute these "
798 "binaries." % _CHROME_TEST_BIN_DIR,
799 )
800 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600801 "--use-external-config",
802 action="store_true",
803 help="When identifying the configuration for a board, "
804 "force usage of the external configuration if both "
805 "internal and external are available. This only "
806 "has an effect when stripping Chrome, i.e. when "
807 "--nostrip is not passed in.",
808 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700809
Georg Neis9b1ff192022-09-14 08:07:22 +0000810 group = parser.add_argument_group("Lacros Options")
811 group.add_argument(
812 "--lacros",
813 action="store_true",
814 default=False,
815 help="Deploys lacros-chrome rather than ash-chrome.",
816 )
817 group.add_argument(
818 "--reset-lacros",
819 action="store_true",
820 default=False,
821 help="Reset Lacros by deleting Lacros user data dir if it exists.",
822 )
823 group.add_argument(
824 "--skip-enabling-lacros-support",
825 action="store_false",
826 dest="enable_lacros_support",
827 help="By default, deploying lacros-chrome modifies the "
828 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
829 "and (2) set the Lacros path, which can interfere with automated "
830 "testing. With this flag, part (1) will be skipped. See the "
831 "--skip-modifying-config-file flag for skipping both parts.",
832 )
833 group.add_argument(
834 "--skip-modifying-config-file",
835 action="store_false",
836 dest="modify_config_file",
837 help="When deploying lacros-chrome, do not modify the "
838 "/etc/chrome_dev.conf file. See also the "
839 "--skip-enabling-lacros-support flag.",
840 )
841
Alex Klein1699fab2022-09-08 08:46:06 -0600842 group = parser.add_argument_group("Advanced Options")
843 group.add_argument(
844 "-l",
845 "--local-pkg-path",
846 type="path",
847 help="Path to local chrome prebuilt package to deploy.",
848 )
849 group.add_argument(
850 "--sloppy",
851 action="store_true",
852 default=False,
853 help="Ignore when mandatory artifacts are missing.",
854 )
855 group.add_argument(
856 "--staging-flags",
857 default=None,
858 type=ValidateStagingFlags,
859 help=(
860 "Extra flags to control staging. Valid flags are - "
861 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
862 ),
863 )
864 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
865 group.add_argument(
866 "--strict",
867 action="store_true",
868 default=False,
869 help='Deprecated. Default behavior is "strict". Use '
870 "--sloppy to omit warnings for missing optional "
871 "files.",
872 )
873 group.add_argument(
874 "--strip-flags",
875 default=None,
876 help="Flags to call the 'strip' binutil tool with. "
877 "Overrides the default arguments.",
878 )
879 group.add_argument(
880 "--ping",
881 action="store_true",
882 default=False,
883 help="Ping the device before connection attempt.",
884 )
885 group.add_argument(
886 "--process-timeout",
887 type=int,
888 default=KILL_PROC_MAX_WAIT,
889 help="Timeout for process shutdown.",
890 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700891
Alex Klein1699fab2022-09-08 08:46:06 -0600892 group = parser.add_argument_group(
893 "Metadata Overrides (Advanced)",
894 description="Provide all of these overrides in order to remove "
895 "dependencies on metadata.json existence.",
896 )
897 group.add_argument(
898 "--target-tc",
899 action="store",
900 default=None,
901 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
902 )
903 group.add_argument(
904 "--toolchain-url",
905 action="store",
906 default=None,
907 help="Override toolchain url format pattern, e.g. "
908 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
909 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700910
Alex Klein1699fab2022-09-08 08:46:06 -0600911 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
912 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
913 parser.add_argument(
914 "--gyp-defines",
915 default=None,
916 type=ValidateStagingFlags,
917 help=argparse.SUPPRESS,
918 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700919
Alex Klein1699fab2022-09-08 08:46:06 -0600920 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
921 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
922 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
923 parser.add_argument(
924 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
925 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700926
Alex Klein1699fab2022-09-08 08:46:06 -0600927 # Path of an empty directory to stage chrome artifacts to. Defaults to a
928 # temporary directory that is removed when the script finishes. If the path
929 # is specified, then it will not be removed.
930 parser.add_argument(
931 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
932 )
933 # Only prepare the staging directory, and skip deploying to the device.
934 parser.add_argument(
935 "--staging-only",
936 action="store_true",
937 default=False,
938 help=argparse.SUPPRESS,
939 )
940 # Uploads the compressed staging directory to the given gs:// path URI.
941 parser.add_argument(
942 "--staging-upload",
943 type="gs_path",
944 help="GS path to upload the compressed staging files to.",
945 )
946 # Used alongside --staging-upload to upload with public-read ACL.
947 parser.add_argument(
948 "--public-read",
949 action="store_true",
950 default=False,
951 help="GS path to upload the compressed staging files to.",
952 )
953 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
954 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
955 # fetching the SDK toolchain.
956 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
957 parser.add_argument(
958 "--compress",
959 action="store",
960 default="auto",
961 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000962 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -0600963 'is set to "auto", that disables compression if '
964 "the target device has a gigabit ethernet port.",
965 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000966 parser.add_argument(
967 "--compressed-ash",
968 action="store_true",
969 default=False,
970 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
971 "binary is stored on DUT in squashfs, mounted upon boot.",
972 )
Alex Klein1699fab2022-09-08 08:46:06 -0600973 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700974
Ryan Cuie535b172012-10-19 18:25:03 -0700975
976def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600977 """Parse args, and run environment-independent checks."""
978 parser = _CreateParser()
979 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700980
Alex Klein1699fab2022-09-08 08:46:06 -0600981 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
982 parser.error(
983 "Need to specify either --gs-path, --local-pkg-path, or "
984 "--build-dir"
985 )
986 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
987 parser.error(
988 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
989 )
990 if options.lacros:
991 if options.dostrip and not options.board:
992 parser.error("Please specify --board.")
993 if options.mount_dir or options.mount:
994 parser.error("--lacros does not support --mount or --mount-dir")
995 if options.deploy_test_binaries:
996 parser.error("--lacros does not support --deploy-test-binaries")
997 if options.local_pkg_path:
998 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000999 if options.compressed_ash:
1000 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001001 else:
1002 if not options.board and options.build_dir:
1003 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1004 if match:
1005 options.board = match.group(1)
1006 logging.info("--board is set to %s", options.board)
1007 if not options.board:
1008 parser.error("--board is required")
1009 if options.gs_path and options.local_pkg_path:
1010 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1011 if not (options.staging_only or options.device):
1012 parser.error("Need to specify --device")
1013 if options.staging_flags and not options.build_dir:
1014 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001015
Alex Klein1699fab2022-09-08 08:46:06 -06001016 if options.strict:
1017 logging.warning("--strict is deprecated.")
1018 if options.gyp_defines:
1019 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001020
Alex Klein1699fab2022-09-08 08:46:06 -06001021 if options.mount or options.mount_dir:
1022 if not options.target_dir:
1023 options.target_dir = _CHROME_DIR_MOUNT
1024 else:
1025 if not options.target_dir:
1026 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001027
Alex Klein1699fab2022-09-08 08:46:06 -06001028 if options.mount and not options.mount_dir:
1029 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001030
Alex Klein1699fab2022-09-08 08:46:06 -06001031 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001032
1033
Mike Frysingerc3061a62015-06-04 04:16:18 -04001034def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001035 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001036
Alex Klein1699fab2022-09-08 08:46:06 -06001037 Args:
1038 options: The options object returned by the cli parser.
1039 """
1040 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1041 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001042
Alex Klein1699fab2022-09-08 08:46:06 -06001043 if not options.gn_args:
1044 gn_env = os.getenv("GN_ARGS")
1045 if gn_env is not None:
1046 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1047 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001048
Alex Klein1699fab2022-09-08 08:46:06 -06001049 if not options.staging_flags:
1050 use_env = os.getenv("USE")
1051 if use_env is not None:
1052 options.staging_flags = " ".join(
1053 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1054 )
1055 logging.info(
1056 "Staging flags taken from USE in environment: %s",
1057 options.staging_flags,
1058 )
Steven Bennetts60600462016-05-12 10:40:20 -07001059
Ryan Cuia56a71e2012-10-18 18:40:35 -07001060
Ryan Cui504db722013-01-22 11:48:01 -08001061def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001062 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001063
Alex Klein1699fab2022-09-08 08:46:06 -06001064 Returns:
1065 Path to the fetched chrome tarball.
1066 """
1067 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1068 files = gs_ctx.LS(gs_path)
1069 files = [
1070 found
1071 for found in files
1072 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1073 ]
1074 if not files:
1075 raise Exception("No chrome package found at %s" % gs_path)
1076 elif len(files) > 1:
1077 # - Users should provide us with a direct link to either a stripped or
1078 # unstripped chrome package.
1079 # - In the case of being provided with an archive directory, where both
1080 # stripped and unstripped chrome available, use the stripped chrome
1081 # package.
1082 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
1083 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
1084 files = [f for f in files if not "unstripped" in f]
1085 assert len(files) == 1
1086 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001087
Alex Klein1699fab2022-09-08 08:46:06 -06001088 filename = _UrlBaseName(files[0])
1089 logging.info("Fetching %s...", filename)
1090 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1091 chrome_path = os.path.join(tempdir, filename)
1092 assert os.path.exists(chrome_path)
1093 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001094
1095
Ryan Cuif890a3e2013-03-07 18:57:06 -08001096@contextlib.contextmanager
1097def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001098 if not options.dostrip:
1099 yield None
1100 elif options.strip_bin:
1101 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001102 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001103 sdk = cros_chrome_sdk.SDKFetcher(
1104 options.cache_dir,
1105 options.board,
1106 use_external_config=options.use_external_config,
1107 )
1108 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1109 with sdk.Prepare(
1110 components=components,
1111 target_tc=options.target_tc,
1112 toolchain_url=options.toolchain_url,
1113 ) as ctx:
1114 env_path = os.path.join(
1115 ctx.key_map[constants.CHROME_ENV_TAR].path,
1116 constants.CHROME_ENV_FILE,
1117 )
1118 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1119 strip_bin = os.path.join(
1120 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1121 "bin",
1122 os.path.basename(strip_bin),
1123 )
1124 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001125
Alex Klein1699fab2022-09-08 08:46:06 -06001126
1127def _UploadStagingDir(
1128 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1129) -> None:
1130 """Uploads the compressed staging directory.
1131
1132 Args:
1133 options: options object.
1134 tempdir: Scratch space.
1135 staging_dir: Directory staging chrome files.
1136 """
1137 staging_tarball_path = os.path.join(
1138 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1139 )
1140 logging.info(
1141 "Compressing staging dir (%s) to (%s)",
1142 staging_dir,
1143 staging_tarball_path,
1144 )
1145 cros_build_lib.CreateTarball(
1146 staging_tarball_path,
1147 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001148 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001149 extra_env={"ZSTD_CLEVEL": "9"},
1150 )
1151 logging.info(
1152 "Uploading staging tarball (%s) into %s",
1153 staging_tarball_path,
1154 options.staging_upload,
1155 )
1156 ctx = gs.GSContext()
1157 ctx.Copy(
1158 staging_tarball_path,
1159 options.staging_upload,
1160 acl="public-read" if options.public_read else "",
1161 )
1162
1163
1164def _PrepareStagingDir(
1165 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1166):
1167 """Place the necessary files in the staging directory.
1168
1169 The staging directory is the directory used to rsync the build artifacts over
1170 to the device. Only the necessary Chrome build artifacts are put into the
1171 staging directory.
1172 """
1173 if chrome_dir is None:
1174 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1175 osutils.SafeMakedirs(staging_dir)
1176 os.chmod(staging_dir, 0o755)
1177 if options.build_dir:
1178 with _StripBinContext(options) as strip_bin:
1179 strip_flags = (
1180 None
1181 if options.strip_flags is None
1182 else shlex.split(options.strip_flags)
1183 )
1184 chrome_util.StageChromeFromBuildDir(
1185 staging_dir,
1186 options.build_dir,
1187 strip_bin,
1188 sloppy=options.sloppy,
1189 gn_args=options.gn_args,
1190 staging_flags=options.staging_flags,
1191 strip_flags=strip_flags,
1192 copy_paths=copy_paths,
1193 )
1194 else:
1195 pkg_path = options.local_pkg_path
1196 if options.gs_path:
1197 pkg_path = _FetchChromePackage(
1198 options.cache_dir, tempdir, options.gs_path
1199 )
1200
1201 assert pkg_path
1202 logging.info("Extracting %s...", pkg_path)
1203 # Extract only the ./opt/google/chrome contents, directly into the staging
1204 # dir, collapsing the directory hierarchy.
1205 if pkg_path[-4:] == ".zip":
1206 cros_build_lib.dbg_run(
1207 [
1208 "unzip",
1209 "-X",
1210 pkg_path,
1211 _ANDROID_DIR_EXTRACT_PATH,
1212 "-d",
1213 staging_dir,
1214 ]
1215 )
1216 for filename in glob.glob(
1217 os.path.join(staging_dir, "system/chrome/*")
1218 ):
1219 shutil.move(filename, staging_dir)
1220 osutils.RmDir(
1221 os.path.join(staging_dir, "system"), ignore_missing=True
1222 )
1223 else:
1224 compression = cros_build_lib.CompressionDetectType(pkg_path)
1225 compressor = cros_build_lib.FindCompressor(compression)
1226 cros_build_lib.dbg_run(
1227 [
1228 "tar",
1229 "--strip-components",
1230 "4",
1231 "--extract",
1232 "-I",
1233 compressor,
1234 "--preserve-permissions",
1235 "--file",
1236 pkg_path,
1237 ".%s" % chrome_dir,
1238 ],
1239 cwd=staging_dir,
1240 )
1241
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001242 if options.compressed_ash:
1243 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1244 # configuration.
1245 sdk = cros_chrome_sdk.SDKFetcher(
1246 options.cache_dir,
1247 options.board,
1248 use_external_config=options.use_external_config,
1249 )
1250 with sdk.Prepare(
1251 components=[],
1252 target_tc=options.target_tc,
1253 toolchain_url=options.toolchain_url,
1254 ):
1255 cros_build_lib.dbg_run(
1256 [
1257 "mksquashfs",
1258 RAW_ASH_FILE,
1259 COMPRESSED_ASH_FILE,
1260 "-all-root",
1261 "-no-progress",
1262 "-comp",
1263 "zstd",
1264 ],
1265 cwd=staging_dir,
1266 )
1267 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1268
Alex Klein1699fab2022-09-08 08:46:06 -06001269 if options.staging_upload:
1270 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001271
Ryan Cui71aa8de2013-04-19 16:12:55 -07001272
Ryan Cui3045c5d2012-07-13 18:00:33 -07001273def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001274 options = _ParseCommandLine(argv)
1275 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001276
Alex Klein1699fab2022-09-08 08:46:06 -06001277 with osutils.TempDir(set_global=True) as tempdir:
1278 staging_dir = options.staging_dir
1279 if not staging_dir:
1280 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001281
Alex Klein1699fab2022-09-08 08:46:06 -06001282 deploy = DeployChrome(options, tempdir, staging_dir)
1283 try:
1284 deploy.Perform()
1285 except failures_lib.StepFailure as ex:
1286 raise SystemExit(str(ex).strip())
1287 deploy.Cleanup()