blob: ec3f16b85ff7e9aa23fcc9d4a8513995a7da247c [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"
Joel Hockey764728e2023-03-14 17:10:04 -070057LAST_LOGIN_COMMAND = "bootstat_get_last login-prompt-visible"
58UNLOCK_PASSWORD_COMMAND = "inject-keys.py -s %s -k enter"
Ryan Cui3045c5d2012-07-13 18:00:33 -070059
Alex Klein1699fab2022-09-08 08:46:06 -060060_ANDROID_DIR = "/system/chrome"
61_ANDROID_DIR_EXTRACT_PATH = "system/chrome/*"
Steve Funge984a532013-11-25 17:09:25 -080062
Alex Klein1699fab2022-09-08 08:46:06 -060063_CHROME_DIR = "/opt/google/chrome"
64_CHROME_DIR_MOUNT = "/mnt/stateful_partition/deploy_rootfs/opt/google/chrome"
65_CHROME_DIR_STAGING_TARBALL_ZSTD = "chrome.tar.zst"
66_CHROME_TEST_BIN_DIR = "/usr/local/libexec/chrome-binary-tests"
David James2cb34002013-03-01 18:42:40 -080067
David Haddock3151d912017-10-24 03:50:32 +000068_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
Alex Klein1699fab2022-09-08 08:46:06 -060069 "if mountpoint -q %(dir)s; then umount %(dir)s; fi"
70)
71_BIND_TO_FINAL_DIR_CMD = "mount --rbind %s %s"
72_SET_MOUNT_FLAGS_CMD = "mount -o remount,exec,suid %s"
73_MKDIR_P_CMD = "mkdir -p --mode 0775 %s"
74_FIND_TEST_BIN_CMD = "find %s -maxdepth 1 -executable -type f" % (
75 _CHROME_TEST_BIN_DIR
76)
David Haddock3151d912017-10-24 03:50:32 +000077
Alex Klein1699fab2022-09-08 08:46:06 -060078DF_COMMAND = "df -k %s"
David Haddock3151d912017-10-24 03:50:32 +000079
Daniil Lunev0c4f65c2022-09-19 11:01:34 +100080# This constants are related to an experiment of running compressed ash chrome
81# to save rootfs space. See b/247397013
82COMPRESSED_ASH_SERVICE = "mount-ash-chrome"
83COMPRESSED_ASH_FILE = "chrome.squashfs"
84RAW_ASH_FILE = "chrome"
85COMPRESSED_ASH_PATH = os.path.join(_CHROME_DIR, COMPRESSED_ASH_FILE)
86RAW_ASH_PATH = os.path.join(_CHROME_DIR, RAW_ASH_FILE)
Daniil Luneve2954832022-10-11 11:38:51 +110087COMPRESSED_ASH_OVERLAY_SUFFIX = "-compressed-ash"
Daniil Lunev0c4f65c2022-09-19 11:01:34 +100088
Alex Klein1699fab2022-09-08 08:46:06 -060089LACROS_DIR = "/usr/local/lacros-chrome"
90_CONF_FILE = "/etc/chrome_dev.conf"
91_KILL_LACROS_CHROME_CMD = "pkill -f %(lacros_dir)s/chrome"
92_RESET_LACROS_CHROME_CMD = "rm -rf /home/chronos/user/lacros"
93MODIFIED_CONF_FILE = f"modified {_CONF_FILE}"
Erik Chen75a2f492020-08-06 19:15:11 -070094
95# This command checks if "--enable-features=LacrosSupport" is present in
96# /etc/chrome_dev.conf. If it is not, then it is added.
97# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
98# to modify chrome_dev.conf. Either revisit this policy or find another
99# mechanism to pass configuration to ash-chrome.
100ENABLE_LACROS_VIA_CONF_COMMAND = f"""
101 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
102 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
103 echo {MODIFIED_CONF_FILE};
104 fi
105"""
106
107# This command checks if "--lacros-chrome-path=" is present with the right value
108# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
109# and the new one is added.
110# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
111# to modify chrome_dev.conf. Either revisit this policy or find another
112# mechanism to pass configuration to ash-chrome.
113_SET_LACROS_PATH_VIA_CONF_COMMAND = """
114 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
115 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
116 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
117 echo %(modified_conf_file)s;
118 fi
119"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500120
Alex Klein1699fab2022-09-08 08:46:06 -0600121
Ryan Cui3045c5d2012-07-13 18:00:33 -0700122def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600123 """Return the last component of the URL."""
124 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700125
126
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700127class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600128 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800129
130
Ryan Cui7193a7e2013-04-26 14:15:19 -0700131DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600132 "DeviceInfo", ["target_dir_size", "target_fs_free"]
133)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700134
135
Ryan Cui3045c5d2012-07-13 18:00:33 -0700136class DeployChrome(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600137 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 def __init__(self, options, tempdir, staging_dir):
140 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700141
Alex Klein1699fab2022-09-08 08:46:06 -0600142 Args:
143 options: options object.
144 tempdir: Scratch space for the class. Caller has responsibility to clean
145 it up.
146 staging_dir: Directory to stage the files to.
147 """
148 self.tempdir = tempdir
149 self.options = options
150 self.staging_dir = staging_dir
151 if not self.options.staging_only:
152 hostname = options.device.hostname
153 port = options.device.port
154 self.device = remote.ChromiumOSDevice(
155 hostname,
156 port=port,
157 ping=options.ping,
158 private_key=options.private_key,
159 include_dev_paths=False,
160 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000161 if self._ShouldUseCompressedAsh():
162 self.options.compressed_ash = True
163
Alex Klein1699fab2022-09-08 08:46:06 -0600164 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 self._deployment_name = "lacros" if options.lacros else "chrome"
167 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700168
Alex Klein1699fab2022-09-08 08:46:06 -0600169 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 # Whether UI was stopped during setup.
172 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800173
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000174 def _ShouldUseCompressedAsh(self):
175 """Detects if the DUT uses compressed-ash setup."""
176 if self.options.lacros:
177 return False
178
179 return self.device.IfFileExists(COMPRESSED_ASH_PATH)
180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 def _GetRemoteMountFree(self, remote_dir):
182 result = self.device.run(DF_COMMAND % remote_dir)
183 line = result.stdout.splitlines()[1]
184 value = line.split()[3]
185 multipliers = {
186 "G": 1024 * 1024 * 1024,
187 "M": 1024 * 1024,
188 "K": 1024,
189 }
190 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700191
Alex Klein1699fab2022-09-08 08:46:06 -0600192 def _GetRemoteDirSize(self, remote_dir):
193 result = self.device.run(
194 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
195 )
196 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700197
Alex Klein1699fab2022-09-08 08:46:06 -0600198 def _GetStagingDirSize(self):
199 result = cros_build_lib.dbg_run(
200 ["du", "-ks", self.staging_dir],
201 capture_output=True,
202 encoding="utf-8",
203 )
204 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700205
Alex Klein1699fab2022-09-08 08:46:06 -0600206 def _ChromeFileInUse(self):
207 result = self.device.run(
208 LSOF_COMMAND_CHROME % (self.options.target_dir,),
209 check=False,
210 capture_output=True,
211 )
212 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700213
Alex Klein1699fab2022-09-08 08:46:06 -0600214 def _Reboot(self):
215 # A reboot in developer mode takes a while (and has delays), so the user
216 # will have time to read and act on the USB boot instructions below.
217 logging.info(
218 "Please remember to press Ctrl-U if you are booting from USB."
219 )
220 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600221
Alex Klein1699fab2022-09-08 08:46:06 -0600222 def _DisableRootfsVerification(self):
223 if not self.options.force:
224 logging.error(
225 "Detected that the device has rootfs verification enabled."
226 )
227 logging.info(
228 "This script can automatically remove the rootfs "
229 "verification, which requires it to reboot the device."
230 )
231 logging.info("Make sure the device is in developer mode!")
232 logging.info("Skip this prompt by specifying --force.")
233 if not cros_build_lib.BooleanPrompt(
234 "Remove rootfs verification?", False
235 ):
236 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700237
Alex Klein1699fab2022-09-08 08:46:06 -0600238 logging.info(
239 "Removing rootfs verification from %s", self.options.device
240 )
241 # Running in VMs cause make_dev_ssd's firmware confidence checks to fail.
242 # Use --force to bypass the checks.
243 cmd = (
244 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
245 "--remove_rootfs_verification --force"
246 )
247 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
248 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700249
Alex Klein1699fab2022-09-08 08:46:06 -0600250 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 # Now that the machine has been rebooted, we need to kill Chrome again.
253 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800254
Alex Klein1699fab2022-09-08 08:46:06 -0600255 # Make sure the rootfs is writable now.
256 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700257
Alex Klein1699fab2022-09-08 08:46:06 -0600258 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700259
Alex Klein1699fab2022-09-08 08:46:06 -0600260 def _CheckUiJobStarted(self):
261 # status output is in the format:
262 # <job_name> <status> ['process' <pid>].
263 # <status> is in the format <goal>/<state>.
264 try:
265 result = self.device.run(
266 "status ui", capture_output=True, encoding="utf-8"
267 )
268 except cros_build_lib.RunCommandError as e:
269 if "Unknown job" in e.stderr:
270 return False
271 else:
272 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700275
Alex Klein1699fab2022-09-08 08:46:06 -0600276 def _KillLacrosChrome(self):
277 """This method kills lacros-chrome on the device, if it's running."""
278 self.device.run(
279 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
280 check=False,
281 )
Erik Chen75a2f492020-08-06 19:15:11 -0700282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 def _ResetLacrosChrome(self):
284 """Reset Lacros to fresh state by deleting user data dir."""
285 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 def _KillAshChromeIfNeeded(self):
288 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700289
Alex Klein1699fab2022-09-08 08:46:06 -0600290 This method calls 'stop ui', and then also manually pkills both ash-chrome
291 and the session manager.
292 """
293 if self._CheckUiJobStarted():
294 logging.info("Shutting down Chrome...")
295 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700296
Alex Klein1699fab2022-09-08 08:46:06 -0600297 # Developers sometimes run session_manager manually, in which case we'll
298 # need to help shut the chrome processes down.
299 try:
300 with timeout_util.Timeout(self.options.process_timeout):
301 while self._ChromeFileInUse():
302 logging.warning(
303 "The chrome binary on the device is in use."
304 )
305 logging.warning(
306 "Killing chrome and session_manager processes...\n"
307 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 self.device.run(
310 "pkill 'chrome|session_manager'", check=False
311 )
312 # Wait for processes to actually terminate
313 time.sleep(POST_KILL_WAIT)
314 logging.info("Rechecking the chrome binary...")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000315 if self.options.compressed_ash:
316 result = self.device.run(
317 ["umount", RAW_ASH_PATH],
318 check=False,
319 capture_output=True,
320 )
321 if result.returncode and not (
322 result.returncode == 32
323 and "not mounted" in result.stderr
324 ):
325 raise DeployFailure(
326 "Could not unmount compressed ash. "
327 f"Error Code: {result.returncode}, "
328 f"Error Message: {result.stderr}"
329 )
Alex Klein1699fab2022-09-08 08:46:06 -0600330 except timeout_util.TimeoutError:
331 msg = (
332 "Could not kill processes after %s seconds. Please exit any "
333 "running chrome processes and try again."
334 % self.options.process_timeout
335 )
336 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700337
Alex Klein1699fab2022-09-08 08:46:06 -0600338 def _MountRootfsAsWritable(self, check=False):
339 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700340
Alex Klein1699fab2022-09-08 08:46:06 -0600341 If the command fails and the root dir is not writable then this function
342 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700343
Alex Klein1699fab2022-09-08 08:46:06 -0600344 Args:
345 check: See remote.RemoteAccess.RemoteSh for details.
346 """
347 # TODO: Should migrate to use the remount functions in remote_access.
348 result = self.device.run(
349 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
350 )
351 if result.returncode and not self.device.IsDirWritable("/"):
352 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700353
Alex Klein1699fab2022-09-08 08:46:06 -0600354 def _EnsureTargetDir(self):
355 """Ensures that the target directory exists on the remote device."""
356 target_dir = self.options.target_dir
357 # Any valid /opt directory should already exist so avoid the remote call.
358 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
359 return
360 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800361
Alex Klein1699fab2022-09-08 08:46:06 -0600362 def _GetDeviceInfo(self):
363 """Returns the disk space used and available for the target diectory."""
364 steps = [
365 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
366 functools.partial(
367 self._GetRemoteMountFree, self.options.target_dir
368 ),
369 ]
370 return_values = parallel.RunParallelSteps(steps, return_values=True)
371 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 def _CheckDeviceFreeSpace(self, device_info):
374 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700375
Alex Klein1699fab2022-09-08 08:46:06 -0600376 Args:
377 device_info: A DeviceInfo named tuple.
378 """
379 effective_free = (
380 device_info.target_dir_size + device_info.target_fs_free
381 )
382 staging_size = self._GetStagingDirSize()
383 if effective_free < staging_size:
384 raise DeployFailure(
385 "Not enough free space on the device. Required: %s MiB, "
386 "actual: %s MiB."
387 % (staging_size // 1024, effective_free // 1024)
388 )
389 if device_info.target_fs_free < (100 * 1024):
390 logging.warning(
391 "The device has less than 100MB free. deploy_chrome may "
392 "hang during the transfer."
393 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700394
Alex Klein1699fab2022-09-08 08:46:06 -0600395 def _ShouldUseCompression(self):
396 """Checks if compression should be used for rsync."""
397 if self.options.compress == "always":
398 return True
399 elif self.options.compress == "never":
400 return False
401 elif self.options.compress == "auto":
402 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900403
Alex Klein1699fab2022-09-08 08:46:06 -0600404 def _Deploy(self):
405 logging.info(
406 "Copying %s to %s on device...",
407 self._deployment_name,
408 self.options.target_dir,
409 )
410 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
411 # This does not work for deploy.
412 if not self.device.HasRsync():
413 raise DeployFailure(
414 "rsync is not found on the device.\n"
415 "Run dev_install on the device to get rsync installed."
416 )
417 self.device.CopyToDevice(
418 "%s/" % os.path.abspath(self.staging_dir),
419 self.options.target_dir,
420 mode="rsync",
421 inplace=True,
422 compress=self._ShouldUseCompression(),
423 debug_level=logging.INFO,
424 verbose=self.options.verbose,
425 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700426
Alex Klein1699fab2022-09-08 08:46:06 -0600427 # Set the security context on the default Chrome dir if that's where it's
428 # getting deployed, and only on SELinux supported devices.
429 if (
430 not self.options.lacros
431 and self.device.IsSELinuxAvailable()
432 and (
433 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
434 )
435 ):
436 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800437
Alex Klein1699fab2022-09-08 08:46:06 -0600438 for p in self.copy_paths:
439 if p.mode:
440 # Set mode if necessary.
441 self.device.run(
442 "chmod %o %s/%s"
443 % (
444 p.mode,
445 self.options.target_dir,
446 p.src if not p.dest else p.dest,
447 )
448 )
Steve Funge984a532013-11-25 17:09:25 -0800449
Alex Klein1699fab2022-09-08 08:46:06 -0600450 if self.options.lacros:
451 self.device.run(
452 ["chown", "-R", "chronos:chronos", self.options.target_dir]
453 )
Erik Chen75a2f492020-08-06 19:15:11 -0700454
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000455 if self.options.compressed_ash:
456 self.device.run(["start", COMPRESSED_ASH_SERVICE])
457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
459 # pick up major changes (bus type, logging, etc.), but all we care about is
460 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
461 # be authorized to take ownership of its service names.
462 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600463
Erik Chen75a2f492020-08-06 19:15:11 -0700464 if self.options.startui and self._stopped_ui:
Joel Hockey764728e2023-03-14 17:10:04 -0700465 last_login = self._GetLastLogin()
Alex Klein1699fab2022-09-08 08:46:06 -0600466 logging.info("Starting UI...")
467 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800468
Joel Hockey764728e2023-03-14 17:10:04 -0700469 if self.options.unlock_password:
470 logging.info("Unlocking...")
471
472 @retry_util.WithRetry(max_retry=5, sleep=1)
473 def WaitForUnlockScreen():
474 if self._GetLastLogin() == last_login:
475 raise DeployFailure("Unlock screen not shown")
476
477 WaitForUnlockScreen()
478 self.device.run(
479 UNLOCK_PASSWORD_COMMAND % self.options.unlock_password
480 )
481
482 def _GetLastLogin(self):
483 """Returns last login time"""
484 return self.device.run(LAST_LOGIN_COMMAND).stdout.strip()
485
Alex Klein1699fab2022-09-08 08:46:06 -0600486 def _DeployTestBinaries(self):
487 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 There could be several binaries located in the local build dir, so compare
490 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
491 over any that we also built ourselves.
492 """
493 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
494 if r.returncode != 0:
495 raise DeployFailure(
496 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
497 )
498 binaries_to_copy = []
499 for f in r.stdout.splitlines():
500 binaries_to_copy.append(
501 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
502 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700503
Alex Klein1699fab2022-09-08 08:46:06 -0600504 staging_dir = os.path.join(
505 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
506 )
507 _PrepareStagingDir(
508 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
509 )
510 # Deploying can occasionally run into issues with rsync getting a broken
511 # pipe, so retry several times. See crbug.com/1141618 for more
512 # information.
513 retry_util.RetryException(
514 None,
515 3,
516 self.device.CopyToDevice,
517 staging_dir,
518 os.path.dirname(_CHROME_TEST_BIN_DIR),
519 mode="rsync",
520 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700521
Alex Klein1699fab2022-09-08 08:46:06 -0600522 def _CheckConnection(self):
523 try:
524 logging.info("Testing connection to the device...")
525 self.device.run("true")
526 except cros_build_lib.RunCommandError as ex:
527 logging.error("Error connecting to the test device.")
528 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800529
Alex Klein1699fab2022-09-08 08:46:06 -0600530 def _CheckBoard(self):
531 """Check that the Chrome build is targeted for the device board."""
532 if self.options.board == self.device.board:
533 return
534 logging.warning(
535 "Device board is %s whereas target board is %s.",
536 self.device.board,
537 self.options.board,
538 )
539 if self.options.force:
540 return
541 if not cros_build_lib.BooleanPrompt(
542 "Continue despite board mismatch?", False
543 ):
544 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800545
Alex Klein1699fab2022-09-08 08:46:06 -0600546 def _CheckDeployType(self):
547 if self.options.build_dir:
548
549 def BinaryExists(filename):
550 """Checks if the passed-in file is present in the build directory."""
551 return os.path.exists(
552 os.path.join(self.options.build_dir, filename)
553 )
554
555 # In the future, lacros-chrome and ash-chrome will likely be named
556 # something other than 'chrome' to avoid confusion.
557 # Handle non-Chrome deployments.
558 if not BinaryExists("chrome"):
559 if BinaryExists("app_shell"):
560 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
561
562 def _PrepareStagingDir(self):
563 _PrepareStagingDir(
564 self.options,
565 self.tempdir,
566 self.staging_dir,
567 self.copy_paths,
568 self.chrome_dir,
569 )
570
571 def _MountTarget(self):
572 logging.info("Mounting Chrome...")
573
574 # Create directory if does not exist.
575 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
576 try:
577 # Umount the existing mount on mount_dir if present first.
578 self.device.run(
579 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
580 )
581 except cros_build_lib.RunCommandError as e:
582 logging.error("Failed to umount %s", self.options.mount_dir)
583 # If there is a failure, check if some processs is using the mount_dir.
584 result = self.device.run(
585 LSOF_COMMAND % (self.options.mount_dir,),
586 check=False,
587 capture_output=True,
588 encoding="utf-8",
589 )
590 logging.error("lsof %s -->", self.options.mount_dir)
591 logging.error(result.stdout)
592 raise e
593
594 self.device.run(
595 _BIND_TO_FINAL_DIR_CMD
596 % (self.options.target_dir, self.options.mount_dir)
597 )
598
599 # Chrome needs partition to have exec and suid flags set
600 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
601
602 def Cleanup(self):
603 """Clean up RemoteDevice."""
604 if not self.options.staging_only:
605 self.device.Cleanup()
606
607 def Perform(self):
608 self._CheckDeployType()
609
610 # If requested, just do the staging step.
611 if self.options.staging_only:
612 self._PrepareStagingDir()
613 return 0
614
615 # Check that the build matches the device. Lacros-chrome skips this check as
616 # it's currently board independent. This means that it's possible to deploy
617 # a build of lacros-chrome with a mismatched architecture. We don't try to
618 # prevent this developer foot-gun.
619 if not self.options.lacros:
620 self._CheckBoard()
621
622 # Ensure that the target directory exists before running parallel steps.
623 self._EnsureTargetDir()
624
625 logging.info("Preparing device")
626 steps = [
627 self._GetDeviceInfo,
628 self._CheckConnection,
629 self._MountRootfsAsWritable,
630 self._PrepareStagingDir,
631 ]
632
633 restart_ui = True
634 if self.options.lacros:
635 # If this is a lacros build, we only want to restart ash-chrome if needed.
636 restart_ui = False
637 steps.append(self._KillLacrosChrome)
638 if self.options.reset_lacros:
639 steps.append(self._ResetLacrosChrome)
640 if self.options.modify_config_file:
641 restart_ui = self._ModifyConfigFileIfNeededForLacros()
642
643 if restart_ui:
644 steps.append(self._KillAshChromeIfNeeded)
645 self._stopped_ui = True
646
647 ret = parallel.RunParallelSteps(
648 steps, halt_on_error=True, return_values=True
649 )
650 self._CheckDeviceFreeSpace(ret[0])
651
652 # If the root dir is not writable, try disabling rootfs verification.
653 # (We always do this by default so that developers can write to
654 # /etc/chriome_dev.conf and other directories in the rootfs).
655 if self._root_dir_is_still_readonly.is_set():
656 if self.options.noremove_rootfs_verification:
657 logging.warning("Skipping disable rootfs verification.")
658 elif not self._DisableRootfsVerification():
659 logging.warning("Failed to disable rootfs verification.")
660
661 # If the target dir is still not writable (i.e. the user opted out or the
662 # command failed), abort.
663 if not self.device.IsDirWritable(self.options.target_dir):
664 if self.options.startui and self._stopped_ui:
665 logging.info("Restarting Chrome...")
666 self.device.run("start ui")
667 raise DeployFailure(
668 "Target location is not writable. Aborting."
669 )
670
671 if self.options.mount_dir is not None:
672 self._MountTarget()
673
674 # Actually deploy Chrome to the device.
675 self._Deploy()
676 if self.options.deploy_test_binaries:
677 self._DeployTestBinaries()
678
679 def _ModifyConfigFileIfNeededForLacros(self):
680 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
681
682 Returns:
683 True if the file is modified, and the return value is usually used to
684 determine whether restarting ash-chrome is needed.
685 """
686 assert (
687 self.options.lacros
688 ), "Only deploying lacros-chrome needs to modify the config file."
689 # Update /etc/chrome_dev.conf to include appropriate flags.
690 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000691 if self.options.enable_lacros_support:
692 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
693 if result.stdout.strip() == MODIFIED_CONF_FILE:
694 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600695 result = self.device.run(
696 _SET_LACROS_PATH_VIA_CONF_COMMAND
697 % {
698 "conf_file": _CONF_FILE,
699 "lacros_path": self.options.target_dir,
700 "modified_conf_file": MODIFIED_CONF_FILE,
701 },
702 shell=True,
703 )
704 if result.stdout.strip() == MODIFIED_CONF_FILE:
705 modified = True
706
707 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800708
709
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700710def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600711 """Convert formatted string to dictionary."""
712 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700713
714
Steven Bennetts368c3e52016-09-23 13:05:21 -0700715def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600716 """Convert GN_ARGS-formatted string to dictionary."""
717 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700718
719
Ryan Cuie535b172012-10-19 18:25:03 -0700720def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600721 """Create our custom parser."""
722 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700723
Alex Klein1699fab2022-09-08 08:46:06 -0600724 # TODO(rcui): Have this use the UI-V2 format of having source and target
725 # device be specified as positional arguments.
726 parser.add_argument(
727 "--force",
728 action="store_true",
729 default=False,
730 help="Skip all prompts (such as the prompt for disabling "
731 "of rootfs verification). This may result in the "
732 "target machine being rebooted.",
733 )
734 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
735 parser.add_argument(
736 "--board",
737 default=sdk_board_env,
738 help="The board the Chrome build is targeted for. When "
739 "in a 'cros chrome-sdk' shell, defaults to the SDK "
740 "board.",
741 )
742 parser.add_argument(
743 "--build-dir",
744 type="path",
745 help="The directory with Chrome build artifacts to "
746 "deploy from. Typically of format "
747 "<chrome_root>/out/Debug. When this option is used, "
748 "the GN_ARGS environment variable must be set.",
749 )
750 parser.add_argument(
751 "--target-dir",
752 type="path",
753 default=None,
754 help="Target directory on device to deploy Chrome into.",
755 )
756 parser.add_argument(
757 "-g",
758 "--gs-path",
759 type="gs_path",
760 help="GS path that contains the chrome to deploy.",
761 )
762 parser.add_argument(
763 "--private-key",
764 type="path",
765 default=None,
766 help="An ssh private key to use when deploying to " "a CrOS device.",
767 )
768 parser.add_argument(
769 "--nostartui",
770 action="store_false",
771 dest="startui",
772 default=True,
773 help="Don't restart the ui daemon after deployment.",
774 )
775 parser.add_argument(
Joel Hockey764728e2023-03-14 17:10:04 -0700776 "--unlock-password",
777 default=None,
778 help="Password to use to unlock after deployment and restart.",
779 )
780 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600781 "--nostrip",
782 action="store_false",
783 dest="dostrip",
784 default=True,
785 help="Don't strip binaries during deployment. Warning: "
786 "the resulting binaries will be very large!",
787 )
788 parser.add_argument(
789 "-d",
790 "--device",
791 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
792 help="Device hostname or IP in the format hostname[:port].",
793 )
794 parser.add_argument(
795 "--mount-dir",
796 type="path",
797 default=None,
798 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000799 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600800 "Any existing mount on this directory will be "
801 "umounted first.",
802 )
803 parser.add_argument(
804 "--mount",
805 action="store_true",
806 default=False,
807 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000808 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600809 "Any existing mount on this directory will be "
810 "umounted first.",
811 )
812 parser.add_argument(
813 "--noremove-rootfs-verification",
814 action="store_true",
815 default=False,
816 help="Never remove rootfs verification.",
817 )
818 parser.add_argument(
819 "--deploy-test-binaries",
820 action="store_true",
821 default=False,
822 help="Also deploy any test binaries to %s. Useful for "
823 "running any Tast tests that execute these "
824 "binaries." % _CHROME_TEST_BIN_DIR,
825 )
826 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600827 "--use-external-config",
828 action="store_true",
829 help="When identifying the configuration for a board, "
830 "force usage of the external configuration if both "
831 "internal and external are available. This only "
832 "has an effect when stripping Chrome, i.e. when "
833 "--nostrip is not passed in.",
834 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700835
Georg Neis9b1ff192022-09-14 08:07:22 +0000836 group = parser.add_argument_group("Lacros Options")
837 group.add_argument(
838 "--lacros",
839 action="store_true",
840 default=False,
841 help="Deploys lacros-chrome rather than ash-chrome.",
842 )
843 group.add_argument(
844 "--reset-lacros",
845 action="store_true",
846 default=False,
847 help="Reset Lacros by deleting Lacros user data dir if it exists.",
848 )
849 group.add_argument(
850 "--skip-enabling-lacros-support",
851 action="store_false",
852 dest="enable_lacros_support",
853 help="By default, deploying lacros-chrome modifies the "
854 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
855 "and (2) set the Lacros path, which can interfere with automated "
856 "testing. With this flag, part (1) will be skipped. See the "
857 "--skip-modifying-config-file flag for skipping both parts.",
858 )
859 group.add_argument(
860 "--skip-modifying-config-file",
861 action="store_false",
862 dest="modify_config_file",
863 help="When deploying lacros-chrome, do not modify the "
864 "/etc/chrome_dev.conf file. See also the "
865 "--skip-enabling-lacros-support flag.",
866 )
867
Alex Klein1699fab2022-09-08 08:46:06 -0600868 group = parser.add_argument_group("Advanced Options")
869 group.add_argument(
870 "-l",
871 "--local-pkg-path",
872 type="path",
873 help="Path to local chrome prebuilt package to deploy.",
874 )
875 group.add_argument(
876 "--sloppy",
877 action="store_true",
878 default=False,
879 help="Ignore when mandatory artifacts are missing.",
880 )
881 group.add_argument(
882 "--staging-flags",
883 default=None,
884 type=ValidateStagingFlags,
885 help=(
886 "Extra flags to control staging. Valid flags are - "
887 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
888 ),
889 )
890 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
891 group.add_argument(
892 "--strict",
893 action="store_true",
894 default=False,
895 help='Deprecated. Default behavior is "strict". Use '
896 "--sloppy to omit warnings for missing optional "
897 "files.",
898 )
899 group.add_argument(
900 "--strip-flags",
901 default=None,
902 help="Flags to call the 'strip' binutil tool with. "
903 "Overrides the default arguments.",
904 )
905 group.add_argument(
906 "--ping",
907 action="store_true",
908 default=False,
909 help="Ping the device before connection attempt.",
910 )
911 group.add_argument(
912 "--process-timeout",
913 type=int,
914 default=KILL_PROC_MAX_WAIT,
915 help="Timeout for process shutdown.",
916 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700917
Alex Klein1699fab2022-09-08 08:46:06 -0600918 group = parser.add_argument_group(
919 "Metadata Overrides (Advanced)",
920 description="Provide all of these overrides in order to remove "
921 "dependencies on metadata.json existence.",
922 )
923 group.add_argument(
924 "--target-tc",
925 action="store",
926 default=None,
927 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
928 )
929 group.add_argument(
930 "--toolchain-url",
931 action="store",
932 default=None,
933 help="Override toolchain url format pattern, e.g. "
934 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
935 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700936
Alex Klein1699fab2022-09-08 08:46:06 -0600937 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
938 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
939 parser.add_argument(
940 "--gyp-defines",
941 default=None,
942 type=ValidateStagingFlags,
943 help=argparse.SUPPRESS,
944 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700945
Alex Klein1699fab2022-09-08 08:46:06 -0600946 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
947 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
948 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
949 parser.add_argument(
950 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
951 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700952
Alex Klein1699fab2022-09-08 08:46:06 -0600953 # Path of an empty directory to stage chrome artifacts to. Defaults to a
954 # temporary directory that is removed when the script finishes. If the path
955 # is specified, then it will not be removed.
956 parser.add_argument(
957 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
958 )
959 # Only prepare the staging directory, and skip deploying to the device.
960 parser.add_argument(
961 "--staging-only",
962 action="store_true",
963 default=False,
964 help=argparse.SUPPRESS,
965 )
966 # Uploads the compressed staging directory to the given gs:// path URI.
967 parser.add_argument(
968 "--staging-upload",
969 type="gs_path",
970 help="GS path to upload the compressed staging files to.",
971 )
972 # Used alongside --staging-upload to upload with public-read ACL.
973 parser.add_argument(
974 "--public-read",
975 action="store_true",
976 default=False,
977 help="GS path to upload the compressed staging files to.",
978 )
979 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
980 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
981 # fetching the SDK toolchain.
982 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
983 parser.add_argument(
984 "--compress",
985 action="store",
986 default="auto",
987 choices=("always", "never", "auto"),
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000988 help="Choose the data transfer compression behavior. Default "
Alex Klein1699fab2022-09-08 08:46:06 -0600989 'is set to "auto", that disables compression if '
990 "the target device has a gigabit ethernet port.",
991 )
Daniil Lunev0c4f65c2022-09-19 11:01:34 +1000992 parser.add_argument(
993 "--compressed-ash",
994 action="store_true",
995 default=False,
996 help="Use compressed-ash deployment scheme. With the flag, ash-chrome "
997 "binary is stored on DUT in squashfs, mounted upon boot.",
998 )
Alex Klein1699fab2022-09-08 08:46:06 -0600999 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -07001000
Ryan Cuie535b172012-10-19 18:25:03 -07001001
1002def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001003 """Parse args, and run environment-independent checks."""
1004 parser = _CreateParser()
1005 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001006
Alex Klein1699fab2022-09-08 08:46:06 -06001007 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
1008 parser.error(
1009 "Need to specify either --gs-path, --local-pkg-path, or "
1010 "--build-dir"
1011 )
1012 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
1013 parser.error(
1014 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
1015 )
1016 if options.lacros:
1017 if options.dostrip and not options.board:
1018 parser.error("Please specify --board.")
1019 if options.mount_dir or options.mount:
1020 parser.error("--lacros does not support --mount or --mount-dir")
1021 if options.deploy_test_binaries:
1022 parser.error("--lacros does not support --deploy-test-binaries")
1023 if options.local_pkg_path:
1024 parser.error("--lacros does not support --local-pkg-path")
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001025 if options.compressed_ash:
1026 parser.error("--lacros does not support --compressed-ash")
Alex Klein1699fab2022-09-08 08:46:06 -06001027 else:
1028 if not options.board and options.build_dir:
1029 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
1030 if match:
1031 options.board = match.group(1)
1032 logging.info("--board is set to %s", options.board)
1033 if not options.board:
1034 parser.error("--board is required")
1035 if options.gs_path and options.local_pkg_path:
1036 parser.error("Cannot specify both --gs-path and --local-pkg-path")
1037 if not (options.staging_only or options.device):
1038 parser.error("Need to specify --device")
1039 if options.staging_flags and not options.build_dir:
1040 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -07001041
Alex Klein1699fab2022-09-08 08:46:06 -06001042 if options.strict:
1043 logging.warning("--strict is deprecated.")
1044 if options.gyp_defines:
1045 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -07001046
Alex Klein1699fab2022-09-08 08:46:06 -06001047 if options.mount or options.mount_dir:
1048 if not options.target_dir:
1049 options.target_dir = _CHROME_DIR_MOUNT
1050 else:
1051 if not options.target_dir:
1052 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001053
Alex Klein1699fab2022-09-08 08:46:06 -06001054 if options.mount and not options.mount_dir:
1055 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -07001056
Alex Klein1699fab2022-09-08 08:46:06 -06001057 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -07001058
1059
Mike Frysingerc3061a62015-06-04 04:16:18 -04001060def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001061 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -07001062
Alex Klein1699fab2022-09-08 08:46:06 -06001063 Args:
1064 options: The options object returned by the cli parser.
1065 """
1066 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
1067 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -07001068
Alex Klein1699fab2022-09-08 08:46:06 -06001069 if not options.gn_args:
1070 gn_env = os.getenv("GN_ARGS")
1071 if gn_env is not None:
1072 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1073 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001074
Alex Klein1699fab2022-09-08 08:46:06 -06001075 if not options.staging_flags:
1076 use_env = os.getenv("USE")
1077 if use_env is not None:
1078 options.staging_flags = " ".join(
1079 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1080 )
1081 logging.info(
1082 "Staging flags taken from USE in environment: %s",
1083 options.staging_flags,
1084 )
Steven Bennetts60600462016-05-12 10:40:20 -07001085
Ryan Cuia56a71e2012-10-18 18:40:35 -07001086
Ryan Cui504db722013-01-22 11:48:01 -08001087def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001088 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001089
Alex Klein1699fab2022-09-08 08:46:06 -06001090 Returns:
1091 Path to the fetched chrome tarball.
1092 """
1093 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1094 files = gs_ctx.LS(gs_path)
1095 files = [
1096 found
1097 for found in files
1098 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1099 ]
1100 if not files:
1101 raise Exception("No chrome package found at %s" % gs_path)
1102 elif len(files) > 1:
1103 # - Users should provide us with a direct link to either a stripped or
1104 # unstripped chrome package.
1105 # - In the case of being provided with an archive directory, where both
1106 # stripped and unstripped chrome available, use the stripped chrome
1107 # package.
1108 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
1109 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
1110 files = [f for f in files if not "unstripped" in f]
1111 assert len(files) == 1
1112 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001113
Alex Klein1699fab2022-09-08 08:46:06 -06001114 filename = _UrlBaseName(files[0])
1115 logging.info("Fetching %s...", filename)
1116 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1117 chrome_path = os.path.join(tempdir, filename)
1118 assert os.path.exists(chrome_path)
1119 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001120
1121
Ryan Cuif890a3e2013-03-07 18:57:06 -08001122@contextlib.contextmanager
1123def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001124 if not options.dostrip:
1125 yield None
1126 elif options.strip_bin:
1127 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001128 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001129 sdk = cros_chrome_sdk.SDKFetcher(
1130 options.cache_dir,
1131 options.board,
1132 use_external_config=options.use_external_config,
1133 )
1134 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1135 with sdk.Prepare(
1136 components=components,
1137 target_tc=options.target_tc,
1138 toolchain_url=options.toolchain_url,
1139 ) as ctx:
1140 env_path = os.path.join(
1141 ctx.key_map[constants.CHROME_ENV_TAR].path,
1142 constants.CHROME_ENV_FILE,
1143 )
1144 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1145 strip_bin = os.path.join(
1146 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1147 "bin",
1148 os.path.basename(strip_bin),
1149 )
1150 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001151
Alex Klein1699fab2022-09-08 08:46:06 -06001152
1153def _UploadStagingDir(
1154 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1155) -> None:
1156 """Uploads the compressed staging directory.
1157
1158 Args:
1159 options: options object.
1160 tempdir: Scratch space.
1161 staging_dir: Directory staging chrome files.
1162 """
1163 staging_tarball_path = os.path.join(
1164 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1165 )
1166 logging.info(
1167 "Compressing staging dir (%s) to (%s)",
1168 staging_dir,
1169 staging_tarball_path,
1170 )
1171 cros_build_lib.CreateTarball(
1172 staging_tarball_path,
1173 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001174 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001175 extra_env={"ZSTD_CLEVEL": "9"},
1176 )
1177 logging.info(
1178 "Uploading staging tarball (%s) into %s",
1179 staging_tarball_path,
1180 options.staging_upload,
1181 )
1182 ctx = gs.GSContext()
1183 ctx.Copy(
1184 staging_tarball_path,
1185 options.staging_upload,
1186 acl="public-read" if options.public_read else "",
1187 )
1188
1189
1190def _PrepareStagingDir(
1191 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1192):
1193 """Place the necessary files in the staging directory.
1194
1195 The staging directory is the directory used to rsync the build artifacts over
1196 to the device. Only the necessary Chrome build artifacts are put into the
1197 staging directory.
1198 """
1199 if chrome_dir is None:
1200 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1201 osutils.SafeMakedirs(staging_dir)
1202 os.chmod(staging_dir, 0o755)
1203 if options.build_dir:
1204 with _StripBinContext(options) as strip_bin:
1205 strip_flags = (
1206 None
1207 if options.strip_flags is None
1208 else shlex.split(options.strip_flags)
1209 )
1210 chrome_util.StageChromeFromBuildDir(
1211 staging_dir,
1212 options.build_dir,
1213 strip_bin,
1214 sloppy=options.sloppy,
1215 gn_args=options.gn_args,
1216 staging_flags=options.staging_flags,
1217 strip_flags=strip_flags,
1218 copy_paths=copy_paths,
1219 )
1220 else:
1221 pkg_path = options.local_pkg_path
1222 if options.gs_path:
1223 pkg_path = _FetchChromePackage(
1224 options.cache_dir, tempdir, options.gs_path
1225 )
1226
1227 assert pkg_path
1228 logging.info("Extracting %s...", pkg_path)
1229 # Extract only the ./opt/google/chrome contents, directly into the staging
1230 # dir, collapsing the directory hierarchy.
1231 if pkg_path[-4:] == ".zip":
1232 cros_build_lib.dbg_run(
1233 [
1234 "unzip",
1235 "-X",
1236 pkg_path,
1237 _ANDROID_DIR_EXTRACT_PATH,
1238 "-d",
1239 staging_dir,
1240 ]
1241 )
1242 for filename in glob.glob(
1243 os.path.join(staging_dir, "system/chrome/*")
1244 ):
1245 shutil.move(filename, staging_dir)
1246 osutils.RmDir(
1247 os.path.join(staging_dir, "system"), ignore_missing=True
1248 )
1249 else:
1250 compression = cros_build_lib.CompressionDetectType(pkg_path)
1251 compressor = cros_build_lib.FindCompressor(compression)
1252 cros_build_lib.dbg_run(
1253 [
1254 "tar",
1255 "--strip-components",
1256 "4",
1257 "--extract",
1258 "-I",
1259 compressor,
1260 "--preserve-permissions",
1261 "--file",
1262 pkg_path,
1263 ".%s" % chrome_dir,
1264 ],
1265 cwd=staging_dir,
1266 )
1267
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001268 if options.compressed_ash:
1269 # Setup SDK here so mksquashfs is still found in no-shell + nostrip
1270 # configuration.
Daniil Luneve2954832022-10-11 11:38:51 +11001271 # HACH(b/247397013, dlunev): to not setup release builders for SDK while
1272 # this is in test, cut the known suffix of experimental overlays.
1273 sdk_orig_board = options.board
1274 if sdk_orig_board.endswith(COMPRESSED_ASH_OVERLAY_SUFFIX):
1275 sdk_orig_board = sdk_orig_board[
1276 : -len(COMPRESSED_ASH_OVERLAY_SUFFIX)
1277 ]
1278
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001279 sdk = cros_chrome_sdk.SDKFetcher(
1280 options.cache_dir,
Daniil Luneve2954832022-10-11 11:38:51 +11001281 sdk_orig_board,
Daniil Lunev0c4f65c2022-09-19 11:01:34 +10001282 use_external_config=options.use_external_config,
1283 )
1284 with sdk.Prepare(
1285 components=[],
1286 target_tc=options.target_tc,
1287 toolchain_url=options.toolchain_url,
1288 ):
1289 cros_build_lib.dbg_run(
1290 [
1291 "mksquashfs",
1292 RAW_ASH_FILE,
1293 COMPRESSED_ASH_FILE,
1294 "-all-root",
1295 "-no-progress",
1296 "-comp",
1297 "zstd",
1298 ],
1299 cwd=staging_dir,
1300 )
1301 os.truncate(os.path.join(staging_dir, RAW_ASH_FILE), 0)
1302
Alex Klein1699fab2022-09-08 08:46:06 -06001303 if options.staging_upload:
1304 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001305
Ryan Cui71aa8de2013-04-19 16:12:55 -07001306
Ryan Cui3045c5d2012-07-13 18:00:33 -07001307def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001308 options = _ParseCommandLine(argv)
1309 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001310
Alex Klein1699fab2022-09-08 08:46:06 -06001311 with osutils.TempDir(set_global=True) as tempdir:
1312 staging_dir = options.staging_dir
1313 if not staging_dir:
1314 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001315
Alex Klein1699fab2022-09-08 08:46:06 -06001316 deploy = DeployChrome(options, tempdir, staging_dir)
1317 try:
1318 deploy.Perform()
1319 except failures_lib.StepFailure as ex:
1320 raise SystemExit(str(ex).strip())
1321 deploy.Cleanup()