Blame plugins/builddep.py

Packit 3a9065
# builddep.py
Packit 3a9065
# Install all the deps needed to build this package.
Packit 3a9065
#
Packit 3a9065
# Copyright (C) 2013-2015  Red Hat, Inc.
Packit 3a9065
# Copyright (C) 2015 Igor Gnatenko
Packit 3a9065
#
Packit 3a9065
# This copyrighted material is made available to anyone wishing to use,
Packit 3a9065
# modify, copy, or redistribute it subject to the terms and conditions of
Packit 3a9065
# the GNU General Public License v.2, or (at your option) any later version.
Packit 3a9065
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit 3a9065
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit 3a9065
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit 3a9065
# Public License for more details.  You should have received a copy of the
Packit 3a9065
# GNU General Public License along with this program; if not, write to the
Packit 3a9065
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 3a9065
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit 3a9065
# source code or documentation are not subject to the GNU General Public
Packit 3a9065
# License and may only be used or replicated with the express permission of
Packit 3a9065
# Red Hat, Inc.
Packit 3a9065
#
Packit 3a9065
Packit 3a9065
from __future__ import absolute_import
Packit 3a9065
from __future__ import unicode_literals
Packit 3a9065
from dnfpluginscore import _, logger
Packit 3a9065
Packit 3a9065
import argparse
Packit 3a9065
import dnf
Packit 3a9065
import dnf.cli
Packit 3a9065
import dnf.exceptions
Packit 3a9065
import dnf.rpm.transaction
Packit 3a9065
import dnf.yum.rpmtrans
Packit 3a9065
import libdnf.repo
Packit 3a9065
import os
Packit 3a9065
import rpm
Packit 3a9065
import shutil
Packit 3a9065
import tempfile
Packit 3a9065
Packit 3a9065
Packit 3a9065
@dnf.plugin.register_command
Packit 3a9065
class BuildDepCommand(dnf.cli.Command):
Packit 3a9065
Packit 3a9065
    aliases = ('builddep', 'build-dep')
Packit 3a9065
    msg = "Install build dependencies for package or spec file"
Packit 3a9065
    summary = _(msg)
Packit 3a9065
    usage = _("[PACKAGE|PACKAGE.spec]")
Packit 3a9065
Packit 3a9065
    def __init__(self, cli):
Packit 3a9065
        super(BuildDepCommand, self).__init__(cli)
Packit 3a9065
        self._rpm_ts = dnf.rpm.transaction.initReadOnlyTransaction()
Packit 3a9065
        self.tempdirs = []
Packit 3a9065
Packit 3a9065
    def __del__(self):
Packit 3a9065
        for temp_dir in self.tempdirs:
Packit 3a9065
            shutil.rmtree(temp_dir)
Packit 3a9065
Packit 3a9065
    def _download_remote_file(self, pkgspec):
Packit 3a9065
        """
Packit 3a9065
        In case pkgspec is a remote URL, download it to a temporary location
Packit 3a9065
        and use the temporary file instead.
Packit 3a9065
        """
Packit 3a9065
        location = dnf.pycomp.urlparse.urlparse(pkgspec)
Packit 3a9065
        if location[0] in ('file', ''):
Packit 3a9065
            # just strip the file:// prefix
Packit 3a9065
            return location.path
Packit 3a9065
Packit 3a9065
        downloader = libdnf.repo.Downloader()
Packit 3a9065
        temp_dir = tempfile.mkdtemp(prefix="dnf_builddep_")
Packit 3a9065
        temp_file = os.path.join(temp_dir, os.path.basename(pkgspec))
Packit 3a9065
        self.tempdirs.append(temp_dir)
Packit 3a9065
Packit 3a9065
        temp_fo = open(temp_file, "wb+")
Packit 3a9065
        try:
Packit 3a9065
            downloader.downloadURL(self.base.conf._config, pkgspec, temp_fo.fileno())
Packit 3a9065
        except RuntimeError as ex:
Packit 3a9065
            raise
Packit 3a9065
        finally:
Packit 3a9065
            temp_fo.close()
Packit 3a9065
        return temp_file
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        def macro_def(arg):
Packit 3a9065
            arglist = arg.split(None, 1) if arg else []
Packit 3a9065
            if len(arglist) < 2:
Packit 3a9065
                msg = _("'%s' is not of the format 'MACRO EXPR'") % arg
Packit 3a9065
                raise argparse.ArgumentTypeError(msg)
Packit 3a9065
            return arglist
Packit 3a9065
Packit 3a9065
        parser.add_argument('packages', nargs='+', metavar='package',
Packit 3a9065
                            help=_('packages with builddeps to install'))
Packit 3a9065
        parser.add_argument('-D', '--define', action='append', default=[],
Packit 3a9065
                            metavar="'MACRO EXPR'", type=macro_def,
Packit 3a9065
                            help=_('define a macro for spec file parsing'))
Packit 3a9065
        parser.add_argument('--skip-unavailable', action='store_true', default=False,
Packit 3a9065
                            help=_('skip build dependencies not available in repositories'))
Packit 3a9065
        ptype = parser.add_mutually_exclusive_group()
Packit 3a9065
        ptype.add_argument('--spec', action='store_true',
Packit 3a9065
                            help=_('treat commandline arguments as spec files'))
Packit 3a9065
        ptype.add_argument('--srpm', action='store_true',
Packit 3a9065
                            help=_('treat commandline arguments as source rpm'))
Packit 3a9065
Packit 3a9065
    def pre_configure(self):
Packit 3a9065
        if not self.opts.rpmverbosity:
Packit 3a9065
            self.opts.rpmverbosity = 'error'
Packit 3a9065
Packit 3a9065
    def configure(self):
Packit 3a9065
        demands = self.cli.demands
Packit 3a9065
        demands.available_repos = True
Packit 3a9065
        demands.resolving = True
Packit 3a9065
        demands.root_user = True
Packit 3a9065
        demands.sack_activation = True
Packit 3a9065
Packit 3a9065
        # enable source repos only if needed
Packit 3a9065
        if not (self.opts.spec or self.opts.srpm):
Packit 3a9065
            for pkgspec in self.opts.packages:
Packit 3a9065
                if not (pkgspec.endswith('.src.rpm')
Packit 3a9065
                        or pkgspec.endswith('.nosrc.rpm')
Packit 3a9065
                        or pkgspec.endswith('.spec')):
Packit 3a9065
                    self.base.repos.enable_source_repos()
Packit 3a9065
                    break
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        rpmlog = dnf.yum.rpmtrans.RPMTransaction(self.base)
Packit 3a9065
        # Push user-supplied macro definitions for spec parsing
Packit 3a9065
        for macro in self.opts.define:
Packit 3a9065
            rpm.addMacro(macro[0], macro[1])
Packit 3a9065
Packit 3a9065
        pkg_errors = False
Packit 3a9065
        for pkgspec in self.opts.packages:
Packit 3a9065
            pkgspec = self._download_remote_file(pkgspec)
Packit 3a9065
            try:
Packit 3a9065
                if self.opts.srpm:
Packit 3a9065
                    self._src_deps(pkgspec)
Packit 3a9065
                elif self.opts.spec:
Packit 3a9065
                    self._spec_deps(pkgspec)
Packit 3a9065
                elif pkgspec.endswith('.src.rpm') or pkgspec.endswith('nosrc.rpm'):
Packit 3a9065
                    self._src_deps(pkgspec)
Packit 3a9065
                elif pkgspec.endswith('.spec'):
Packit 3a9065
                    self._spec_deps(pkgspec)
Packit 3a9065
                else:
Packit 3a9065
                    self._remote_deps(pkgspec)
Packit 3a9065
            except dnf.exceptions.Error as e:
Packit 3a9065
                for line in rpmlog.messages():
Packit 3a9065
                    logger.error(_("RPM: {}").format(line))
Packit 3a9065
                logger.error(e)
Packit 3a9065
                pkg_errors = True
Packit 3a9065
Packit 3a9065
        # Pop user macros so they don't affect future rpm calls
Packit 3a9065
        for macro in self.opts.define:
Packit 3a9065
            rpm.delMacro(macro[0])
Packit 3a9065
Packit 3a9065
        if pkg_errors:
Packit 3a9065
            raise dnf.exceptions.Error(_("Some packages could not be found."))
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def _rpm_dep2reldep_str(rpm_dep):
Packit 3a9065
        return rpm_dep.DNEVR()[2:]
Packit 3a9065
Packit 3a9065
    def _install(self, reldep_str):
Packit 3a9065
        # Try to find something by provides
Packit 3a9065
        sltr = dnf.selector.Selector(self.base.sack)
Packit 3a9065
        sltr.set(provides=reldep_str)
Packit 3a9065
        found = sltr.matches()
Packit 3a9065
        if not found and reldep_str.startswith("/"):
Packit 3a9065
            # Nothing matches by provides and since it's file, try by files
Packit 3a9065
            sltr = dnf.selector.Selector(self.base.sack)
Packit 3a9065
            sltr.set(file=reldep_str)
Packit 3a9065
            found = sltr.matches()
Packit 3a9065
Packit 3a9065
        if not found and not reldep_str.startswith("("):
Packit 3a9065
            # No provides, no files
Packit 3a9065
            # Richdeps can have no matches but it could be correct (solver must decide later)
Packit 3a9065
            msg = _("No matching package to install: '%s'")
Packit 3a9065
            logger.warning(msg, reldep_str)
Packit 3a9065
            return self.opts.skip_unavailable is True
Packit 3a9065
Packit 3a9065
        if found:
Packit 3a9065
            already_inst = self.base._sltr_matches_installed(sltr)
Packit 3a9065
            if already_inst:
Packit 3a9065
                for package in already_inst:
Packit 3a9065
                    dnf.base._msg_installed(package)
Packit 3a9065
        self.base._goal.install(select=sltr, optional=False)
Packit 3a9065
        return True
Packit 3a9065
Packit 3a9065
    def _src_deps(self, src_fn):
Packit 3a9065
        fd = os.open(src_fn, os.O_RDONLY)
Packit 3a9065
        try:
Packit 3a9065
            h = self._rpm_ts.hdrFromFdno(fd)
Packit 3a9065
        except rpm.error as e:
Packit 3a9065
            if str(e) == 'error reading package header':
Packit 3a9065
                e = _("Failed to open: '%s', not a valid source rpm file.") % src_fn
Packit 3a9065
            os.close(fd)
Packit 3a9065
            raise dnf.exceptions.Error(e)
Packit 3a9065
        os.close(fd)
Packit 3a9065
        ds = h.dsFromHeader('requirename')
Packit 3a9065
        done = True
Packit 3a9065
        for dep in ds:
Packit 3a9065
            reldep_str = self._rpm_dep2reldep_str(dep)
Packit 3a9065
            if reldep_str.startswith('rpmlib('):
Packit 3a9065
                continue
Packit 3a9065
            done &= self._install(reldep_str)
Packit 3a9065
Packit 3a9065
        if not done:
Packit 3a9065
            err = _("Not all dependencies satisfied")
Packit 3a9065
            raise dnf.exceptions.Error(err)
Packit 3a9065
Packit 3a9065
    def _spec_deps(self, spec_fn):
Packit 3a9065
        try:
Packit 3a9065
            spec = rpm.spec(spec_fn)
Packit 3a9065
        except ValueError as ex:
Packit 3a9065
            msg = _("Failed to open: '%s', not a valid spec file: %s") % (
Packit 3a9065
                    spec_fn, ex)
Packit 3a9065
            raise dnf.exceptions.Error(msg)
Packit 3a9065
        done = True
Packit 3a9065
        for dep in rpm.ds(spec.sourceHeader, 'requires'):
Packit 3a9065
            reldep_str = self._rpm_dep2reldep_str(dep)
Packit 3a9065
            done &= self._install(reldep_str)
Packit 3a9065
Packit 3a9065
        if not done:
Packit 3a9065
            err = _("Not all dependencies satisfied")
Packit 3a9065
            raise dnf.exceptions.Error(err)
Packit 3a9065
Packit 3a9065
    def _remote_deps(self, package):
Packit 3a9065
        available = dnf.subject.Subject(package).get_best_query(
Packit 3a9065
                        self.base.sack).filter(arch__neq="src")
Packit 3a9065
        sourcenames = list({pkg.source_name for pkg in available})
Packit 3a9065
        pkgs = self.base.sack.query().available().filter(
Packit 3a9065
                name=(sourcenames + [package]), arch="src").latest().run()
Packit 3a9065
        if not pkgs:
Packit 3a9065
            raise dnf.exceptions.Error(_('no package matched: %s') % package)
Packit 3a9065
        done = True
Packit 3a9065
        for pkg in pkgs:
Packit 3a9065
            for req in pkg.requires:
Packit 3a9065
                done &= self._install(str(req))
Packit 3a9065
Packit 3a9065
        if not done:
Packit 3a9065
            err = _("Not all dependencies satisfied")
Packit 3a9065
            raise dnf.exceptions.Error(err)