blob: b2e7be5261989f12d65f12799ee9cf0bde892dd5 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Ryan Cui3045c5d2012-07-13 18:00:33 -07002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Steve Funge984a532013-11-25 17:09:25 -08006"""Script that deploys a Chrome build to a device.
Ryan Cuia56a71e2012-10-18 18:40:35 -07007
8The script supports deploying Chrome from these sources:
9
101. A local build output directory, such as chromium/src/out/[Debug|Release].
112. A Chrome tarball uploaded by a trybot/official-builder to GoogleStorage.
123. A Chrome tarball existing locally.
13
14The script copies the necessary contents of the source location (tarball or
15build directory) and rsyncs the contents of the staging directory onto your
16device's rootfs.
17"""
Ryan Cui3045c5d2012-07-13 18:00:33 -070018
Mike Frysinger383367e2014-09-16 15:06:17 -040019from __future__ import print_function
20
Mike Frysingerc3061a62015-06-04 04:16:18 -040021import argparse
Ryan Cui7193a7e2013-04-26 14:15:19 -070022import collections
Ryan Cuif890a3e2013-03-07 18:57:06 -080023import contextlib
Ryan Cui7193a7e2013-04-26 14:15:19 -070024import functools
Steve Funge984a532013-11-25 17:09:25 -080025import glob
David James88e6f032013-03-02 08:13:20 -080026import multiprocessing
Ryan Cui3045c5d2012-07-13 18:00:33 -070027import os
Ryan Cui5b7c2ed2013-03-18 18:45:46 -070028import shlex
Steve Funge984a532013-11-25 17:09:25 -080029import shutil
Mike Frysinger6165cdc2020-02-21 02:38:07 -050030import sys
Ryan Cui3045c5d2012-07-13 18:00:33 -070031import time
Ryan Cui3045c5d2012-07-13 18:00:33 -070032
Aviv Keshetb7519e12016-10-04 00:50:00 -070033from chromite.lib import constants
34from chromite.lib import failures_lib
David Pursellcfd58872015-03-19 09:15:48 -070035from chromite.cli.cros import cros_chrome_sdk
Ryan Cuia56a71e2012-10-18 18:40:35 -070036from chromite.lib import chrome_util
Ryan Cuie535b172012-10-19 18:25:03 -070037from chromite.lib import commandline
Ralph Nathan91874ca2015-03-19 13:29:41 -070038from chromite.lib import cros_build_lib
39from chromite.lib import cros_logging as logging
Ryan Cui777ff422012-12-07 13:12:54 -080040from chromite.lib import gs
Ryan Cui3045c5d2012-07-13 18:00:33 -070041from chromite.lib import osutils
David James88e6f032013-03-02 08:13:20 -080042from chromite.lib import parallel
Ryan Cui3045c5d2012-07-13 18:00:33 -070043from chromite.lib import remote_access as remote
David James3432acd2013-11-27 10:02:18 -080044from chromite.lib import timeout_util
Steven Bennetts368c3e52016-09-23 13:05:21 -070045from gn_helpers import gn_helpers
Ryan Cui3045c5d2012-07-13 18:00:33 -070046
47
Mike Frysinger6165cdc2020-02-21 02:38:07 -050048assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
49
50
Ryan Cui3045c5d2012-07-13 18:00:33 -070051KERNEL_A_PARTITION = 2
52KERNEL_B_PARTITION = 4
53
54KILL_PROC_MAX_WAIT = 10
55POST_KILL_WAIT = 2
56
David Haddock3151d912017-10-24 03:50:32 +000057MOUNT_RW_COMMAND = 'mount -o remount,rw /'
Anushruth8d797672019-10-17 12:22:31 -070058LSOF_COMMAND_CHROME = 'lsof %s/chrome'
59LSOF_COMMAND = 'lsof %s'
David Haddock3151d912017-10-24 03:50:32 +000060DBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon'
Ryan Cui3045c5d2012-07-13 18:00:33 -070061
Steve Funge984a532013-11-25 17:09:25 -080062_ANDROID_DIR = '/system/chrome'
63_ANDROID_DIR_EXTRACT_PATH = 'system/chrome/*'
64
David James2cb34002013-03-01 18:42:40 -080065_CHROME_DIR = '/opt/google/chrome'
Thiago Goncales12793312013-05-23 11:26:17 -070066_CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
Ben Pastenee484b342020-06-30 18:29:27 -070067_CHROME_TEST_BIN_DIR = '/usr/local/libexec/chrome-binary-tests'
David James2cb34002013-03-01 18:42:40 -080068
David Haddock3151d912017-10-24 03:50:32 +000069_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
70 'if mountpoint -q %(dir)s; then umount %(dir)s; fi')
71_BIND_TO_FINAL_DIR_CMD = 'mount --rbind %s %s'
72_SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
Anushruth8d797672019-10-17 12:22:31 -070073_MKDIR_P_CMD = 'mkdir -p --mode 0775 %s'
Ben Pastenee484b342020-06-30 18:29:27 -070074_FIND_TEST_BIN_CMD = 'find %s -maxdepth 1 -executable -type f' % (
75 _CHROME_TEST_BIN_DIR)
David Haddock3151d912017-10-24 03:50:32 +000076
77DF_COMMAND = 'df -k %s'
78
ChromeOS Developer536f59a2020-08-10 14:39:27 -070079LACROS_DIR = '/usr/local/lacros-chrome'
Erik Chen75a2f492020-08-06 19:15:11 -070080_CONF_FILE = '/etc/chrome_dev.conf'
81_KILL_LACROS_CHROME_CMD = 'pkill -f %(lacros_dir)s/chrome'
82MODIFIED_CONF_FILE = f'modified {_CONF_FILE}'
83
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
Ryan Cui3045c5d2012-07-13 18:00:33 -0700110def _UrlBaseName(url):
111 """Return the last component of the URL."""
112 return url.rstrip('/').rpartition('/')[-1]
113
114
Yu-Ju Hongc54d3342014-05-14 12:42:06 -0700115class DeployFailure(failures_lib.StepFailure):
David James88e6f032013-03-02 08:13:20 -0800116 """Raised whenever the deploy fails."""
117
118
Ryan Cui7193a7e2013-04-26 14:15:19 -0700119DeviceInfo = collections.namedtuple(
120 'DeviceInfo', ['target_dir_size', 'target_fs_free'])
121
122
Ryan Cui3045c5d2012-07-13 18:00:33 -0700123class DeployChrome(object):
124 """Wraps the core deployment functionality."""
Mike Frysingere65f3752014-12-08 00:46:39 -0500125
Ryan Cuia56a71e2012-10-18 18:40:35 -0700126 def __init__(self, options, tempdir, staging_dir):
Ryan Cuie535b172012-10-19 18:25:03 -0700127 """Initialize the class.
128
Mike Frysinger02e1e072013-11-10 22:11:34 -0500129 Args:
Mike Frysingerc3061a62015-06-04 04:16:18 -0400130 options: options object.
Ryan Cuie535b172012-10-19 18:25:03 -0700131 tempdir: Scratch space for the class. Caller has responsibility to clean
132 it up.
Steve Funge984a532013-11-25 17:09:25 -0800133 staging_dir: Directory to stage the files to.
Ryan Cuie535b172012-10-19 18:25:03 -0700134 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700135 self.tempdir = tempdir
136 self.options = options
Ryan Cuia56a71e2012-10-18 18:40:35 -0700137 self.staging_dir = staging_dir
Robert Flack1dc7ea82014-11-26 13:50:24 -0500138 if not self.options.staging_only:
Avery Musbach3edff0e2020-03-27 13:35:53 -0700139 self.device = remote.ChromiumOSDevice(options.to, port=options.port,
140 ping=options.ping,
141 private_key=options.private_key,
142 include_dev_paths=False)
Steven Bennettsca73efa2018-07-10 13:36:56 -0700143 self._root_dir_is_still_readonly = multiprocessing.Event()
Steven Bennetts5a7c72d2016-10-17 20:04:46 +0000144
Erik Chen75a2f492020-08-06 19:15:11 -0700145 self._deployment_name = 'lacros' if options.lacros else 'chrome'
146 self.copy_paths = chrome_util.GetCopyPaths(self._deployment_name)
147
148 self.chrome_dir = LACROS_DIR if self.options.lacros else _CHROME_DIR
149
150 # Whether UI was stopped during setup.
151 self._stopped_ui = False
Steve Funge984a532013-11-25 17:09:25 -0800152
Ryan Cui7193a7e2013-04-26 14:15:19 -0700153 def _GetRemoteMountFree(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400154 result = self.device.run(DF_COMMAND % remote_dir)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700155 line = result.output.splitlines()[1]
Steve Funge984a532013-11-25 17:09:25 -0800156 value = line.split()[3]
157 multipliers = {
158 'G': 1024 * 1024 * 1024,
159 'M': 1024 * 1024,
160 'K': 1024,
161 }
162 return int(value.rstrip('GMK')) * multipliers.get(value[-1], 1)
Ryan Cui7193a7e2013-04-26 14:15:19 -0700163
164 def _GetRemoteDirSize(self, remote_dir):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400165 result = self.device.run('du -ks %s' % remote_dir,
166 capture_output=True, encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700167 return int(result.output.split()[0])
168
169 def _GetStagingDirSize(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400170 result = cros_build_lib.dbg_run(['du', '-ks', self.staging_dir],
Mike Frysinger0282d222019-12-17 17:15:48 -0500171 stdout=True, capture_output=True,
Mike Frysingerb88d5fb2019-10-23 00:38:45 -0400172 encoding='utf-8')
Ryan Cui7193a7e2013-04-26 14:15:19 -0700173 return int(result.output.split()[0])
174
Ryan Cui3045c5d2012-07-13 18:00:33 -0700175 def _ChromeFileInUse(self):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400176 result = self.device.run(LSOF_COMMAND_CHROME % (self.options.target_dir,),
177 check=False, capture_output=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700178 return result.returncode == 0
179
Justin TerAvestfac210e2017-04-13 11:39:00 -0600180 def _Reboot(self):
181 # A reboot in developer mode takes a while (and has delays), so the user
182 # will have time to read and act on the USB boot instructions below.
183 logging.info('Please remember to press Ctrl-U if you are booting from USB.')
184 self.device.Reboot()
185
Ryan Cui3045c5d2012-07-13 18:00:33 -0700186 def _DisableRootfsVerification(self):
187 if not self.options.force:
188 logging.error('Detected that the device has rootfs verification enabled.')
189 logging.info('This script can automatically remove the rootfs '
190 'verification, which requires that it reboot the device.')
191 logging.info('Make sure the device is in developer mode!')
192 logging.info('Skip this prompt by specifying --force.')
Ben Chan37d64e52018-10-05 15:35:02 -0700193 if not cros_build_lib.BooleanPrompt('Remove rootfs verification?', False):
Steven Bennettsca73efa2018-07-10 13:36:56 -0700194 return False
Ryan Cui3045c5d2012-07-13 18:00:33 -0700195
196 logging.info('Removing rootfs verification from %s', self.options.to)
197 # Running in VM's cause make_dev_ssd's firmware sanity checks to fail.
198 # Use --force to bypass the checks.
David Haddock3151d912017-10-24 03:50:32 +0000199 cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
200 '--remove_rootfs_verification --force')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700201 for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400202 self.device.run(cmd % partition, check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700203
Justin TerAvestfac210e2017-04-13 11:39:00 -0600204 self._Reboot()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700205
David James88e6f032013-03-02 08:13:20 -0800206 # Now that the machine has been rebooted, we need to kill Chrome again.
Erik Chen75a2f492020-08-06 19:15:11 -0700207 self._KillAshChromeIfNeeded()
David James88e6f032013-03-02 08:13:20 -0800208
209 # Make sure the rootfs is writable now.
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500210 self._MountRootfsAsWritable(check=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700211
Steven Bennettsca73efa2018-07-10 13:36:56 -0700212 return True
213
Ryan Cui3045c5d2012-07-13 18:00:33 -0700214 def _CheckUiJobStarted(self):
215 # status output is in the format:
216 # <job_name> <status> ['process' <pid>].
217 # <status> is in the format <goal>/<state>.
Ryan Cuif2d1a582013-02-19 14:08:13 -0800218 try:
Mike Frysinger3459bf52020-03-31 00:52:11 -0400219 result = self.device.run('status ui', capture_output=True,
220 encoding='utf-8')
Ryan Cuif2d1a582013-02-19 14:08:13 -0800221 except cros_build_lib.RunCommandError as e:
222 if 'Unknown job' in e.result.error:
223 return False
224 else:
225 raise e
226
Ryan Cui3045c5d2012-07-13 18:00:33 -0700227 return result.output.split()[1].split('/')[0] == 'start'
228
Erik Chen75a2f492020-08-06 19:15:11 -0700229 def _KillLacrosChrome(self):
230 """This method kills lacros-chrome on the device, if it's running."""
231 self.device.run(_KILL_LACROS_CHROME_CMD %
232 {'lacros_dir': self.options.target_dir}, check=False)
233
234 def _KillAshChromeIfNeeded(self):
235 """This method kills ash-chrome on the device, if it's running.
236
237 This method calls 'stop ui', and then also manually pkills both ash-chrome
238 and the session manager.
239 """
Ryan Cui3045c5d2012-07-13 18:00:33 -0700240 if self._CheckUiJobStarted():
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800241 logging.info('Shutting down Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400242 self.device.run('stop ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700243
244 # Developers sometimes run session_manager manually, in which case we'll
245 # need to help shut the chrome processes down.
246 try:
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700247 with timeout_util.Timeout(self.options.process_timeout):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700248 while self._ChromeFileInUse():
249 logging.warning('The chrome binary on the device is in use.')
250 logging.warning('Killing chrome and session_manager processes...\n')
251
Mike Frysinger3459bf52020-03-31 00:52:11 -0400252 self.device.run("pkill 'chrome|session_manager'", check=False)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700253 # Wait for processes to actually terminate
254 time.sleep(POST_KILL_WAIT)
255 logging.info('Rechecking the chrome binary...')
David James3432acd2013-11-27 10:02:18 -0800256 except timeout_util.TimeoutError:
David James88e6f032013-03-02 08:13:20 -0800257 msg = ('Could not kill processes after %s seconds. Please exit any '
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700258 'running chrome processes and try again.'
259 % self.options.process_timeout)
David James88e6f032013-03-02 08:13:20 -0800260 raise DeployFailure(msg)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700261
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500262 def _MountRootfsAsWritable(self, check=False):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800263 """Mounts the rootfs as writable.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700264
Steven Bennettsca73efa2018-07-10 13:36:56 -0700265 If the command fails and the root dir is not writable then this function
266 sets self._root_dir_is_still_readonly.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700267
Mike Frysinger02e1e072013-11-10 22:11:34 -0500268 Args:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500269 check: See remote.RemoteAccess.RemoteSh for details.
David James88e6f032013-03-02 08:13:20 -0800270 """
Yu-Ju Hong7e116ac2014-01-03 17:08:26 -0800271 # TODO: Should migrate to use the remount functions in remote_access.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400272 result = self.device.run(MOUNT_RW_COMMAND, check=check,
273 capture_output=True, encoding='utf-8')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700274 if result.returncode and not self.device.IsDirWritable('/'):
275 self._root_dir_is_still_readonly.set()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700276
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800277 def _EnsureTargetDir(self):
278 """Ensures that the target directory exists on the remote device."""
279 target_dir = self.options.target_dir
280 # Any valid /opt directory should already exist so avoid the remote call.
281 if os.path.commonprefix([target_dir, '/opt']) == '/opt':
282 return
Mike Frysinger3459bf52020-03-31 00:52:11 -0400283 self.device.run(['mkdir', '-p', '--mode', '0775', target_dir])
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800284
Ryan Cui7193a7e2013-04-26 14:15:19 -0700285 def _GetDeviceInfo(self):
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800286 """Returns the disk space used and available for the target diectory."""
Ryan Cui7193a7e2013-04-26 14:15:19 -0700287 steps = [
288 functools.partial(self._GetRemoteDirSize, self.options.target_dir),
289 functools.partial(self._GetRemoteMountFree, self.options.target_dir)
290 ]
291 return_values = parallel.RunParallelSteps(steps, return_values=True)
292 return DeviceInfo(*return_values)
293
294 def _CheckDeviceFreeSpace(self, device_info):
295 """See if target device has enough space for Chrome.
296
Mike Frysinger02e1e072013-11-10 22:11:34 -0500297 Args:
Ryan Cui7193a7e2013-04-26 14:15:19 -0700298 device_info: A DeviceInfo named tuple.
299 """
300 effective_free = device_info.target_dir_size + device_info.target_fs_free
301 staging_size = self._GetStagingDirSize()
302 if effective_free < staging_size:
Daniel Erat1ae46382014-08-14 10:23:39 -0700303 raise DeployFailure(
304 'Not enough free space on the device. Required: %s MiB, '
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400305 'actual: %s MiB.' % (staging_size // 1024, effective_free // 1024))
Ryan Cui7193a7e2013-04-26 14:15:19 -0700306 if device_info.target_fs_free < (100 * 1024):
307 logging.warning('The device has less than 100MB free. deploy_chrome may '
308 'hang during the transfer.')
309
Satoru Takabayashif2893002017-06-20 14:52:48 +0900310 def _ShouldUseCompression(self):
311 """Checks if compression should be used for rsync."""
312 if self.options.compress == 'always':
313 return True
314 elif self.options.compress == 'never':
315 return False
316 elif self.options.compress == 'auto':
317 return not self.device.HasGigabitEthernet()
318
Ryan Cui3045c5d2012-07-13 18:00:33 -0700319 def _Deploy(self):
Erik Chen75a2f492020-08-06 19:15:11 -0700320 logging.info('Copying %s to %s on device...', self._deployment_name,
321 self.options.target_dir)
Ilja H. Friedel0ab63e12017-03-28 13:29:48 -0700322 # CopyToDevice will fall back to scp if rsync is corrupted on stateful.
323 # This does not work for deploy.
Satoru Takabayashiab380bc2015-01-15 16:00:41 +0900324 if not self.device.HasRsync():
325 raise DeployFailure(
David Pursellcfd58872015-03-19 09:15:48 -0700326 'rsync is not found on the device.\n'
Jorge Lucangeli Obesea3742f2019-02-07 15:29:09 -0500327 'Run dev_install on the device to get rsync installed.')
Robert Flack1dc7ea82014-11-26 13:50:24 -0500328 self.device.CopyToDevice('%s/' % os.path.abspath(self.staging_dir),
329 self.options.target_dir,
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700330 mode='rsync', inplace=True,
Satoru Takabayashif2893002017-06-20 14:52:48 +0900331 compress=self._ShouldUseCompression(),
Steven Bennettsd57a72a2017-03-20 12:54:00 -0700332 debug_level=logging.INFO,
Robert Flack1dc7ea82014-11-26 13:50:24 -0500333 verbose=self.options.verbose)
Ben Pastene5f03b052019-08-12 18:03:24 -0700334
335 # Set the security context on the default Chrome dir if that's where it's
336 # getting deployed, and only on SELinux supported devices.
Erik Chen75a2f492020-08-06 19:15:11 -0700337 if not self.options.lacros and self.device.IsSELinuxAvailable() and (
Ben Pastene5f03b052019-08-12 18:03:24 -0700338 _CHROME_DIR in (self.options.target_dir, self.options.mount_dir)):
Mike Frysinger3459bf52020-03-31 00:52:11 -0400339 self.device.run(['restorecon', '-R', _CHROME_DIR])
Steve Funge984a532013-11-25 17:09:25 -0800340
341 for p in self.copy_paths:
Steve Funge984a532013-11-25 17:09:25 -0800342 if p.mode:
343 # Set mode if necessary.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400344 self.device.run('chmod %o %s/%s' % (
David Haddock3151d912017-10-24 03:50:32 +0000345 p.mode, self.options.target_dir, p.src if not p.dest else p.dest))
Steve Funge984a532013-11-25 17:09:25 -0800346
Erik Chen75a2f492020-08-06 19:15:11 -0700347 if self.options.lacros:
348 self.device.run(['chown', '-R', 'chronos:chronos',
349 self.options.target_dir])
350
Daniel Erat0fce5bf2017-09-28 17:29:23 -0700351 # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
352 # pick up major changes (bus type, logging, etc.), but all we care about is
353 # getting the latest policy from /opt/google/chrome/dbus so that Chrome will
354 # be authorized to take ownership of its service names.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400355 self.device.run(DBUS_RELOAD_COMMAND, check=False)
Justin TerAvestfac210e2017-04-13 11:39:00 -0600356
Erik Chen75a2f492020-08-06 19:15:11 -0700357 if self.options.startui and self._stopped_ui:
Steve Funge984a532013-11-25 17:09:25 -0800358 logging.info('Starting UI...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400359 self.device.run('start ui')
Ryan Cui3045c5d2012-07-13 18:00:33 -0700360
Ben Pastenee484b342020-06-30 18:29:27 -0700361 def _DeployTestBinaries(self):
362 """Deploys any local test binary to _CHROME_TEST_BIN_DIR on the device.
363
364 There could be several binaries located in the local build dir, so compare
365 what's already present on the device in _CHROME_TEST_BIN_DIR , and copy
366 over any that we also built ourselves.
367 """
368 r = self.device.run(_FIND_TEST_BIN_CMD, check=False)
369 if r.returncode != 0:
370 raise DeployFailure('Unable to ls contents of %s' % _CHROME_TEST_BIN_DIR)
371 binaries_to_copy = []
372 for f in r.output.splitlines():
373 binaries_to_copy.append(
374 chrome_util.Path(os.path.basename(f), exe=True, optional=True))
375
376 staging_dir = os.path.join(
377 self.tempdir, os.path.basename(_CHROME_TEST_BIN_DIR))
378 _PrepareStagingDir(self.options, self.tempdir, staging_dir,
379 copy_paths=binaries_to_copy)
380 self.device.CopyToDevice(
381 staging_dir, os.path.dirname(_CHROME_TEST_BIN_DIR), mode='rsync')
382
David James88e6f032013-03-02 08:13:20 -0800383 def _CheckConnection(self):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700384 try:
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800385 logging.info('Testing connection to the device...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400386 self.device.run('true')
David James88e6f032013-03-02 08:13:20 -0800387 except cros_build_lib.RunCommandError as ex:
Ryan Cui3045c5d2012-07-13 18:00:33 -0700388 logging.error('Error connecting to the test device.')
David James88e6f032013-03-02 08:13:20 -0800389 raise DeployFailure(ex)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700390
Avery Musbach3edff0e2020-03-27 13:35:53 -0700391 def _CheckBoard(self):
392 """Check that the Chrome build is targeted for the device board."""
393 if self.options.board == self.device.board:
394 return
395 logging.warning('Device board is %s whereas target board is %s.',
396 self.device.board, self.options.board)
397 if self.options.force:
398 return
399 if not cros_build_lib.BooleanPrompt('Continue despite board mismatch?',
400 False):
401 raise DeployFailure('Aborted.')
402
Steve Funge984a532013-11-25 17:09:25 -0800403 def _CheckDeployType(self):
Steve Fung63d705d2014-03-16 03:14:03 -0700404 if self.options.build_dir:
Daniel Erat3fd6c7a2014-05-02 14:28:31 -0700405 def BinaryExists(filename):
406 """Checks if the passed-in file is present in the build directory."""
407 return os.path.exists(os.path.join(self.options.build_dir, filename))
408
Erik Chen75a2f492020-08-06 19:15:11 -0700409 # In the future, lacros-chrome and ash-chrome will likely be named
410 # something other than 'chrome' to avoid confusion.
Daniel Erat9813f0e2014-11-12 11:00:28 -0700411 # Handle non-Chrome deployments.
412 if not BinaryExists('chrome'):
Daniel Eratf53bd3a2016-12-02 11:28:36 -0700413 if BinaryExists('app_shell'):
Daniel Erat9813f0e2014-11-12 11:00:28 -0700414 self.copy_paths = chrome_util.GetCopyPaths('app_shell')
415
David James88e6f032013-03-02 08:13:20 -0800416 def _PrepareStagingDir(self):
Steve Funge984a532013-11-25 17:09:25 -0800417 _PrepareStagingDir(self.options, self.tempdir, self.staging_dir,
418 self.copy_paths, self.chrome_dir)
David James88e6f032013-03-02 08:13:20 -0800419
Thiago Goncales12793312013-05-23 11:26:17 -0700420 def _MountTarget(self):
421 logging.info('Mounting Chrome...')
422
Anushruth8d797672019-10-17 12:22:31 -0700423 # Create directory if does not exist.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400424 self.device.run(_MKDIR_P_CMD % self.options.mount_dir)
Anushruth8d797672019-10-17 12:22:31 -0700425 try:
426 # Umount the existing mount on mount_dir if present first.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400427 self.device.run(_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
428 {'dir': self.options.mount_dir})
Anushruth8d797672019-10-17 12:22:31 -0700429 except cros_build_lib.RunCommandError as e:
430 logging.error('Failed to umount %s', self.options.mount_dir)
431 # If there is a failure, check if some processs is using the mount_dir.
Mike Frysinger3459bf52020-03-31 00:52:11 -0400432 result = self.device.run(LSOF_COMMAND % (self.options.mount_dir,),
433 check=False, capture_output=True,
434 encoding='utf-8')
Anushruth8d797672019-10-17 12:22:31 -0700435 logging.error('lsof %s -->', self.options.mount_dir)
436 logging.error(result.stdout)
437 raise e
438
Mike Frysinger3459bf52020-03-31 00:52:11 -0400439 self.device.run(_BIND_TO_FINAL_DIR_CMD % (self.options.target_dir,
440 self.options.mount_dir))
Anushruth8d797672019-10-17 12:22:31 -0700441
Thiago Goncales12793312013-05-23 11:26:17 -0700442 # Chrome needs partition to have exec and suid flags set
Mike Frysinger3459bf52020-03-31 00:52:11 -0400443 self.device.run(_SET_MOUNT_FLAGS_CMD % (self.options.mount_dir,))
Robert Flack1dc7ea82014-11-26 13:50:24 -0500444
445 def Cleanup(self):
446 """Clean up RemoteDevice."""
447 if not self.options.staging_only:
448 self.device.Cleanup()
Thiago Goncales12793312013-05-23 11:26:17 -0700449
David James88e6f032013-03-02 08:13:20 -0800450 def Perform(self):
Steve Funge984a532013-11-25 17:09:25 -0800451 self._CheckDeployType()
452
David James88e6f032013-03-02 08:13:20 -0800453 # If requested, just do the staging step.
454 if self.options.staging_only:
455 self._PrepareStagingDir()
456 return 0
457
Erik Chen75a2f492020-08-06 19:15:11 -0700458 # Check that the build matches the device. Lacros-chrome skips this check as
459 # it's currently board independent. This means that it's possible to deploy
460 # a build of lacros-chrome with a mismatched architecture. We don't try to
461 # prevent this developer foot-gun.
462 if not self.options.lacros:
463 self._CheckBoard()
Avery Musbach3edff0e2020-03-27 13:35:53 -0700464
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800465 # Ensure that the target directory exists before running parallel steps.
466 self._EnsureTargetDir()
467
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800468 logging.info('Preparing device')
Steve Funge984a532013-11-25 17:09:25 -0800469 steps = [self._GetDeviceInfo, self._CheckConnection,
Erik Chen75a2f492020-08-06 19:15:11 -0700470 self._MountRootfsAsWritable,
Steve Funge984a532013-11-25 17:09:25 -0800471 self._PrepareStagingDir]
Erik Chen75a2f492020-08-06 19:15:11 -0700472
473 # If this is a lacros build, we only want to restart ash-chrome if
474 # necessary, which is done below.
ChromeOS Developer78345852020-08-11 23:09:48 -0700475 if not self.options.lacros:
476 self._stopped_ui = True
Erik Chen75a2f492020-08-06 19:15:11 -0700477 steps += ([self._KillLacrosChrome] if self.options.lacros else
478 [self._KillAshChromeIfNeeded])
Ryan Cui7193a7e2013-04-26 14:15:19 -0700479 ret = parallel.RunParallelSteps(steps, halt_on_error=True,
480 return_values=True)
481 self._CheckDeviceFreeSpace(ret[0])
David James88e6f032013-03-02 08:13:20 -0800482
Steven Bennettsca73efa2018-07-10 13:36:56 -0700483 # If the root dir is not writable, try disabling rootfs verification.
484 # (We always do this by default so that developers can write to
485 # /etc/chriome_dev.conf and other directories in the rootfs).
486 if self._root_dir_is_still_readonly.is_set():
487 if self.options.noremove_rootfs_verification:
488 logging.warning('Skipping disable rootfs verification.')
489 elif not self._DisableRootfsVerification():
490 logging.warning('Failed to disable rootfs verification.')
491
492 # If the target dir is still not writable (i.e. the user opted out or the
493 # command failed), abort.
494 if not self.device.IsDirWritable(self.options.target_dir):
Erik Chen75a2f492020-08-06 19:15:11 -0700495 if self.options.startui and self._stopped_ui:
Steven Bennettsca73efa2018-07-10 13:36:56 -0700496 logging.info('Restarting Chrome...')
Mike Frysinger3459bf52020-03-31 00:52:11 -0400497 self.device.run('start ui')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700498 raise DeployFailure('Target location is not writable. Aborting.')
David James88e6f032013-03-02 08:13:20 -0800499
Thiago Goncales12793312013-05-23 11:26:17 -0700500 if self.options.mount_dir is not None:
501 self._MountTarget()
502
Erik Chen75a2f492020-08-06 19:15:11 -0700503 if self.options.lacros:
504 # Update /etc/chrome_dev.conf to include appropriate flags.
505 restart_ui = False
506 result = self.device.run(ENABLE_LACROS_VIA_CONF_COMMAND, shell=True)
507 if result.stdout.strip() == MODIFIED_CONF_FILE:
508 restart_ui = True
509 result = self.device.run(_SET_LACROS_PATH_VIA_CONF_COMMAND % {
510 'conf_file': _CONF_FILE, 'lacros_path': self.options.target_dir,
511 'modified_conf_file': MODIFIED_CONF_FILE}, shell=True)
512 if result.stdout.strip() == MODIFIED_CONF_FILE:
513 restart_ui = True
514
515 if restart_ui:
516 self._KillAshChromeIfNeeded()
517
David James88e6f032013-03-02 08:13:20 -0800518 # Actually deploy Chrome to the device.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700519 self._Deploy()
Ben Pastenee484b342020-06-30 18:29:27 -0700520 if self.options.deploy_test_binaries:
521 self._DeployTestBinaries()
Ryan Cui3045c5d2012-07-13 18:00:33 -0700522
523
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700524def ValidateStagingFlags(value):
525 """Convert formatted string to dictionary."""
526 return chrome_util.ProcessShellFlags(value)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700527
528
Steven Bennetts368c3e52016-09-23 13:05:21 -0700529def ValidateGnArgs(value):
530 """Convert GN_ARGS-formatted string to dictionary."""
531 return gn_helpers.FromGNArgs(value)
532
533
Ryan Cuie535b172012-10-19 18:25:03 -0700534def _CreateParser():
535 """Create our custom parser."""
Mike Frysingerc3061a62015-06-04 04:16:18 -0400536 parser = commandline.ArgumentParser(description=__doc__, caching=True)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700537
Ryan Cuia56a71e2012-10-18 18:40:35 -0700538 # TODO(rcui): Have this use the UI-V2 format of having source and target
539 # device be specified as positional arguments.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400540 parser.add_argument('--force', action='store_true', default=False,
Avery Musbach3edff0e2020-03-27 13:35:53 -0700541 help='Skip all prompts (such as the prompt for disabling '
542 'of rootfs verification). This may result in the '
543 'target machine being rebooted.')
Ryan Cuia0215a72013-02-14 16:20:45 -0800544 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
Mike Frysingerc3061a62015-06-04 04:16:18 -0400545 parser.add_argument('--board', default=sdk_board_env,
Mike Frysinger80de5012019-08-01 14:10:53 -0400546 help='The board the Chrome build is targeted for. When '
Mike Frysingerc3061a62015-06-04 04:16:18 -0400547 "in a 'cros chrome-sdk' shell, defaults to the SDK "
Mike Frysinger80de5012019-08-01 14:10:53 -0400548 'board.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400549 parser.add_argument('--build-dir', type='path',
550 help='The directory with Chrome build artifacts to '
551 'deploy from. Typically of format '
552 '<chrome_root>/out/Debug. When this option is used, '
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700553 'the GN_ARGS environment variable must be set.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400554 parser.add_argument('--target-dir', type='path',
555 default=None,
556 help='Target directory on device to deploy Chrome into.')
557 parser.add_argument('-g', '--gs-path', type='gs_path',
558 help='GS path that contains the chrome to deploy.')
Adrian Eldera2c548a2017-11-07 19:01:29 -0500559 parser.add_argument('--private-key', type='path', default=None,
560 help='An ssh private key to use when deploying to '
561 'a CrOS device.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400562 parser.add_argument('--nostartui', action='store_false', dest='startui',
563 default=True,
564 help="Don't restart the ui daemon after deployment.")
565 parser.add_argument('--nostrip', action='store_false', dest='dostrip',
566 default=True,
567 help="Don't strip binaries during deployment. Warning: "
568 'the resulting binaries will be very large!')
569 parser.add_argument('-p', '--port', type=int, default=remote.DEFAULT_SSH_PORT,
570 help='Port of the target device to connect to.')
571 parser.add_argument('-t', '--to',
572 help='The IP address of the CrOS device to deploy to.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400573 parser.add_argument('--mount-dir', type='path', default=None,
574 help='Deploy Chrome in target directory and bind it '
575 'to the directory specified by this flag.'
576 'Any existing mount on this directory will be '
577 'umounted first.')
578 parser.add_argument('--mount', action='store_true', default=False,
579 help='Deploy Chrome to default target directory and bind '
580 'it to the default mount directory.'
581 'Any existing mount on this directory will be '
582 'umounted first.')
Steven Bennettsca73efa2018-07-10 13:36:56 -0700583 parser.add_argument('--noremove-rootfs-verification', action='store_true',
584 default=False, help='Never remove rootfs verification.')
Ben Pastenee484b342020-06-30 18:29:27 -0700585 parser.add_argument('--deploy-test-binaries', action='store_true',
586 default=False,
587 help='Also deploy any test binaries to %s. Useful for '
588 'running any Tast tests that execute these '
589 'binaries.' % _CHROME_TEST_BIN_DIR)
Erik Chen75a2f492020-08-06 19:15:11 -0700590 parser.add_argument('--lacros', action='store_true', default=False,
591 help='Deploys lacros-chrome rather than ash-chrome.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700592
Mike Frysingerc3061a62015-06-04 04:16:18 -0400593 group = parser.add_argument_group('Advanced Options')
594 group.add_argument('-l', '--local-pkg-path', type='path',
595 help='Path to local chrome prebuilt package to deploy.')
596 group.add_argument('--sloppy', action='store_true', default=False,
597 help='Ignore when mandatory artifacts are missing.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700598 group.add_argument('--staging-flags', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400599 help=('Extra flags to control staging. Valid flags are - '
600 '%s' % ', '.join(chrome_util.STAGING_FLAGS)))
Steven Bennetts46a84c32016-08-12 15:20:46 -0700601 # TODO(stevenjb): Remove --strict entirely once removed from the ebuild.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400602 group.add_argument('--strict', action='store_true', default=False,
Steven Bennetts46a84c32016-08-12 15:20:46 -0700603 help='Deprecated. Default behavior is "strict". Use '
604 '--sloppy to omit warnings for missing optional '
605 'files.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400606 group.add_argument('--strip-flags', default=None,
607 help="Flags to call the 'strip' binutil tool with. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400608 'Overrides the default arguments.')
Mike Frysingerc3061a62015-06-04 04:16:18 -0400609 group.add_argument('--ping', action='store_true', default=False,
610 help='Ping the device before connection attempt.')
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700611 group.add_argument('--process-timeout', type=int,
612 default=KILL_PROC_MAX_WAIT,
613 help='Timeout for process shutdown.')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700614
Mike Frysingerc3061a62015-06-04 04:16:18 -0400615 group = parser.add_argument_group(
616 'Metadata Overrides (Advanced)',
617 description='Provide all of these overrides in order to remove '
618 'dependencies on metadata.json existence.')
619 group.add_argument('--target-tc', action='store', default=None,
620 help='Override target toolchain name, e.g. '
621 'x86_64-cros-linux-gnu')
622 group.add_argument('--toolchain-url', action='store', default=None,
623 help='Override toolchain url format pattern, e.g. '
624 '2014/04/%%(target)s-2014.04.23.220740.tar.xz')
Aviv Keshet1c986f32014-04-24 13:20:49 -0700625
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700626 # DEPRECATED: --gyp-defines is ignored, but retained for backwards
627 # compatibility. TODO(stevenjb): Remove once eliminated from the ebuild.
628 parser.add_argument('--gyp-defines', default=None, type=ValidateStagingFlags,
Mike Frysingerc3061a62015-06-04 04:16:18 -0400629 help=argparse.SUPPRESS)
Steven Bennetts368c3e52016-09-23 13:05:21 -0700630
631 # GN_ARGS (args.gn) used to build Chrome. Influences which files are staged
632 # when --build-dir is set. Defaults to reading from the GN_ARGS env variable.
633 # CURRENLY IGNORED, ADDED FOR FORWARD COMPATABILITY.
634 parser.add_argument('--gn-args', default=None, type=ValidateGnArgs,
635 help=argparse.SUPPRESS)
636
Ryan Cuia56a71e2012-10-18 18:40:35 -0700637 # Path of an empty directory to stage chrome artifacts to. Defaults to a
638 # temporary directory that is removed when the script finishes. If the path
639 # is specified, then it will not be removed.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400640 parser.add_argument('--staging-dir', type='path', default=None,
641 help=argparse.SUPPRESS)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700642 # Only prepare the staging directory, and skip deploying to the device.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400643 parser.add_argument('--staging-only', action='store_true', default=False,
644 help=argparse.SUPPRESS)
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700645 # Path to a binutil 'strip' tool to strip binaries with. The passed-in path
646 # is used as-is, and not normalized. Used by the Chrome ebuild to skip
647 # fetching the SDK toolchain.
Mike Frysingerc3061a62015-06-04 04:16:18 -0400648 parser.add_argument('--strip-bin', default=None, help=argparse.SUPPRESS)
Satoru Takabayashif2893002017-06-20 14:52:48 +0900649 parser.add_argument('--compress', action='store', default='auto',
650 choices=('always', 'never', 'auto'),
651 help='Choose the data compression behavior. Default '
652 'is set to "auto", that disables compression if '
653 'the target device has a gigabit ethernet port.')
Ryan Cuie535b172012-10-19 18:25:03 -0700654 return parser
Ryan Cui3045c5d2012-07-13 18:00:33 -0700655
Ryan Cuie535b172012-10-19 18:25:03 -0700656
657def _ParseCommandLine(argv):
658 """Parse args, and run environment-independent checks."""
659 parser = _CreateParser()
Mike Frysingerc3061a62015-06-04 04:16:18 -0400660 options = parser.parse_args(argv)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700661
Ryan Cuia56a71e2012-10-18 18:40:35 -0700662 if not any([options.gs_path, options.local_pkg_path, options.build_dir]):
663 parser.error('Need to specify either --gs-path, --local-pkg-path, or '
Ryan Cuief91e702013-02-04 12:06:36 -0800664 '--build-dir')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700665 if options.build_dir and any([options.gs_path, options.local_pkg_path]):
666 parser.error('Cannot specify both --build_dir and '
667 '--gs-path/--local-pkg-patch')
Erik Chen75a2f492020-08-06 19:15:11 -0700668 if options.lacros:
669 if options.board:
670 parser.error('--board is not supported with --lacros')
671 # The stripping implemented in this file rely on the cros-chrome-sdk, which
672 # is inappropriate for Lacros. Lacros stripping is currently not
673 # implemented.
674 if options.dostrip:
675 parser.error('--lacros requires --nostrip')
676 if options.mount_dir or options.mount:
677 parser.error('--lacros does not support --mount or --mount-dir')
678 if options.deploy_test_binaries:
679 parser.error('--lacros does not support --deploy-test-binaries')
680 if options.local_pkg_path:
681 parser.error('--lacros does not support --local-pkg-path')
682 else:
683 if not options.board:
684 parser.error('--board is required')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700685 if options.gs_path and options.local_pkg_path:
686 parser.error('Cannot specify both --gs-path and --local-pkg-path')
687 if not (options.staging_only or options.to):
Ryan Cui3045c5d2012-07-13 18:00:33 -0700688 parser.error('Need to specify --to')
Steven Bennetts46a84c32016-08-12 15:20:46 -0700689 if options.staging_flags and not options.build_dir:
690 parser.error('--staging-flags require --build-dir to be set.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700691
Steven Bennetts46a84c32016-08-12 15:20:46 -0700692 if options.strict:
693 logging.warning('--strict is deprecated.')
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700694 if options.gyp_defines:
695 logging.warning('--gyp-defines is deprecated.')
Thiago Goncales12793312013-05-23 11:26:17 -0700696
697 if options.mount or options.mount_dir:
698 if not options.target_dir:
699 options.target_dir = _CHROME_DIR_MOUNT
700 else:
701 if not options.target_dir:
Erik Chen75a2f492020-08-06 19:15:11 -0700702 options.target_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Thiago Goncales12793312013-05-23 11:26:17 -0700703
704 if options.mount and not options.mount_dir:
705 options.mount_dir = _CHROME_DIR
706
Mike Frysingerc3061a62015-06-04 04:16:18 -0400707 return options
Ryan Cui3045c5d2012-07-13 18:00:33 -0700708
709
Mike Frysingerc3061a62015-06-04 04:16:18 -0400710def _PostParseCheck(options):
Steve Funge984a532013-11-25 17:09:25 -0800711 """Perform some usage validation (after we've parsed the arguments).
Ryan Cui3045c5d2012-07-13 18:00:33 -0700712
713 Args:
Mike Frysinger5fe3f222016-09-01 00:14:16 -0400714 options: The options object returned by the cli parser.
Ryan Cui3045c5d2012-07-13 18:00:33 -0700715 """
Ryan Cuia56a71e2012-10-18 18:40:35 -0700716 if options.local_pkg_path and not os.path.isfile(options.local_pkg_path):
717 cros_build_lib.Die('%s is not a file.', options.local_pkg_path)
718
Steven Bennetts368c3e52016-09-23 13:05:21 -0700719 if not options.gn_args:
720 gn_env = os.getenv('GN_ARGS')
721 if gn_env is not None:
722 options.gn_args = gn_helpers.FromGNArgs(gn_env)
Steven Bennetts2ae83c72017-12-04 16:34:24 -0800723 logging.debug('GN_ARGS taken from environment: %s', options.gn_args)
Ryan Cuib623e7b2013-03-14 12:54:11 -0700724
Steven Bennetts60600462016-05-12 10:40:20 -0700725 if not options.staging_flags:
726 use_env = os.getenv('USE')
727 if use_env is not None:
728 options.staging_flags = ' '.join(set(use_env.split()).intersection(
729 chrome_util.STAGING_FLAGS))
730 logging.info('Staging flags taken from USE in environment: %s',
731 options.staging_flags)
732
Ryan Cuia56a71e2012-10-18 18:40:35 -0700733
Ryan Cui504db722013-01-22 11:48:01 -0800734def _FetchChromePackage(cache_dir, tempdir, gs_path):
Ryan Cuia56a71e2012-10-18 18:40:35 -0700735 """Get the chrome prebuilt tarball from GS.
736
Mike Frysinger02e1e072013-11-10 22:11:34 -0500737 Returns:
738 Path to the fetched chrome tarball.
Ryan Cuia56a71e2012-10-18 18:40:35 -0700739 """
David James9374aac2013-10-08 16:00:17 -0700740 gs_ctx = gs.GSContext(cache_dir=cache_dir, init_boto=True)
Mike Frysinger7ad4fd62014-02-11 16:53:56 -0500741 files = gs_ctx.LS(gs_path)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800742 files = [found for found in files if
743 _UrlBaseName(found).startswith('%s-' % constants.CHROME_PN)]
744 if not files:
745 raise Exception('No chrome package found at %s' % gs_path)
746 elif len(files) > 1:
747 # - Users should provide us with a direct link to either a stripped or
748 # unstripped chrome package.
749 # - In the case of being provided with an archive directory, where both
750 # stripped and unstripped chrome available, use the stripped chrome
751 # package.
752 # - Stripped chrome pkg is chromeos-chrome-<version>.tar.gz
753 # - Unstripped chrome pkg is chromeos-chrome-<version>-unstripped.tar.gz.
754 files = [f for f in files if not 'unstripped' in f]
755 assert len(files) == 1
756 logging.warning('Multiple chrome packages found. Using %s', files[0])
Ryan Cui777ff422012-12-07 13:12:54 -0800757
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800758 filename = _UrlBaseName(files[0])
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800759 logging.info('Fetching %s...', filename)
Ryan Cui4c2d42c2013-02-08 16:22:26 -0800760 gs_ctx.Copy(files[0], tempdir, print_cmd=False)
761 chrome_path = os.path.join(tempdir, filename)
762 assert os.path.exists(chrome_path)
763 return chrome_path
Ryan Cuia56a71e2012-10-18 18:40:35 -0700764
765
Ryan Cuif890a3e2013-03-07 18:57:06 -0800766@contextlib.contextmanager
767def _StripBinContext(options):
768 if not options.dostrip:
769 yield None
770 elif options.strip_bin:
771 yield options.strip_bin
772 else:
Ryan Cuia0215a72013-02-14 16:20:45 -0800773 sdk = cros_chrome_sdk.SDKFetcher(options.cache_dir, options.board)
Ryan Cui686ec052013-02-12 16:39:41 -0800774 components = (sdk.TARGET_TOOLCHAIN_KEY, constants.CHROME_ENV_TAR)
Aviv Keshet1c986f32014-04-24 13:20:49 -0700775 with sdk.Prepare(components=components, target_tc=options.target_tc,
776 toolchain_url=options.toolchain_url) as ctx:
Ryan Cui686ec052013-02-12 16:39:41 -0800777 env_path = os.path.join(ctx.key_map[constants.CHROME_ENV_TAR].path,
778 constants.CHROME_ENV_FILE)
779 strip_bin = osutils.SourceEnvironment(env_path, ['STRIP'])['STRIP']
780 strip_bin = os.path.join(ctx.key_map[sdk.TARGET_TOOLCHAIN_KEY].path,
781 'bin', os.path.basename(strip_bin))
Ryan Cuif890a3e2013-03-07 18:57:06 -0800782 yield strip_bin
783
784
Steve Funge984a532013-11-25 17:09:25 -0800785def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
Erik Chen75a2f492020-08-06 19:15:11 -0700786 chrome_dir=None):
Ryan Cuif890a3e2013-03-07 18:57:06 -0800787 """Place the necessary files in the staging directory.
788
789 The staging directory is the directory used to rsync the build artifacts over
790 to the device. Only the necessary Chrome build artifacts are put into the
791 staging directory.
792 """
Erik Chen75a2f492020-08-06 19:15:11 -0700793 if chrome_dir is None:
794 chrome_dir = LACROS_DIR if options.lacros else _CHROME_DIR
Ryan Cui5866be02013-03-18 14:12:00 -0700795 osutils.SafeMakedirs(staging_dir)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400796 os.chmod(staging_dir, 0o755)
Ryan Cuif890a3e2013-03-07 18:57:06 -0800797 if options.build_dir:
798 with _StripBinContext(options) as strip_bin:
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700799 strip_flags = (None if options.strip_flags is None else
800 shlex.split(options.strip_flags))
Ryan Cui686ec052013-02-12 16:39:41 -0800801 chrome_util.StageChromeFromBuildDir(
Steven Bennetts46a84c32016-08-12 15:20:46 -0700802 staging_dir, options.build_dir, strip_bin,
Steven Bennettsda8d9f02016-09-23 13:25:45 -0700803 sloppy=options.sloppy, gn_args=options.gn_args,
Ryan Cui5b7c2ed2013-03-18 18:45:46 -0700804 staging_flags=options.staging_flags,
Steve Funge984a532013-11-25 17:09:25 -0800805 strip_flags=strip_flags, copy_paths=copy_paths)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700806 else:
807 pkg_path = options.local_pkg_path
808 if options.gs_path:
Ryan Cui504db722013-01-22 11:48:01 -0800809 pkg_path = _FetchChromePackage(options.cache_dir, tempdir,
810 options.gs_path)
Ryan Cuia56a71e2012-10-18 18:40:35 -0700811
812 assert pkg_path
Ryan Cui4d6b0db2013-02-28 15:13:24 -0800813 logging.info('Extracting %s...', pkg_path)
Ryan Cui5866be02013-03-18 14:12:00 -0700814 # Extract only the ./opt/google/chrome contents, directly into the staging
815 # dir, collapsing the directory hierarchy.
Steve Funge984a532013-11-25 17:09:25 -0800816 if pkg_path[-4:] == '.zip':
Mike Frysinger45602c72019-09-22 02:15:11 -0400817 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800818 ['unzip', '-X', pkg_path, _ANDROID_DIR_EXTRACT_PATH, '-d',
819 staging_dir])
820 for filename in glob.glob(os.path.join(staging_dir, 'system/chrome/*')):
821 shutil.move(filename, staging_dir)
822 osutils.RmDir(os.path.join(staging_dir, 'system'), ignore_missing=True)
823 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400824 cros_build_lib.dbg_run(
Steve Funge984a532013-11-25 17:09:25 -0800825 ['tar', '--strip-components', '4', '--extract',
826 '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
827 cwd=staging_dir)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700828
Ryan Cui71aa8de2013-04-19 16:12:55 -0700829
Ryan Cui3045c5d2012-07-13 18:00:33 -0700830def main(argv):
Mike Frysingerc3061a62015-06-04 04:16:18 -0400831 options = _ParseCommandLine(argv)
832 _PostParseCheck(options)
Ryan Cui3045c5d2012-07-13 18:00:33 -0700833
Aviv Keshet01a82e92017-03-02 17:39:59 -0800834 with osutils.TempDir(set_global=True) as tempdir:
835 staging_dir = options.staging_dir
836 if not staging_dir:
837 staging_dir = os.path.join(tempdir, 'chrome')
Ryan Cuia56a71e2012-10-18 18:40:35 -0700838
Aviv Keshet01a82e92017-03-02 17:39:59 -0800839 deploy = DeployChrome(options, tempdir, staging_dir)
840 try:
841 deploy.Perform()
842 except failures_lib.StepFailure as ex:
843 raise SystemExit(str(ex).strip())
844 deploy.Cleanup()