Blame plugins/builddep.py

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