blob: 3a2ce51474ec5dfb9e5cd19a8ae5fa25c1b7865c [file] [log] [blame]
Ryan Cui3045c5d2012-07-13 18:00:33 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# 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
634 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
635 if result.stdout.strip() == MODIFIED_CONF_FILE:
636 modified = True
637 result = self.device.run(
638 _SET_LACROS_PATH_VIA_CONF_COMMAND
639 % {
640 "conf_file": _CONF_FILE,
641 "lacros_path": self.options.target_dir,
642 "modified_conf_file": MODIFIED_CONF_FILE,
643 },
644 shell=True,
645 )
646 if result.stdout.strip() == MODIFIED_CONF_FILE:
647 modified = True
648
649 return modified
Yuke Liaobe6bac32020-12-26 22:16:49 -0800650
651
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700652def ValidateStagingFlags(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600653 """Convert formatted string to dictionary."""
654 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700655
656
Steven Bennetts368c3e52016-09-23 13:05:21 -0700657def ValidateGnArgs(value):
Alex Klein1699fab2022-09-08 08:46:06 -0600658 """Convert GN_ARGS-formatted string to dictionary."""
659 return gn_helpers.FromGNArgs(value)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700660
661
Ryan Cuie535b172012-10-19 18:25:03 -0700662def _CreateParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600663 """Create our custom parser."""
664 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700665
Alex Klein1699fab2022-09-08 08:46:06 -0600666 # TODO(rcui): Have this use the UI-V2 format of having source and target
667 # device be specified as positional arguments.
668 parser.add_argument(
669 "--force",
670 action="store_true",
671 default=False,
672 help="Skip all prompts (such as the prompt for disabling "
673 "of rootfs verification). This may result in the "
674 "target machine being rebooted.",
675 )
676 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
677 parser.add_argument(
678 "--board",
679 default=sdk_board_env,
680 help="The board the Chrome build is targeted for. When "
681 "in a 'cros chrome-sdk' shell, defaults to the SDK "
682 "board.",
683 )
684 parser.add_argument(
685 "--build-dir",
686 type="path",
687 help="The directory with Chrome build artifacts to "
688 "deploy from. Typically of format "
689 "<chrome_root>/out/Debug. When this option is used, "
690 "the GN_ARGS environment variable must be set.",
691 )
692 parser.add_argument(
693 "--target-dir",
694 type="path",
695 default=None,
696 help="Target directory on device to deploy Chrome into.",
697 )
698 parser.add_argument(
699 "-g",
700 "--gs-path",
701 type="gs_path",
702 help="GS path that contains the chrome to deploy.",
703 )
704 parser.add_argument(
705 "--private-key",
706 type="path",
707 default=None,
708 help="An ssh private key to use when deploying to " "a CrOS device.",
709 )
710 parser.add_argument(
711 "--nostartui",
712 action="store_false",
713 dest="startui",
714 default=True,
715 help="Don't restart the ui daemon after deployment.",
716 )
717 parser.add_argument(
718 "--nostrip",
719 action="store_false",
720 dest="dostrip",
721 default=True,
722 help="Don't strip binaries during deployment. Warning: "
723 "the resulting binaries will be very large!",
724 )
725 parser.add_argument(
726 "-d",
727 "--device",
728 type=commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH),
729 help="Device hostname or IP in the format hostname[:port].",
730 )
731 parser.add_argument(
732 "--mount-dir",
733 type="path",
734 default=None,
735 help="Deploy Chrome in target directory and bind it "
736 "to the directory specified by this flag."
737 "Any existing mount on this directory will be "
738 "umounted first.",
739 )
740 parser.add_argument(
741 "--mount",
742 action="store_true",
743 default=False,
744 help="Deploy Chrome to default target directory and bind "
745 "it to the default mount directory."
746 "Any existing mount on this directory will be "
747 "umounted first.",
748 )
749 parser.add_argument(
750 "--noremove-rootfs-verification",
751 action="store_true",
752 default=False,
753 help="Never remove rootfs verification.",
754 )
755 parser.add_argument(
756 "--deploy-test-binaries",
757 action="store_true",
758 default=False,
759 help="Also deploy any test binaries to %s. Useful for "
760 "running any Tast tests that execute these "
761 "binaries." % _CHROME_TEST_BIN_DIR,
762 )
763 parser.add_argument(
764 "--lacros",
765 action="store_true",
766 default=False,
767 help="Deploys lacros-chrome rather than ash-chrome.",
768 )
769 parser.add_argument(
770 "--reset-lacros",
771 action="store_true",
772 default=False,
773 help="Reset Lacros by deleting Lacros user data dir if " "exists.",
774 )
775 parser.add_argument(
776 "--skip-modifying-config-file",
777 action="store_false",
778 dest="modify_config_file",
779 help="By default, deploying lacros-chrome modifies the "
780 "/etc/chrome_dev.conf file, which interferes with "
781 "automated testing, and this argument disables it.",
782 )
783 parser.add_argument(
784 "--use-external-config",
785 action="store_true",
786 help="When identifying the configuration for a board, "
787 "force usage of the external configuration if both "
788 "internal and external are available. This only "
789 "has an effect when stripping Chrome, i.e. when "
790 "--nostrip is not passed in.",
791 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700792
Alex Klein1699fab2022-09-08 08:46:06 -0600793 group = parser.add_argument_group("Advanced Options")
794 group.add_argument(
795 "-l",
796 "--local-pkg-path",
797 type="path",
798 help="Path to local chrome prebuilt package to deploy.",
799 )
800 group.add_argument(
801 "--sloppy",
802 action="store_true",
803 default=False,
804 help="Ignore when mandatory artifacts are missing.",
805 )
806 group.add_argument(
807 "--staging-flags",
808 default=None,
809 type=ValidateStagingFlags,
810 help=(
811 "Extra flags to control staging. Valid flags are - "
812 "%s" % ", ".join(chrome_util.STAGING_FLAGS)
813 ),
814 )
815 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
816 group.add_argument(
817 "--strict",
818 action="store_true",
819 default=False,
820 help='Deprecated. Default behavior is "strict". Use '
821 "--sloppy to omit warnings for missing optional "
822 "files.",
823 )
824 group.add_argument(
825 "--strip-flags",
826 default=None,
827 help="Flags to call the 'strip' binutil tool with. "
828 "Overrides the default arguments.",
829 )
830 group.add_argument(
831 "--ping",
832 action="store_true",
833 default=False,
834 help="Ping the device before connection attempt.",
835 )
836 group.add_argument(
837 "--process-timeout",
838 type=int,
839 default=KILL_PROC_MAX_WAIT,
840 help="Timeout for process shutdown.",
841 )
Ryan Cuia56a71e2012-10-18 18:40:35 -0700842
Alex Klein1699fab2022-09-08 08:46:06 -0600843 group = parser.add_argument_group(
844 "Metadata Overrides (Advanced)",
845 description="Provide all of these overrides in order to remove "
846 "dependencies on metadata.json existence.",
847 )
848 group.add_argument(
849 "--target-tc",
850 action="store",
851 default=None,
852 help="Override target toolchain name, e.g. " "x86_64-cros-linux-gnu",
853 )
854 group.add_argument(
855 "--toolchain-url",
856 action="store",
857 default=None,
858 help="Override toolchain url format pattern, e.g. "
859 "2014/04/%%(target)s-2014.04.23.220740.tar.xz",
860 )
Aviv Keshet1c986f32014-04-24 13:20:49 -0700861
Alex Klein1699fab2022-09-08 08:46:06 -0600862 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
863 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
864 parser.add_argument(
865 "--gyp-defines",
866 default=None,
867 type=ValidateStagingFlags,
868 help=argparse.SUPPRESS,
869 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700870
Alex Klein1699fab2022-09-08 08:46:06 -0600871 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
872 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
873 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
874 parser.add_argument(
875 "--gn-args", default=None, type=ValidateGnArgs, help=argparse.SUPPRESS
876 )
Steven Bennetts368c3e52016-09-23 13:05:21 -0700877
Alex Klein1699fab2022-09-08 08:46:06 -0600878 # Path of an empty directory to stage chrome artifacts to. Defaults to a
879 # temporary directory that is removed when the script finishes. If the path
880 # is specified, then it will not be removed.
881 parser.add_argument(
882 "--staging-dir", type="path", default=None, help=argparse.SUPPRESS
883 )
884 # Only prepare the staging directory, and skip deploying to the device.
885 parser.add_argument(
886 "--staging-only",
887 action="store_true",
888 default=False,
889 help=argparse.SUPPRESS,
890 )
891 # Uploads the compressed staging directory to the given gs:// path URI.
892 parser.add_argument(
893 "--staging-upload",
894 type="gs_path",
895 help="GS path to upload the compressed staging files to.",
896 )
897 # Used alongside --staging-upload to upload with public-read ACL.
898 parser.add_argument(
899 "--public-read",
900 action="store_true",
901 default=False,
902 help="GS path to upload the compressed staging files to.",
903 )
904 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
905 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
906 # fetching the SDK toolchain.
907 parser.add_argument("--strip-bin", default=None, help=argparse.SUPPRESS)
908 parser.add_argument(
909 "--compress",
910 action="store",
911 default="auto",
912 choices=("always", "never", "auto"),
913 help="Choose the data compression behavior. Default "
914 'is set to "auto", that disables compression if '
915 "the target device has a gigabit ethernet port.",
916 )
917 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700918
Ryan Cuie535b172012-10-19 18:25:03 -0700919
920def _ParseCommandLine(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600921 """Parse args, and run environment-independent checks."""
922 parser = _CreateParser()
923 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700924
Alex Klein1699fab2022-09-08 08:46:06 -0600925 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
926 parser.error(
927 "Need to specify either --gs-path, --local-pkg-path, or "
928 "--build-dir"
929 )
930 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
931 parser.error(
932 "Cannot specify both --build_dir and " "--gs-path/--local-pkg-patch"
933 )
934 if options.lacros:
935 if options.dostrip and not options.board:
936 parser.error("Please specify --board.")
937 if options.mount_dir or options.mount:
938 parser.error("--lacros does not support --mount or --mount-dir")
939 if options.deploy_test_binaries:
940 parser.error("--lacros does not support --deploy-test-binaries")
941 if options.local_pkg_path:
942 parser.error("--lacros does not support --local-pkg-path")
943 else:
944 if not options.board and options.build_dir:
945 match = re.search(r"out_([^/]+)/Release$", options.build_dir)
946 if match:
947 options.board = match.group(1)
948 logging.info("--board is set to %s", options.board)
949 if not options.board:
950 parser.error("--board is required")
951 if options.gs_path and options.local_pkg_path:
952 parser.error("Cannot specify both --gs-path and --local-pkg-path")
953 if not (options.staging_only or options.device):
954 parser.error("Need to specify --device")
955 if options.staging_flags and not options.build_dir:
956 parser.error("--staging-flags require --build-dir to be set.")
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700957
Alex Klein1699fab2022-09-08 08:46:06 -0600958 if options.strict:
959 logging.warning("--strict is deprecated.")
960 if options.gyp_defines:
961 logging.warning("--gyp-defines is deprecated.")
Thiago Goncales12793312013-05-23 11:26:17 -0700962
Alex Klein1699fab2022-09-08 08:46:06 -0600963 if options.mount or options.mount_dir:
964 if not options.target_dir:
965 options.target_dir = _CHROME_DIR_MOUNT
966 else:
967 if not options.target_dir:
968 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700969
Alex Klein1699fab2022-09-08 08:46:06 -0600970 if options.mount and not options.mount_dir:
971 options.mount_dir = _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700972
Alex Klein1699fab2022-09-08 08:46:06 -0600973 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700974
975
Mike Frysingerc3061a62015-06-04 04:16:18 -0400976def _PostParseCheck(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600977 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700978
Alex Klein1699fab2022-09-08 08:46:06 -0600979 Args:
980 options: The options object returned by the cli parser.
981 """
982 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
983 cros_build_lib.Die("%s is not a file.", options.local_pkg_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700984
Alex Klein1699fab2022-09-08 08:46:06 -0600985 if not options.gn_args:
986 gn_env = os.getenv("GN_ARGS")
987 if gn_env is not None:
988 options.gn_args = gn_helpers.FromGNArgs(gn_env)
989 logging.debug("GN_ARGS taken from environment: %s", options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700990
Alex Klein1699fab2022-09-08 08:46:06 -0600991 if not options.staging_flags:
992 use_env = os.getenv("USE")
993 if use_env is not None:
994 options.staging_flags = " ".join(
995 set(use_env.split()).intersection(chrome_util.STAGING_FLAGS)
996 )
997 logging.info(
998 "Staging flags taken from USE in environment: %s",
999 options.staging_flags,
1000 )
Steven Bennetts60600462016-05-12 10:40:20 -07001001
Ryan Cuia56a71e2012-10-18 18:40:35 -07001002
Ryan Cui504db722013-01-22 11:48:01 -08001003def _FetchChromePackage(cache_dir, tempdir, gs_path):
Alex Klein1699fab2022-09-08 08:46:06 -06001004 """Get the chrome prebuilt tarball from GS.
Ryan Cuia56a71e2012-10-18 18:40:35 -07001005
Alex Klein1699fab2022-09-08 08:46:06 -06001006 Returns:
1007 Path to the fetched chrome tarball.
1008 """
1009 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
1010 files = gs_ctx.LS(gs_path)
1011 files = [
1012 found
1013 for found in files
1014 if _UrlBaseName(found).startswith("%s-" % constants.CHROME_PN)
1015 ]
1016 if not files:
1017 raise Exception("No chrome package found at %s" % gs_path)
1018 elif len(files) > 1:
1019 # - Users should provide us with a direct link to either a stripped or
1020 # unstripped chrome package.
1021 # - In the case of being provided with an archive directory, where both
1022 # stripped and unstripped chrome available, use the stripped chrome
1023 # package.
1024 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
1025 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
1026 files = [f for f in files if not "unstripped" in f]
1027 assert len(files) == 1
1028 logging.warning("Multiple chrome packages found. Using %s", files[0])
Ryan Cui777ff422012-12-07 13:12:54 -08001029
Alex Klein1699fab2022-09-08 08:46:06 -06001030 filename = _UrlBaseName(files[0])
1031 logging.info("Fetching %s...", filename)
1032 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
1033 chrome_path = os.path.join(tempdir, filename)
1034 assert os.path.exists(chrome_path)
1035 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -07001036
1037
Ryan Cuif890a3e2013-03-07 18:57:06 -08001038@contextlib.contextmanager
1039def _StripBinContext(options):
Alex Klein1699fab2022-09-08 08:46:06 -06001040 if not options.dostrip:
1041 yield None
1042 elif options.strip_bin:
1043 yield options.strip_bin
Steve Funge984a532013-11-25 17:09:25 -08001044 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001045 sdk = cros_chrome_sdk.SDKFetcher(
1046 options.cache_dir,
1047 options.board,
1048 use_external_config=options.use_external_config,
1049 )
1050 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
1051 with sdk.Prepare(
1052 components=components,
1053 target_tc=options.target_tc,
1054 toolchain_url=options.toolchain_url,
1055 ) as ctx:
1056 env_path = os.path.join(
1057 ctx.key_map[constants.CHROME_ENV_TAR].path,
1058 constants.CHROME_ENV_FILE,
1059 )
1060 strip_bin = osutils.SourceEnvironment(env_path, ["STRIP"])["STRIP"]
1061 strip_bin = os.path.join(
1062 ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
1063 "bin",
1064 os.path.basename(strip_bin),
1065 )
1066 yield strip_bin
Ryan Cui3045c5d2012-07-13 18:00:33 -07001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068
1069def _UploadStagingDir(
1070 options: commandline.ArgumentNamespace, tempdir: str, staging_dir: str
1071) -> None:
1072 """Uploads the compressed staging directory.
1073
1074 Args:
1075 options: options object.
1076 tempdir: Scratch space.
1077 staging_dir: Directory staging chrome files.
1078 """
1079 staging_tarball_path = os.path.join(
1080 tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD
1081 )
1082 logging.info(
1083 "Compressing staging dir (%s) to (%s)",
1084 staging_dir,
1085 staging_tarball_path,
1086 )
1087 cros_build_lib.CreateTarball(
1088 staging_tarball_path,
1089 staging_dir,
1090 compression=cros_build_lib.COMP_ZSTD,
1091 extra_env={"ZSTD_CLEVEL": "9"},
1092 )
1093 logging.info(
1094 "Uploading staging tarball (%s) into %s",
1095 staging_tarball_path,
1096 options.staging_upload,
1097 )
1098 ctx = gs.GSContext()
1099 ctx.Copy(
1100 staging_tarball_path,
1101 options.staging_upload,
1102 acl="public-read" if options.public_read else "",
1103 )
1104
1105
1106def _PrepareStagingDir(
1107 options, tempdir, staging_dir, copy_paths=None, chrome_dir=None
1108):
1109 """Place the necessary files in the staging directory.
1110
1111 The staging directory is the directory used to rsync the build artifacts over
1112 to the device. Only the necessary Chrome build artifacts are put into the
1113 staging directory.
1114 """
1115 if chrome_dir is None:
1116 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
1117 osutils.SafeMakedirs(staging_dir)
1118 os.chmod(staging_dir, 0o755)
1119 if options.build_dir:
1120 with _StripBinContext(options) as strip_bin:
1121 strip_flags = (
1122 None
1123 if options.strip_flags is None
1124 else shlex.split(options.strip_flags)
1125 )
1126 chrome_util.StageChromeFromBuildDir(
1127 staging_dir,
1128 options.build_dir,
1129 strip_bin,
1130 sloppy=options.sloppy,
1131 gn_args=options.gn_args,
1132 staging_flags=options.staging_flags,
1133 strip_flags=strip_flags,
1134 copy_paths=copy_paths,
1135 )
1136 else:
1137 pkg_path = options.local_pkg_path
1138 if options.gs_path:
1139 pkg_path = _FetchChromePackage(
1140 options.cache_dir, tempdir, options.gs_path
1141 )
1142
1143 assert pkg_path
1144 logging.info("Extracting %s...", pkg_path)
1145 # Extract only the ./opt/google/chrome contents, directly into the staging
1146 # dir, collapsing the directory hierarchy.
1147 if pkg_path[-4:] == ".zip":
1148 cros_build_lib.dbg_run(
1149 [
1150 "unzip",
1151 "-X",
1152 pkg_path,
1153 _ANDROID_DIR_EXTRACT_PATH,
1154 "-d",
1155 staging_dir,
1156 ]
1157 )
1158 for filename in glob.glob(
1159 os.path.join(staging_dir, "system/chrome/*")
1160 ):
1161 shutil.move(filename, staging_dir)
1162 osutils.RmDir(
1163 os.path.join(staging_dir, "system"), ignore_missing=True
1164 )
1165 else:
1166 compression = cros_build_lib.CompressionDetectType(pkg_path)
1167 compressor = cros_build_lib.FindCompressor(compression)
1168 cros_build_lib.dbg_run(
1169 [
1170 "tar",
1171 "--strip-components",
1172 "4",
1173 "--extract",
1174 "-I",
1175 compressor,
1176 "--preserve-permissions",
1177 "--file",
1178 pkg_path,
1179 ".%s" % chrome_dir,
1180 ],
1181 cwd=staging_dir,
1182 )
1183
1184 if options.staging_upload:
1185 _UploadStagingDir(options, tempdir, staging_dir)
Jae Hoon Kimdf842912022-05-19 06:40:42 +00001186
Ryan Cui71aa8de2013-04-19 16:12:55 -07001187
Ryan Cui3045c5d2012-07-13 18:00:33 -07001188def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001189 options = _ParseCommandLine(argv)
1190 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -07001191
Alex Klein1699fab2022-09-08 08:46:06 -06001192 with osutils.TempDir(set_global=True) as tempdir:
1193 staging_dir = options.staging_dir
1194 if not staging_dir:
1195 staging_dir = os.path.join(tempdir, "chrome")
Ryan Cuia56a71e2012-10-18 18:40:35 -07001196
Alex Klein1699fab2022-09-08 08:46:06 -06001197 deploy = DeployChrome(options, tempdir, staging_dir)
1198 try:
1199 deploy.Perform()
1200 except failures_lib.StepFailure as ex:
1201 raise SystemExit(str(ex).strip())
1202 deploy.Cleanup()