blob: 64b368484f949a728fa667a891d856ad8a5c7015 [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
Alex Klein1699fab2022-09-08 08:46:06 -060078LACROS_DIR = "/usr/local/lacros-chrome"
79_CONF_FILE = "/etc/chrome_dev.conf"
80_KILL_LACROS_CHROME_CMD = "pkill -f %(lacros_dir)s/chrome"
81_RESET_LACROS_CHROME_CMD = "rm -rf /home/chronos/user/lacros"
82MODIFIED_CONF_FILE = f"modified {_CONF_FILE}"
Erik Chen75a2f492020-08-06 19:15:11 -070083
84# This command checks if "--enable-features=LacrosSupport" is present in
85# /etc/chrome_dev.conf. If it is not, then it is added.
86# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
87# to modify chrome_dev.conf. Either revisit this policy or find another
88# mechanism to pass configuration to ash-chrome.
89ENABLE_LACROS_VIA_CONF_COMMAND = f"""
90 if ! grep -q "^--enable-features=LacrosSupport$" {_CONF_FILE}; then
91 echo "--enable-features=LacrosSupport" >> {_CONF_FILE};
92 echo {MODIFIED_CONF_FILE};
93 fi
94"""
95
96# This command checks if "--lacros-chrome-path=" is present with the right value
97# in /etc/chrome_dev.conf. If it is not, then all previous instances are removed
98# and the new one is added.
99# TODO(https://crbug.com/1112493): Automated scripts are currently not allowed
100# to modify chrome_dev.conf. Either revisit this policy or find another
101# mechanism to pass configuration to ash-chrome.
102_SET_LACROS_PATH_VIA_CONF_COMMAND = """
103 if ! grep -q "^--lacros-chrome-path=%(lacros_path)s$" %(conf_file)s; then
104 sed 's/--lacros-chrome-path/#--lacros-chrome-path/' %(conf_file)s;
105 echo "--lacros-chrome-path=%(lacros_path)s" >> %(conf_file)s;
106 echo %(modified_conf_file)s;
107 fi
108"""
Mike Frysingere65f3752014-12-08 00:46:39 -0500109
Alex Klein1699fab2022-09-08 08:46:06 -0600110
Ryan Cui3045c5d2012-07-13 18:00:33 -0700111def _UrlBaseName(url):
Alex Klein1699fab2022-09-08 08:46:06 -0600112 """Return the last component of the URL."""
113 return url.rstrip("/").rpartition("/")[-1]
Ryan Cui3045c5d2012-07-13 18:00:33 -0700114
115
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700116class DeployFailure(failures_lib.StepFailure):
Alex Klein1699fab2022-09-08 08:46:06 -0600117 """Raised whenever the deploy fails."""
David James88e6f032013-03-02 08:13:20 -0800118
119
Ryan Cui7193a7e2013-04-26 14:15:19 -0700120DeviceInfo = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -0600121 "DeviceInfo", ["target_dir_size", "target_fs_free"]
122)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700123
124
Ryan Cui3045c5d2012-07-13 18:00:33 -0700125class DeployChrome(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600126 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 def __init__(self, options, tempdir, staging_dir):
129 """Initialize the class.
Ryan Cuie535b172012-10-19 18:25:03 -0700130
Alex Klein1699fab2022-09-08 08:46:06 -0600131 Args:
132 options: options object.
133 tempdir: Scratch space for the class. Caller has responsibility to clean
134 it up.
135 staging_dir: Directory to stage the files to.
136 """
137 self.tempdir = tempdir
138 self.options = options
139 self.staging_dir = staging_dir
140 if not self.options.staging_only:
141 hostname = options.device.hostname
142 port = options.device.port
143 self.device = remote.ChromiumOSDevice(
144 hostname,
145 port=port,
146 ping=options.ping,
147 private_key=options.private_key,
148 include_dev_paths=False,
149 )
150 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000151
Alex Klein1699fab2022-09-08 08:46:06 -0600152 self._deployment_name = "lacros" if options.lacros else "chrome"
153 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
Erik Chen75a2f492020-08-06 19:15:11 -0700154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
Erik Chen75a2f492020-08-06 19:15:11 -0700156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 # Whether UI was stopped during setup.
158 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800159
Alex Klein1699fab2022-09-08 08:46:06 -0600160 def _GetRemoteMountFree(self, remote_dir):
161 result = self.device.run(DF_COMMAND % remote_dir)
162 line = result.stdout.splitlines()[1]
163 value = line.split()[3]
164 multipliers = {
165 "G": 1024 * 1024 * 1024,
166 "M": 1024 * 1024,
167 "K": 1024,
168 }
169 return int(value.rstrip("GMK")) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 def _GetRemoteDirSize(self, remote_dir):
172 result = self.device.run(
173 "du -ks %s" % remote_dir, capture_output=True, encoding="utf-8"
174 )
175 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 def _GetStagingDirSize(self):
178 result = cros_build_lib.dbg_run(
179 ["du", "-ks", self.staging_dir],
180 capture_output=True,
181 encoding="utf-8",
182 )
183 return int(result.stdout.split()[0])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700184
Alex Klein1699fab2022-09-08 08:46:06 -0600185 def _ChromeFileInUse(self):
186 result = self.device.run(
187 LSOF_COMMAND_CHROME % (self.options.target_dir,),
188 check=False,
189 capture_output=True,
190 )
191 return result.returncode == 0
Ryan Cui3045c5d2012-07-13 18:00:33 -0700192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 def _Reboot(self):
194 # A reboot in developer mode takes a while (and has delays), so the user
195 # will have time to read and act on the USB boot instructions below.
196 logging.info(
197 "Please remember to press Ctrl-U if you are booting from USB."
198 )
199 self.device.Reboot()
Justin TerAvestfac210e2017-04-13 11:39:00 -0600200
Alex Klein1699fab2022-09-08 08:46:06 -0600201 def _DisableRootfsVerification(self):
202 if not self.options.force:
203 logging.error(
204 "Detected that the device has rootfs verification enabled."
205 )
206 logging.info(
207 "This script can automatically remove the rootfs "
208 "verification, which requires it to reboot the device."
209 )
210 logging.info("Make sure the device is in developer mode!")
211 logging.info("Skip this prompt by specifying --force.")
212 if not cros_build_lib.BooleanPrompt(
213 "Remove rootfs verification?", False
214 ):
215 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700216
Alex Klein1699fab2022-09-08 08:46:06 -0600217 logging.info(
218 "Removing rootfs verification from %s", self.options.device
219 )
220 # Running in VMs cause make_dev_ssd's firmware confidence checks to fail.
221 # Use --force to bypass the checks.
222 cmd = (
223 "/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d "
224 "--remove_rootfs_verification --force"
225 )
226 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
227 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700228
Alex Klein1699fab2022-09-08 08:46:06 -0600229 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700230
Alex Klein1699fab2022-09-08 08:46:06 -0600231 # Now that the machine has been rebooted, we need to kill Chrome again.
232 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800233
Alex Klein1699fab2022-09-08 08:46:06 -0600234 # Make sure the rootfs is writable now.
235 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 return True
Steven Bennettsca73efa2018-07-10 13:36:56 -0700238
Alex Klein1699fab2022-09-08 08:46:06 -0600239 def _CheckUiJobStarted(self):
240 # status output is in the format:
241 # <job_name> <status> ['process' <pid>].
242 # <status> is in the format <goal>/<state>.
243 try:
244 result = self.device.run(
245 "status ui", capture_output=True, encoding="utf-8"
246 )
247 except cros_build_lib.RunCommandError as e:
248 if "Unknown job" in e.stderr:
249 return False
250 else:
251 raise e
Ryan Cuif2d1a582013-02-19 14:08:13 -0800252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 return result.stdout.split()[1].split("/")[0] == "start"
Ryan Cui3045c5d2012-07-13 18:00:33 -0700254
Alex Klein1699fab2022-09-08 08:46:06 -0600255 def _KillLacrosChrome(self):
256 """This method kills lacros-chrome on the device, if it's running."""
257 self.device.run(
258 _KILL_LACROS_CHROME_CMD % {"lacros_dir": self.options.target_dir},
259 check=False,
260 )
Erik Chen75a2f492020-08-06 19:15:11 -0700261
Alex Klein1699fab2022-09-08 08:46:06 -0600262 def _ResetLacrosChrome(self):
263 """Reset Lacros to fresh state by deleting user data dir."""
264 self.device.run(_RESET_LACROS_CHROME_CMD, check=False)
Sven Zheng92eb66e2022-02-03 21:23:51 +0000265
Alex Klein1699fab2022-09-08 08:46:06 -0600266 def _KillAshChromeIfNeeded(self):
267 """This method kills ash-chrome on the device, if it's running.
Erik Chen75a2f492020-08-06 19:15:11 -0700268
Alex Klein1699fab2022-09-08 08:46:06 -0600269 This method calls 'stop ui', and then also manually pkills both ash-chrome
270 and the session manager.
271 """
272 if self._CheckUiJobStarted():
273 logging.info("Shutting down Chrome...")
274 self.device.run("stop ui")
Ryan Cui3045c5d2012-07-13 18:00:33 -0700275
Alex Klein1699fab2022-09-08 08:46:06 -0600276 # Developers sometimes run session_manager manually, in which case we'll
277 # need to help shut the chrome processes down.
278 try:
279 with timeout_util.Timeout(self.options.process_timeout):
280 while self._ChromeFileInUse():
281 logging.warning(
282 "The chrome binary on the device is in use."
283 )
284 logging.warning(
285 "Killing chrome and session_manager processes...\n"
286 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 self.device.run(
289 "pkill 'chrome|session_manager'", check=False
290 )
291 # Wait for processes to actually terminate
292 time.sleep(POST_KILL_WAIT)
293 logging.info("Rechecking the chrome binary...")
294 except timeout_util.TimeoutError:
295 msg = (
296 "Could not kill processes after %s seconds. Please exit any "
297 "running chrome processes and try again."
298 % self.options.process_timeout
299 )
300 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 def _MountRootfsAsWritable(self, check=False):
303 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700304
Alex Klein1699fab2022-09-08 08:46:06 -0600305 If the command fails and the root dir is not writable then this function
306 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 Args:
309 check: See remote.RemoteAccess.RemoteSh for details.
310 """
311 # TODO: Should migrate to use the remount functions in remote_access.
312 result = self.device.run(
313 MOUNT_RW_COMMAND, check=check, capture_output=True, encoding="utf-8"
314 )
315 if result.returncode and not self.device.IsDirWritable("/"):
316 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 def _EnsureTargetDir(self):
319 """Ensures that the target directory exists on the remote device."""
320 target_dir = self.options.target_dir
321 # Any valid /opt directory should already exist so avoid the remote call.
322 if os.path.commonprefix([target_dir, "/opt"]) == "/opt":
323 return
324 self.device.run(["mkdir", "-p", "--mode", "0775", target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800325
Alex Klein1699fab2022-09-08 08:46:06 -0600326 def _GetDeviceInfo(self):
327 """Returns the disk space used and available for the target diectory."""
328 steps = [
329 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
330 functools.partial(
331 self._GetRemoteMountFree, self.options.target_dir
332 ),
333 ]
334 return_values = parallel.RunParallelSteps(steps, return_values=True)
335 return DeviceInfo(*return_values)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700336
Alex Klein1699fab2022-09-08 08:46:06 -0600337 def _CheckDeviceFreeSpace(self, device_info):
338 """See if target device has enough space for Chrome.
Ryan Cui7193a7e2013-04-26 14:15:19 -0700339
Alex Klein1699fab2022-09-08 08:46:06 -0600340 Args:
341 device_info: A DeviceInfo named tuple.
342 """
343 effective_free = (
344 device_info.target_dir_size + device_info.target_fs_free
345 )
346 staging_size = self._GetStagingDirSize()
347 if effective_free < staging_size:
348 raise DeployFailure(
349 "Not enough free space on the device. Required: %s MiB, "
350 "actual: %s MiB."
351 % (staging_size // 1024, effective_free // 1024)
352 )
353 if device_info.target_fs_free < (100 * 1024):
354 logging.warning(
355 "The device has less than 100MB free. deploy_chrome may "
356 "hang during the transfer."
357 )
Ryan Cui7193a7e2013-04-26 14:15:19 -0700358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 def _ShouldUseCompression(self):
360 """Checks if compression should be used for rsync."""
361 if self.options.compress == "always":
362 return True
363 elif self.options.compress == "never":
364 return False
365 elif self.options.compress == "auto":
366 return not self.device.HasGigabitEthernet()
Satoru Takabayashif2893002017-06-20 14:52:48 +0900367
Alex Klein1699fab2022-09-08 08:46:06 -0600368 def _Deploy(self):
369 logging.info(
370 "Copying %s to %s on device...",
371 self._deployment_name,
372 self.options.target_dir,
373 )
374 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
375 # This does not work for deploy.
376 if not self.device.HasRsync():
377 raise DeployFailure(
378 "rsync is not found on the device.\n"
379 "Run dev_install on the device to get rsync installed."
380 )
381 self.device.CopyToDevice(
382 "%s/" % os.path.abspath(self.staging_dir),
383 self.options.target_dir,
384 mode="rsync",
385 inplace=True,
386 compress=self._ShouldUseCompression(),
387 debug_level=logging.INFO,
388 verbose=self.options.verbose,
389 )
Ben Pastene5f03b052019-08-12 18:03:24 -0700390
Alex Klein1699fab2022-09-08 08:46:06 -0600391 # Set the security context on the default Chrome dir if that's where it's
392 # getting deployed, and only on SELinux supported devices.
393 if (
394 not self.options.lacros
395 and self.device.IsSELinuxAvailable()
396 and (
397 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)
398 )
399 ):
400 self.device.run(["restorecon", "-R", _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 for p in self.copy_paths:
403 if p.mode:
404 # Set mode if necessary.
405 self.device.run(
406 "chmod %o %s/%s"
407 % (
408 p.mode,
409 self.options.target_dir,
410 p.src if not p.dest else p.dest,
411 )
412 )
Steve Funge984a532013-11-25 17:09:25 -0800413
Alex Klein1699fab2022-09-08 08:46:06 -0600414 if self.options.lacros:
415 self.device.run(
416 ["chown", "-R", "chronos:chronos", self.options.target_dir]
417 )
Erik Chen75a2f492020-08-06 19:15:11 -0700418
Alex Klein1699fab2022-09-08 08:46:06 -0600419 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
420 # pick up major changes (bus type, logging, etc.), but all we care about is
421 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
422 # be authorized to take ownership of its service names.
423 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600424
Erik Chen75a2f492020-08-06 19:15:11 -0700425 if self.options.startui and self._stopped_ui:
Alex Klein1699fab2022-09-08 08:46:06 -0600426 logging.info("Starting UI...")
427 self.device.run("start ui")
David James88e6f032013-03-02 08:13:20 -0800428
Alex Klein1699fab2022-09-08 08:46:06 -0600429 def _DeployTestBinaries(self):
430 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
Thiago Goncales12793312013-05-23 11:26:17 -0700431
Alex Klein1699fab2022-09-08 08:46:06 -0600432 There could be several binaries located in the local build dir, so compare
433 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
434 over any that we also built ourselves.
435 """
436 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
437 if r.returncode != 0:
438 raise DeployFailure(
439 "Unable to ls contents of %s" % _CHROME_TEST_BIN_DIR
440 )
441 binaries_to_copy = []
442 for f in r.stdout.splitlines():
443 binaries_to_copy.append(
444 chrome_util.Path(os.path.basename(f), exe=True, optional=True)
445 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700446
Alex Klein1699fab2022-09-08 08:46:06 -0600447 staging_dir = os.path.join(
448 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR)
449 )
450 _PrepareStagingDir(
451 self.options, self.tempdir, staging_dir, copy_paths=binaries_to_copy
452 )
453 # Deploying can occasionally run into issues with rsync getting a broken
454 # pipe, so retry several times. See crbug.com/1141618 for more
455 # information.
456 retry_util.RetryException(
457 None,
458 3,
459 self.device.CopyToDevice,
460 staging_dir,
461 os.path.dirname(_CHROME_TEST_BIN_DIR),
462 mode="rsync",
463 )
Ryan Cui3045c5d2012-07-13 18:00:33 -0700464
Alex Klein1699fab2022-09-08 08:46:06 -0600465 def _CheckConnection(self):
466 try:
467 logging.info("Testing connection to the device...")
468 self.device.run("true")
469 except cros_build_lib.RunCommandError as ex:
470 logging.error("Error connecting to the test device.")
471 raise DeployFailure(ex)
Yuke Liaobe6bac32020-12-26 22:16:49 -0800472
Alex Klein1699fab2022-09-08 08:46:06 -0600473 def _CheckBoard(self):
474 """Check that the Chrome build is targeted for the device board."""
475 if self.options.board == self.device.board:
476 return
477 logging.warning(
478 "Device board is %s whereas target board is %s.",
479 self.device.board,
480 self.options.board,
481 )
482 if self.options.force:
483 return
484 if not cros_build_lib.BooleanPrompt(
485 "Continue despite board mismatch?", False
486 ):
487 raise DeployFailure("Aborted.")
Yuke Liaobe6bac32020-12-26 22:16:49 -0800488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 def _CheckDeployType(self):
490 if self.options.build_dir:
491
492 def BinaryExists(filename):
493 """Checks if the passed-in file is present in the build directory."""
494 return os.path.exists(
495 os.path.join(self.options.build_dir, filename)
496 )
497
498 # In the future, lacros-chrome and ash-chrome will likely be named
499 # something other than 'chrome' to avoid confusion.
500 # Handle non-Chrome deployments.
501 if not BinaryExists("chrome"):
502 if BinaryExists("app_shell"):
503 self.copy_paths = chrome_util.GetCopyPaths("app_shell")
504
505 def _PrepareStagingDir(self):
506 _PrepareStagingDir(
507 self.options,
508 self.tempdir,
509 self.staging_dir,
510 self.copy_paths,
511 self.chrome_dir,
512 )
513
514 def _MountTarget(self):
515 logging.info("Mounting Chrome...")
516
517 # Create directory if does not exist.
518 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
519 try:
520 # Umount the existing mount on mount_dir if present first.
521 self.device.run(
522 _UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": self.options.mount_dir}
523 )
524 except cros_build_lib.RunCommandError as e:
525 logging.error("Failed to umount %s", self.options.mount_dir)
526 # If there is a failure, check if some processs is using the mount_dir.
527 result = self.device.run(
528 LSOF_COMMAND % (self.options.mount_dir,),
529 check=False,
530 capture_output=True,
531 encoding="utf-8",
532 )
533 logging.error("lsof %s -->", self.options.mount_dir)
534 logging.error(result.stdout)
535 raise e
536
537 self.device.run(
538 _BIND_TO_FINAL_DIR_CMD
539 % (self.options.target_dir, self.options.mount_dir)
540 )
541
542 # Chrome needs partition to have exec and suid flags set
543 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
544
545 def Cleanup(self):
546 """Clean up RemoteDevice."""
547 if not self.options.staging_only:
548 self.device.Cleanup()
549
550 def Perform(self):
551 self._CheckDeployType()
552
553 # If requested, just do the staging step.
554 if self.options.staging_only:
555 self._PrepareStagingDir()
556 return 0
557
558 # Check that the build matches the device. Lacros-chrome skips this check as
559 # it's currently board independent. This means that it's possible to deploy
560 # a build of lacros-chrome with a mismatched architecture. We don't try to
561 # prevent this developer foot-gun.
562 if not self.options.lacros:
563 self._CheckBoard()
564
565 # Ensure that the target directory exists before running parallel steps.
566 self._EnsureTargetDir()
567
568 logging.info("Preparing device")
569 steps = [
570 self._GetDeviceInfo,
571 self._CheckConnection,
572 self._MountRootfsAsWritable,
573 self._PrepareStagingDir,
574 ]
575
576 restart_ui = True
577 if self.options.lacros:
578 # If this is a lacros build, we only want to restart ash-chrome if needed.
579 restart_ui = False
580 steps.append(self._KillLacrosChrome)
581 if self.options.reset_lacros:
582 steps.append(self._ResetLacrosChrome)
583 if self.options.modify_config_file:
584 restart_ui = self._ModifyConfigFileIfNeededForLacros()
585
586 if restart_ui:
587 steps.append(self._KillAshChromeIfNeeded)
588 self._stopped_ui = True
589
590 ret = parallel.RunParallelSteps(
591 steps, halt_on_error=True, return_values=True
592 )
593 self._CheckDeviceFreeSpace(ret[0])
594
595 # If the root dir is not writable, try disabling rootfs verification.
596 # (We always do this by default so that developers can write to
597 # /etc/chriome_dev.conf and other directories in the rootfs).
598 if self._root_dir_is_still_readonly.is_set():
599 if self.options.noremove_rootfs_verification:
600 logging.warning("Skipping disable rootfs verification.")
601 elif not self._DisableRootfsVerification():
602 logging.warning("Failed to disable rootfs verification.")
603
604 # If the target dir is still not writable (i.e. the user opted out or the
605 # command failed), abort.
606 if not self.device.IsDirWritable(self.options.target_dir):
607 if self.options.startui and self._stopped_ui:
608 logging.info("Restarting Chrome...")
609 self.device.run("start ui")
610 raise DeployFailure(
611 "Target location is not writable. Aborting."
612 )
613
614 if self.options.mount_dir is not None:
615 self._MountTarget()
616
617 # Actually deploy Chrome to the device.
618 self._Deploy()
619 if self.options.deploy_test_binaries:
620 self._DeployTestBinaries()
621
622 def _ModifyConfigFileIfNeededForLacros(self):
623 """Modifies the /etc/chrome_dev.conf file for lacros-chrome.
624
625 Returns:
626 True if the file is modified, and the return value is usually used to
627 determine whether restarting ash-chrome is needed.
628 """
629 assert (
630 self.options.lacros
631 ), "Only deploying lacros-chrome needs to modify the config file."
632 # Update /etc/chrome_dev.conf to include appropriate flags.
633 modified = False
Georg Neis9b1ff192022-09-14 08:07:22 +0000634 if self.options.enable_lacros_support:
635 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
636 if result.stdout.strip() == MODIFIED_CONF_FILE:
637 modified = True
Alex Klein1699fab2022-09-08 08:46:06 -0600638 result = self.device.run(
639 _SET_LACROS_PATH_VIA_CONF_COMMAND
640 % {
641 "conf_file": _CONF_FILE,
642 "lacros_path": self.options.target_dir,
643 "modified_conf_file": MODIFIED_CONF_FILE,
644 },
645 shell=True,
646 )
647 if result.stdout.strip() == MODIFIED_CONF_FILE:
648 modified = True
649
650 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800651
652
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700653def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600654 """Convert formatted string to dictionary."""
655 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700656
657
Steven Bennetts368c3e52016-09-23 13:05:21 -0700658def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600659 """Convert GN_ARGS-formatted string to dictionary."""
660 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700661
662
Ryan Cuie535b172012-10-19 18:25:03 -0700663def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600664 """Create our custom parser."""
665 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700666
Alex Klein1699fab2022-09-08 08:46:06 -0600667 # TODO(rcui): Have this use the UI-V2 format of having source and target
668 # device be specified as positional arguments.
669 parser.add_argument(
670 "--force",
671 action="store_true",
672 default=False,
673 help="Skip all prompts (such as the prompt for disabling "
674 "of rootfs verification). This may result in the "
675 "target machine being rebooted.",
676 )
677 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
678 parser.add_argument(
679 "--board",
680 default=sdk_board_env,
681 help="The board the Chrome build is targeted for. When "
682 "in a 'cros chrome-sdk' shell, defaults to the SDK "
683 "board.",
684 )
685 parser.add_argument(
686 "--build-dir",
687 type="path",
688 help="The directory with Chrome build artifacts to "
689 "deploy from. Typically of format "
690 "<chrome_root>/out/Debug. When this option is used, "
691 "the GN_ARGS environment variable must be set.",
692 )
693 parser.add_argument(
694 "--target-dir",
695 type="path",
696 default=None,
697 help="Target directory on device to deploy Chrome into.",
698 )
699 parser.add_argument(
700 "-g",
701 "--gs-path",
702 type="gs_path",
703 help="GS path that contains the chrome to deploy.",
704 )
705 parser.add_argument(
706 "--private-key",
707 type="path",
708 default=None,
709 help="An ssh private key to use when deploying to " "a CrOS device.",
710 )
711 parser.add_argument(
712 "--nostartui",
713 action="store_false",
714 dest="startui",
715 default=True,
716 help="Don't restart the ui daemon after deployment.",
717 )
718 parser.add_argument(
719 "--nostrip",
720 action="store_false",
721 dest="dostrip",
722 default=True,
723 help="Don't strip binaries during deployment. Warning: "
724 "the resulting binaries will be very large!",
725 )
726 parser.add_argument(
727 "-d",
728 "--device",
729 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
730 help="Device hostname or IP in the format hostname[:port].",
731 )
732 parser.add_argument(
733 "--mount-dir",
734 type="path",
735 default=None,
736 help="Deploy Chrome in target directory and bind it "
Georg Neis9b1ff192022-09-14 08:07:22 +0000737 "to the directory specified by this flag. "
Alex Klein1699fab2022-09-08 08:46:06 -0600738 "Any existing mount on this directory will be "
739 "umounted first.",
740 )
741 parser.add_argument(
742 "--mount",
743 action="store_true",
744 default=False,
745 help="Deploy Chrome to default target directory and bind "
Georg Neis9b1ff192022-09-14 08:07:22 +0000746 "it to the default mount directory. "
Alex Klein1699fab2022-09-08 08:46:06 -0600747 "Any existing mount on this directory will be "
748 "umounted first.",
749 )
750 parser.add_argument(
751 "--noremove-rootfs-verification",
752 action="store_true",
753 default=False,
754 help="Never remove rootfs verification.",
755 )
756 parser.add_argument(
757 "--deploy-test-binaries",
758 action="store_true",
759 default=False,
760 help="Also deploy any test binaries to %s. Useful for "
761 "running any Tast tests that execute these "
762 "binaries." % _CHROME_TEST_BIN_DIR,
763 )
764 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600765 "--use-external-config",
766 action="store_true",
767 help="When identifying the configuration for a board, "
768 "force usage of the external configuration if both "
769 "internal and external are available. This only "
770 "has an effect when stripping Chrome, i.e. when "
771 "--nostrip is not passed in.",
772 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700773
Georg Neis9b1ff192022-09-14 08:07:22 +0000774 group = parser.add_argument_group("Lacros Options")
775 group.add_argument(
776 "--lacros",
777 action="store_true",
778 default=False,
779 help="Deploys lacros-chrome rather than ash-chrome.",
780 )
781 group.add_argument(
782 "--reset-lacros",
783 action="store_true",
784 default=False,
785 help="Reset Lacros by deleting Lacros user data dir if it exists.",
786 )
787 group.add_argument(
788 "--skip-enabling-lacros-support",
789 action="store_false",
790 dest="enable_lacros_support",
791 help="By default, deploying lacros-chrome modifies the "
792 "/etc/chrome_dev.conf file to (1) enable the LacrosSupport feature "
793 "and (2) set the Lacros path, which can interfere with automated "
794 "testing. With this flag, part (1) will be skipped. See the "
795 "--skip-modifying-config-file flag for skipping both parts.",
796 )
797 group.add_argument(
798 "--skip-modifying-config-file",
799 action="store_false",
800 dest="modify_config_file",
801 help="When deploying lacros-chrome, do not modify the "
802 "/etc/chrome_dev.conf file. See also the "
803 "--skip-enabling-lacros-support flag.",
804 )
805
Alex Klein1699fab2022-09-08 08:46:06 -0600806 group = parser.add_argument_group("Advanced Options")
807 group.add_argument(
808 "-l",
809 "--local-pkg-path",
810 type="path",
811 help="Path to local chrome prebuilt package to deploy.",
812 )
813 group.add_argument(
814 "--sloppy",
815 action="store_true",
816 default=False,
817 help="Ignore when mandatory artifacts are missing.",
818 )
819 group.add_argument(
820 "--staging-flags",
821 default=None,
822 type=ValidateStagingFlags,
823 help=(
824 "Extra flags to control staging. Valid flags are - "
825 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
826 ),
827 )
828 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
829 group.add_argument(
830 "--strict",
831 action="store_true",
832 default=False,
833 help='Deprecated. Default behavior is "strict". Use '
834 "--sloppy to omit warnings for missing optional "
835 "files.",
836 )
837 group.add_argument(
838 "--strip-flags",
839 default=None,
840 help="Flags to call the 'strip' binutil tool with. "
841 "Overrides the default arguments.",
842 )
843 group.add_argument(
844 "--ping",
845 action="store_true",
846 default=False,
847 help="Ping the device before connection attempt.",
848 )
849 group.add_argument(
850 "--process-timeout",
851 type=int,
852 default=KILL_PROC_MAX_WAIT,
853 help="Timeout for process shutdown.",
854 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700855
Alex Klein1699fab2022-09-08 08:46:06 -0600856 group = parser.add_argument_group(
857 "Metadata Overrides (Advanced)",
858 description="Provide all of these overrides in order to remove "
859 "dependencies on metadata.json existence.",
860 )
861 group.add_argument(
862 "--target-tc",
863 action="store",
864 default=None,
865 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
866 )
867 group.add_argument(
868 "--toolchain-url",
869 action="store",
870 default=None,
871 help="Override toolchain url format pattern, e.g. "
872 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
873 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700874
Alex Klein1699fab2022-09-08 08:46:06 -0600875 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
876 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
877 parser.add_argument(
878 "--gyp-defines",
879 default=None,
880 type=ValidateStagingFlags,
881 help=argparse.SUPPRESS,
882 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700883
Alex Klein1699fab2022-09-08 08:46:06 -0600884 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
885 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
886 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
887 parser.add_argument(
888 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
889 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700890
Alex Klein1699fab2022-09-08 08:46:06 -0600891 # Path of an empty directory to stage chrome artifacts to. Defaults to a
892 # temporary directory that is removed when the script finishes. If the path
893 # is specified, then it will not be removed.
894 parser.add_argument(
895 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
896 )
897 # Only prepare the staging directory, and skip deploying to the device.
898 parser.add_argument(
899 "--staging-only",
900 action="store_true",
901 default=False,
902 help=argparse.SUPPRESS,
903 )
904 # Uploads the compressed staging directory to the given gs:// path URI.
905 parser.add_argument(
906 "--staging-upload",
907 type="gs_path",
908 help="GS path to upload the compressed staging files to.",
909 )
910 # Used alongside --staging-upload to upload with public-read ACL.
911 parser.add_argument(
912 "--public-read",
913 action="store_true",
914 default=False,
915 help="GS path to upload the compressed staging files to.",
916 )
917 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
918 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
919 # fetching the SDK toolchain.
920 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
921 parser.add_argument(
922 "--compress",
923 action="store",
924 default="auto",
925 choices=("always", "never", "auto"),
926 help="Choose the data compression behavior. Default "
927 'is set to "auto", that disables compression if '
928 "the target device has a gigabit ethernet port.",
929 )
930 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700931
Ryan Cuie535b172012-10-19 18:25:03 -0700932
933def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600934 """Parse args, and run environment-independent checks."""
935 parser = _CreateParser()
936 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700937
Alex Klein1699fab2022-09-08 08:46:06 -0600938 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
939 parser.error(
940 "Need to specify either --gs-path, --local-pkg-path, or "
941 "--build-dir"
942 )
943 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
944 parser.error(
945 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
946 )
947 if options.lacros:
948 if options.dostrip and not options.board:
949 parser.error("Please specify --board.")
950 if options.mount_dir or options.mount:
951 parser.error("--lacros does not support --mount or --mount-dir")
952 if options.deploy_test_binaries:
953 parser.error("--lacros does not support --deploy-test-binaries")
954 if options.local_pkg_path:
955 parser.error("--lacros does not support --local-pkg-path")
956 else:
957 if not options.board and options.build_dir:
958 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
959 if match:
960 options.board = match.group(1)
961 logging.info("--board is set to %s", options.board)
962 if not options.board:
963 parser.error("--board is required")
964 if options.gs_path and options.local_pkg_path:
965 parser.error("Cannot specify both --gs-path and --local-pkg-path")
966 if not (options.staging_only or options.device):
967 parser.error("Need to specify --device")
968 if options.staging_flags and not options.build_dir:
969 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700970
Alex Klein1699fab2022-09-08 08:46:06 -0600971 if options.strict:
972 logging.warning("--strict is deprecated.")
973 if options.gyp_defines:
974 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -0700975
Alex Klein1699fab2022-09-08 08:46:06 -0600976 if options.mount or options.mount_dir:
977 if not options.target_dir:
978 options.target_dir = _CHROME_DIR_MOUNT
979 else:
980 if not options.target_dir:
981 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700982
Alex Klein1699fab2022-09-08 08:46:06 -0600983 if options.mount and not options.mount_dir:
984 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700985
Alex Klein1699fab2022-09-08 08:46:06 -0600986 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700987
988
Mike Frysingerc3061a62015-06-04 04:16:18 -0400989def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600990 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700991
Alex Klein1699fab2022-09-08 08:46:06 -0600992 Args:
993 options: The options object returned by the cli parser.
994 """
995 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
996 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700997
Alex Klein1699fab2022-09-08 08:46:06 -0600998 if not options.gn_args:
999 gn_env = os.getenv("GN_ARGS")
1000 if gn_env is not None:
1001 options.gn_args = gn_helpers.FromGNArgs(gn_env)
1002 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -07001003
Alex Klein1699fab2022-09-08 08:46:06 -06001004 if not options.staging_flags:
1005 use_env = os.getenv("USE")
1006 if use_env is not None:
1007 options.staging_flags = " ".join(
1008 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
1009 )
1010 logging.info(
1011 "Staging flags taken from USE in environment: %s",
1012 options.staging_flags,
1013 )
Steven Bennetts60600462016-05-12 10:40:20 -07001014
Ryan Cuia56a71e2012-10-18 18:40:35 -07001015
Ryan Cui504db722013-01-22 11:48:01 -08001016def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001017 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001018
Alex Klein1699fab2022-09-08 08:46:06 -06001019 Returns:
1020 Path to the fetched chrome tarball.
1021 """
1022 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1023 files = gs_ctx.LS(gs_path)
1024 files = [
1025 found
1026 for found in files
1027 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1028 ]
1029 if not files:
1030 raise Exception("No chrome package found at %s" % gs_path)
1031 elif len(files) > 1:
1032 # - Users should provide us with a direct link to either a stripped or
1033 # unstripped chrome package.
1034 # - In the case of being provided with an archive directory, where both
1035 # stripped and unstripped chrome available, use the stripped chrome
1036 # package.
1037 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
1038 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
1039 files = [f for f in files if not "unstripped" in f]
1040 assert len(files) == 1
1041 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001042
Alex Klein1699fab2022-09-08 08:46:06 -06001043 filename = _UrlBaseName(files[0])
1044 logging.info("Fetching %s...", filename)
1045 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1046 chrome_path = os.path.join(tempdir, filename)
1047 assert os.path.exists(chrome_path)
1048 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001049
1050
Ryan Cuif890a3e2013-03-07 18:57:06 -08001051@contextlib.contextmanager
1052def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001053 if not options.dostrip:
1054 yield None
1055 elif options.strip_bin:
1056 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001057 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001058 sdk = cros_chrome_sdk.SDKFetcher(
1059 options.cache_dir,
1060 options.board,
1061 use_external_config=options.use_external_config,
1062 )
1063 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1064 with sdk.Prepare(
1065 components=components,
1066 target_tc=options.target_tc,
1067 toolchain_url=options.toolchain_url,
1068 ) as ctx:
1069 env_path = os.path.join(
1070 ctx.key_map[constants.CHROME_ENV_TAR].path,
1071 constants.CHROME_ENV_FILE,
1072 )
1073 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1074 strip_bin = os.path.join(
1075 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1076 "bin",
1077 os.path.basename(strip_bin),
1078 )
1079 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001080
Alex Klein1699fab2022-09-08 08:46:06 -06001081
1082def _UploadStagingDir(
1083 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1084) -> None:
1085 """Uploads the compressed staging directory.
1086
1087 Args:
1088 options: options object.
1089 tempdir: Scratch space.
1090 staging_dir: Directory staging chrome files.
1091 """
1092 staging_tarball_path = os.path.join(
1093 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1094 )
1095 logging.info(
1096 "Compressing staging dir (%s) to (%s)",
1097 staging_dir,
1098 staging_tarball_path,
1099 )
1100 cros_build_lib.CreateTarball(
1101 staging_tarball_path,
1102 staging_dir,
Mike Frysinger66306012022-04-22 15:23:13 -04001103 compression=cros_build_lib.CompressionType.ZSTD,
Alex Klein1699fab2022-09-08 08:46:06 -06001104 extra_env={"ZSTD_CLEVEL": "9"},
1105 )
1106 logging.info(
1107 "Uploading staging tarball (%s) into %s",
1108 staging_tarball_path,
1109 options.staging_upload,
1110 )
1111 ctx = gs.GSContext()
1112 ctx.Copy(
1113 staging_tarball_path,
1114 options.staging_upload,
1115 acl="public-read" if options.public_read else "",
1116 )
1117
1118
1119def _PrepareStagingDir(
1120 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1121):
1122 """Place the necessary files in the staging directory.
1123
1124 The staging directory is the directory used to rsync the build artifacts over
1125 to the device. Only the necessary Chrome build artifacts are put into the
1126 staging directory.
1127 """
1128 if chrome_dir is None:
1129 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1130 osutils.SafeMakedirs(staging_dir)
1131 os.chmod(staging_dir, 0o755)
1132 if options.build_dir:
1133 with _StripBinContext(options) as strip_bin:
1134 strip_flags = (
1135 None
1136 if options.strip_flags is None
1137 else shlex.split(options.strip_flags)
1138 )
1139 chrome_util.StageChromeFromBuildDir(
1140 staging_dir,
1141 options.build_dir,
1142 strip_bin,
1143 sloppy=options.sloppy,
1144 gn_args=options.gn_args,
1145 staging_flags=options.staging_flags,
1146 strip_flags=strip_flags,
1147 copy_paths=copy_paths,
1148 )
1149 else:
1150 pkg_path = options.local_pkg_path
1151 if options.gs_path:
1152 pkg_path = _FetchChromePackage(
1153 options.cache_dir, tempdir, options.gs_path
1154 )
1155
1156 assert pkg_path
1157 logging.info("Extracting %s...", pkg_path)
1158 # Extract only the ./opt/google/chrome contents, directly into the staging
1159 # dir, collapsing the directory hierarchy.
1160 if pkg_path[-4:] == ".zip":
1161 cros_build_lib.dbg_run(
1162 [
1163 "unzip",
1164 "-X",
1165 pkg_path,
1166 _ANDROID_DIR_EXTRACT_PATH,
1167 "-d",
1168 staging_dir,
1169 ]
1170 )
1171 for filename in glob.glob(
1172 os.path.join(staging_dir, "system/chrome/*")
1173 ):
1174 shutil.move(filename, staging_dir)
1175 osutils.RmDir(
1176 os.path.join(staging_dir, "system"), ignore_missing=True
1177 )
1178 else:
1179 compression = cros_build_lib.CompressionDetectType(pkg_path)
1180 compressor = cros_build_lib.FindCompressor(compression)
1181 cros_build_lib.dbg_run(
1182 [
1183 "tar",
1184 "--strip-components",
1185 "4",
1186 "--extract",
1187 "-I",
1188 compressor,
1189 "--preserve-permissions",
1190 "--file",
1191 pkg_path,
1192 ".%s" % chrome_dir,
1193 ],
1194 cwd=staging_dir,
1195 )
1196
1197 if options.staging_upload:
1198 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001199
Ryan Cui71aa8de2013-04-19 16:12:55 -07001200
Ryan Cui3045c5d2012-07-13 18:00:33 -07001201def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001202 options = _ParseCommandLine(argv)
1203 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001204
Alex Klein1699fab2022-09-08 08:46:06 -06001205 with osutils.TempDir(set_global=True) as tempdir:
1206 staging_dir = options.staging_dir
1207 if not staging_dir:
1208 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001209
Alex Klein1699fab2022-09-08 08:46:06 -06001210 deploy = DeployChrome(options, tempdir, staging_dir)
1211 try:
1212 deploy.Perform()
1213 except failures_lib.StepFailure as ex:
1214 raise SystemExit(str(ex).strip())
1215 deploy.Cleanup()