1 # Copyright (c) 2014 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.
5 """cros deploy: Deploy the packages onto the target device."""
7 from __future__ import print_function
13 from chromite import cros
14 from chromite.lib import cros_build_lib
15 from chromite.lib import portage_util
16 from chromite.lib import remote_access
19 @cros.CommandDecorator('deploy')
20 class DeployCommand(cros.CrosCommand):
21 """Deploy the requested packages to the target device.
23 This command assumes the requested packages are already built in the
24 chroot. This command needs to run inside the chroot for inspecting
25 the installed packages.
27 Note: If the rootfs on your device is read-only, this command
28 remounts it as read-write. If the rootfs verification is enabled on
29 your device, this command disables it.
34 cros deploy device power_manager cherrypy
35 cros deploy device /path/to/package
37 To uninstall packages:
38 cros deploy --unmerge cherrypy
40 For more information of cros build usage:
44 DEVICE_BASE_DIR = '/usr/local/tmp/cros-deploy'
45 # This is defined in src/platform/dev/builder.py
46 STRIPPED_PACKAGES_DIR = 'stripped-packages'
48 # Override base class property to enable stats upload.
51 def __init__(self, options):
52 """Initializes DeployCommand."""
53 cros.CrosCommand.__init__(self, options)
56 self.clean_binpkg = True
57 self.ssh_hostname = None
59 self.ssh_username = None
60 self.ssh_private_key = None
61 # The installation root of packages.
66 def AddParser(cls, parser):
68 super(cls, DeployCommand).AddParser(parser)
70 'device', help='IP[:port] address of the target device.')
72 'packages', help='Packages to install. You can specify '
73 '[category/]package[:slot] or the path to the binary package.',
76 '--board', default=None, help='The board to use. By default it is '
77 'automatically detected. You can override the detected board with '
80 '--no-strip', dest='strip', action='store_false', default=True,
81 help='Do not run strip_package to filter out preset paths in the '
82 'package. Stripping removes debug symbol files and reduces the size '
83 'of the package significantly. Defaults to always strip.')
85 '--unmerge', dest='emerge', action='store_false', default=True,
86 help='Unmerge requested packages.')
88 '--root', default='/',
89 help="Package installation root, e.g. '/' or '/usr/local'"
92 '--no-clean-binpkg', dest='clean_binpkg', action='store_false',
93 default=True, help='Do not clean outdated binary packages. '
94 ' Defaults to always clean.')
96 '--emerge-args', default=None,
97 help='Extra arguments to pass to emerge.')
99 '--private-key', type='path', default=None,
100 help='SSH identify file (private key).')
102 '--no-ping', dest='ping', action='store_false', default=True,
103 help='Do not ping the device before attempting to connect to it.')
105 def GetLatestPackage(self, board, pkg):
106 """Returns the path to the latest |pkg| for |board|."""
107 sysroot = cros_build_lib.GetSysroot(board=board)
108 matches = portage_util.FindPackageNameMatches(pkg, board=board)
110 raise ValueError('Package %s is not installed!' % pkg)
114 # Ask user to pick among multiple matches.
115 idx = cros_build_lib.GetChoice(
116 'Multiple matches found for %s: ' % pkg,
117 [os.path.join(x.category, x.pv) for x in matches])
123 cros_build_lib.RunCommand(
124 ['strip_package', '--board', board,
125 os.path.join(cpv.category, '%s' % (cpv.pv))])
126 packages_dir = self.STRIPPED_PACKAGES_DIR
127 except cros_build_lib.RunCommandError:
128 logging.error('Cannot strip package %s', pkg)
131 return portage_util.GetBinaryPackagePath(
132 cpv.category, cpv.package, cpv.version, sysroot=sysroot,
133 packages_dir=packages_dir)
135 def _Emerge(self, device, board, pkg, root, extra_args=None):
136 """Copies |pkg| to |device| and emerges it.
139 device: A ChromiumOSDevice object.
140 board: The board to use for retrieving |pkg|.
142 root: The installation root of |pkg|.
143 extra_args: Extra arguments to pass to emerge.
145 if os.path.isfile(pkg):
148 latest_pkg = self.GetLatestPackage(board, pkg)
151 cros_build_lib.Die('Missing package %s.' % pkg)
153 pkgroot = os.path.join(device.work_dir, 'packages')
154 pkg_name = os.path.basename(latest_pkg)
155 pkg_dirname = os.path.basename(os.path.dirname(latest_pkg))
156 pkg_dir = os.path.join(pkgroot, pkg_dirname)
157 device.RunCommand(['mkdir', '-p', pkg_dir], remote_sudo=True)
159 logging.info('Copying %s to device...', latest_pkg)
160 device.CopyToDevice(latest_pkg, pkg_dir, remote_sudo=True)
162 portage_tmpdir = os.path.join(device.work_dir, 'portage-tmp')
163 device.RunCommand(['mkdir', '-p', portage_tmpdir], remote_sudo=True)
164 logging.info('Use portage temp dir %s', portage_tmpdir)
166 logging.info('Installing %s...', latest_pkg)
167 pkg_path = os.path.join(pkg_dir, pkg_name)
169 # We set PORTAGE_CONFIGROOT to '/usr/local' because by default all
170 # chromeos-base packages will be skipped due to the configuration
171 # in /etc/protage/make.profile/package.provided. However, there is
172 # a known bug that /usr/local/etc/portage is not setup properly
173 # (crbug.com/312041). This does not affect `cros deploy` because
174 # we do not use the preset PKGDIR.
176 'FEATURES': '-sandbox',
178 'PORTAGE_CONFIGROOT': '/usr/local',
179 'PORTAGE_TMPDIR': portage_tmpdir,
180 'PORTDIR': device.work_dir,
181 'CONFIG_PROTECT': '-*',
183 cmd = ['emerge', '--usepkg', pkg_path]
184 cmd.append('--root=%s' % root)
186 cmd.append(extra_args)
189 # Always showing the emerge output for clarity.
190 device.RunCommand(cmd, extra_env=extra_env, remote_sudo=True,
191 capture_output=False, debug_level=logging.INFO)
193 logging.error('Failed to emerge package %s', pkg)
196 logging.info('%s has been installed.', pkg)
198 # Free up the space for other packages.
199 device.RunCommand(['rm', '-rf', portage_tmpdir, pkg_dir],
200 error_code_ok=True, remote_sudo=True)
202 def _Unmerge(self, device, pkg, root):
203 """Unmerges |pkg| on |device|.
206 device: A RemoteDevice object.
208 root: The installation root of |pkg|.
210 logging.info('Unmerging %s...', pkg)
211 cmd = ['qmerge', '--yes']
212 # Check if qmerge is available on the device. If not, use emerge.
213 if device.RunCommand(
214 ['qmerge', '--version'], error_code_ok=True).returncode != 0:
217 cmd.extend(['--unmerge', pkg, '--root=%s' % root])
219 # Always showing the qmerge/emerge output for clarity.
220 device.RunCommand(cmd, capture_output=False, remote_sudo=True,
221 debug_level=logging.INFO)
223 logging.error('Failed to unmerge package %s', pkg)
226 logging.info('%s has been uninstalled.', pkg)
228 def _IsPathWritable(self, device, path):
229 """Returns True if |path| on |device| is writable."""
230 tmp_file = os.path.join(path, 'tmp.cros_flash')
231 result = device.RunCommand(['touch', tmp_file], remote_sudo=True,
232 error_code_ok=True, capture_output=True)
234 if result.returncode != 0:
237 device.RunCommand(['rm', tmp_file], error_code_ok=True, remote_sudo=True)
241 def _ReadOptions(self):
242 """Processes options and set variables."""
243 self.emerge = self.options.emerge
244 self.strip = self.options.strip
245 self.clean_binpkg = self.options.clean_binpkg
246 self.root = self.options.root
247 self.ping = self.options.ping
248 device = self.options.device
249 # pylint: disable=E1101
250 if urlparse.urlparse(device).scheme == '':
251 # For backward compatibility, prepend ssh:// ourselves.
252 device = 'ssh://%s' % device
254 parsed = urlparse.urlparse(device)
256 if parsed.scheme == 'ssh':
257 self.ssh_hostname = parsed.hostname
258 self.ssh_username = parsed.username
259 self.ssh_port = parsed.port
260 self.ssh_private_key = self.options.private_key
262 cros_build_lib.Die('Does not support device %s' % self.options.device)
265 """Run cros deploy."""
266 cros_build_lib.AssertInsideChroot()
269 with remote_access.ChromiumOSDeviceHandler(
270 self.ssh_hostname, port=self.ssh_port, username=self.ssh_username,
271 private_key=self.ssh_private_key, base_dir=self.DEVICE_BASE_DIR,
272 ping=self.ping) as device:
273 board = cros_build_lib.GetBoard(device_board=device.board,
274 override_board=self.options.board)
275 logging.info('Board is %s', board)
277 if self.clean_binpkg:
278 logging.info('Cleaning outdated binary packages for %s', board)
279 portage_util.CleanOutdatedBinaryPackages(board)
281 if not self._IsPathWritable(device, self.root):
282 # Only remounts rootfs if the given root is not writable.
283 if not device.MountRootfsReadWrite():
284 cros_build_lib.Die('Cannot remount rootfs as read-write. Exiting.')
286 for pkg in self.options.packages:
288 self._Emerge(device, board, pkg, self.root,
289 extra_args=self.options.emerge_args)
291 self._Unmerge(device, pkg, self.root)
293 except (Exception, KeyboardInterrupt) as e:
295 logging.error('Cros Deploy terminated before completing!')
296 if self.options.debug:
299 logging.info('Cros Deploy completed successfully.')