blob: 4cf41d152364e46c7535764aee22d5c46daec657 [file] [log] [blame]
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""cros deploy: Deploy the packages onto the target device."""
import os
import logging
from chromite import cros
from chromite.buildbot import portage_utilities
from chromite.lib import cros_build_lib
from chromite.lib import remote_access
@cros.CommandDecorator('deploy')
class DeployCommand(cros.CrosCommand):
"""Deploy the requested packages to the target device.
This command assumes the requested packages are already built in the
chroot. This command needs to run inside the chroot for inspecting
the installed packages.
Note: If the rootfs on your device is read-only, this command
remounts it as read-write. If the rootfs verification is enabled on
your device, this command disables it.
"""
EPILOG = """
To deploy packages:
cros deploy device power_manager cherrypy
To uninstall packages:
cros deploy --unmerge cherrypy
For more information of cros build usage:
cros build -h
"""
DEVICE_WORK_DIR = '/tmp/cros-deploy'
# Override base class property to enable stats upload.
upload_stats = True
def __init__(self, options):
"""Initializes DeployCommand."""
cros.CrosCommand.__init__(self, options)
self.emerge = True
@classmethod
def AddParser(cls, parser):
"""Add a parser."""
super(cls, DeployCommand).AddParser(parser)
parser.add_argument(
'device', help='IP address of the target device.')
parser.add_argument(
'packages', help='Packages to install.', nargs='+')
parser.add_argument(
'--board', default=None, help='The board to use. By default it is '
'automatically detected. You can override the detected board with '
'this option.')
parser.add_argument(
'--unmerge', dest='emerge', action='store_false', default=True,
help='Unmerge requested packages.')
parser.add_argument(
'--emerge-args', default=None,
help='Extra arguments to pass to emerge.')
def GetLatestPackage(self, board, pkg):
"""Returns the path to the latest |pkg| for |board|."""
sysroot = cros_build_lib.GetSysroot(board=board)
cpv = portage_utilities.SplitCPV(pkg, strict=False)
category = cpv.category
package_name = cpv.package
version = cpv.version
if not category:
# If category is not given, find possible matches across all categories.
matches = portage_utilities.FindPackageNameMatches(
package_name, sysroot=sysroot)
if not matches:
raise ValueError('Package %s is not installed!' % pkg)
idx = 0
if len(matches) > 1:
# Ask user to pick among multiple matches.
idx = cros_build_lib.GetChoice(
'Multiple matches found for %s: ' % pkg,
[os.path.join(x.category, x.package) for x in matches])
category = matches[idx].category
if not version:
# If version is not given, pick the best visible binary package.
try:
bv_cpv = portage_utilities.BestVisible(
os.path.join(category, package_name),
board=board, pkg_type='binary')
version = bv_cpv.version
except cros_build_lib.RunCommandError:
raise ValueError('Package %s is not installed!' % pkg)
return portage_utilities.GetBinaryPackagePath(
category, package_name, version, sysroot=sysroot)
def _Emerge(self, device, board, pkg, extra_args=None):
"""Copies |pkg| to |device| and emerges it.
Args:
device: A ChromiumOSDevice object.
board: The board to use for retrieving |pkg|.
pkg: A package name.
extra_args: Extra arguments to pass to emerge.
"""
latest_pkg = self.GetLatestPackage(board, pkg)
if not latest_pkg:
cros_build_lib.Die('Missing package %s.' % pkg)
pkgroot = os.path.join(device.work_dir, 'packages')
pkg_name = os.path.basename(latest_pkg)
pkg_dirname = os.path.basename(os.path.dirname(latest_pkg))
pkg_dir = os.path.join(pkgroot, pkg_dirname)
device.RunCommand(['mkdir', '-p', pkg_dir])
logging.info('Copying %s to device...', latest_pkg)
device.CopyToDevice(latest_pkg, pkg_dir)
logging.info('Installing %s...', latest_pkg)
pkg_path = os.path.join(pkg_dir, pkg_name)
extra_env = {'FEATURES': '-sandbox', 'PKGDIR': pkgroot}
cmd = ['emerge', '--usepkg', pkg_path]
if extra_args:
cmd.append(extra_args)
try:
result = device.RunCommand(cmd, extra_env=extra_env)
logging.debug(result.output)
except Exception:
logging.error('Failed to emerge package %s', pkg)
raise
else:
logging.info('%s has been installed.', pkg)
def _Unmerge(self, device, pkg):
"""Unmerges |pkg| on |device|.
Args:
device: A RemoteDevice object.
pkg: A package name.
"""
logging.info('Unmerging %s...', pkg)
cmd = ['emerge', '--unmerge', pkg]
try:
result = device.RunCommand(cmd)
logging.debug(result.output)
except Exception:
logging.error('Failed to unmerge package %s', pkg)
raise
else:
logging.info('%s has been uninstalled.', pkg)
def _ReadOptions(self):
"""Processes options and set variables."""
self.emerge = self.options.emerge
def Run(self):
"""Run cros deploy."""
cros_build_lib.AssertInsideChroot()
self._ReadOptions()
try:
with remote_access.ChromiumOSDeviceHandler(
self.options.device, work_dir=self.DEVICE_WORK_DIR) as device:
board = cros_build_lib.GetBoard(device_board=device.board,
override_board=self.options.board)
logging.info('Board is %s', board)
if not device.MountRootfsReadWrite():
cros_build_lib.Die('Cannot remount rootfs as read-write. Exiting...')
for pkg in self.options.packages:
if self.emerge:
self._Emerge(device, board, pkg,
extra_args=self.options.emerge_args)
else:
self._Unmerge(device, pkg)
except Exception:
logging.error('Cros Deploy terminated before completing!')
raise
else:
logging.info('All packages are processed.')