Blame plugins/repoclosure.py

Packit 3a9065
# repoclosure.py
Packit 3a9065
# DNF plugin adding a command to display a list of unresolved dependencies
Packit 3a9065
# for repositories.
Packit 3a9065
#
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 _
Packit 3a9065
Packit 3a9065
import dnf.cli
Packit 3a9065
Packit 3a9065
Packit 3a9065
class RepoClosure(dnf.Plugin):
Packit 3a9065
Packit 3a9065
    name = "repoclosure"
Packit 3a9065
Packit 3a9065
    def __init__(self, base, cli):
Packit 3a9065
        super(RepoClosure, self).__init__(base, cli)
Packit 3a9065
        if cli is None:
Packit 3a9065
            return
Packit 3a9065
        cli.register_command(RepoClosureCommand)
Packit 3a9065
Packit 3a9065
Packit 3a9065
class RepoClosureCommand(dnf.cli.Command):
Packit 3a9065
    aliases = ("repoclosure",)
Packit 3a9065
    summary = _("Display a list of unresolved dependencies for repositories")
Packit 3a9065
Packit 3a9065
    def configure(self):
Packit 3a9065
        demands = self.cli.demands
Packit 3a9065
        demands.sack_activation = True
Packit 3a9065
        demands.available_repos = True
Packit 3a9065
        if self.opts.repo:
Packit 3a9065
            for repo in self.base.repos.all():
Packit 3a9065
                if repo.id not in self.opts.repo and repo.id not in self.opts.check:
Packit 3a9065
                    repo.disable()
Packit 3a9065
                else:
Packit 3a9065
                    repo.enable()
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        if self.opts.arches:
Packit 3a9065
            unresolved = self._get_unresolved(self.opts.arches)
Packit 3a9065
        else:
Packit 3a9065
            unresolved = self._get_unresolved()
Packit 3a9065
        for pkg in sorted(unresolved.keys()):
Packit 3a9065
            print("package: {} from {}".format(str(pkg), pkg.reponame))
Packit 3a9065
            print("  unresolved deps:")
Packit 3a9065
            for dep in unresolved[pkg]:
Packit 3a9065
                print("    {}".format(dep))
Packit 3a9065
        if len(unresolved) > 0:
Packit 3a9065
            msg = _("Repoclosure ended with unresolved dependencies.")
Packit 3a9065
            raise dnf.exceptions.Error(msg)
Packit 3a9065
Packit 3a9065
    def _get_unresolved(self, arch=None):
Packit 3a9065
        unresolved = {}
Packit 3a9065
        deps = set()
Packit 3a9065
Packit 3a9065
        # We have two sets of packages, available and to_check:
Packit 3a9065
        # * available is the set of packages used to satisfy dependencies
Packit 3a9065
        # * to_check is the set of packages we are checking the dependencies of
Packit 3a9065
        #
Packit 3a9065
        # to_check can be a subset of available if the --arch, --best, --check,
Packit 3a9065
        # --newest, or --pkg options are used
Packit 3a9065
        #
Packit 3a9065
        # --arch:   only packages matching arch are checked
Packit 3a9065
        # --best:   available only contains the latest packages per arch across all repos
Packit 3a9065
        # --check:  only check packages in the specified repo(s)
Packit 3a9065
        # --newest: only consider the latest versions of a package from each repo
Packit 3a9065
        # --pkg:    only check the specified packages
Packit 3a9065
        #
Packit 3a9065
        # Relationship of --best and --newest:
Packit 3a9065
        #
Packit 3a9065
        # Pkg Set   | Neither |  --best             | --newest        | --best and --newest |
Packit 3a9065
        # available | all     | latest in all repos | latest per repo | latest in all repos |
Packit 3a9065
        # to_check  | all     | all                 | latest per repo | latest per repo     |
Packit 3a9065
Packit 3a9065
        if self.opts.newest:
Packit 3a9065
            available = self.base.sack.query().filter(empty=True)
Packit 3a9065
            to_check = self.base.sack.query().filter(empty=True)
Packit 3a9065
            for repo in self.base.repos.iter_enabled():
Packit 3a9065
                available = \
Packit 3a9065
                    available.union(self.base.sack.query().filter(reponame=repo.id).latest())
Packit 3a9065
                to_check = \
Packit 3a9065
                    to_check.union(self.base.sack.query().filter(reponame=repo.id).latest())
Packit 3a9065
        else:
Packit 3a9065
            available = self.base.sack.query().available()
Packit 3a9065
            to_check = self.base.sack.query().available()
Packit 3a9065
Packit 3a9065
        if self.opts.pkglist:
Packit 3a9065
            pkglist_q = self.base.sack.query().filter(empty=True)
Packit 3a9065
            errors = []
Packit 3a9065
            for pkg in self.opts.pkglist:
Packit 3a9065
                subj = dnf.subject.Subject(pkg)
Packit 3a9065
                pkg_q = to_check.intersection(
Packit 3a9065
                    subj.get_best_query(self.base.sack, with_nevra=True,
Packit 3a9065
                                        with_provides=False, with_filenames=False))
Packit 3a9065
                if pkg_q:
Packit 3a9065
                    pkglist_q = pkglist_q.union(pkg_q)
Packit 3a9065
                else:
Packit 3a9065
                    errors.append(pkg)
Packit 3a9065
            if errors:
Packit 3a9065
                raise dnf.exceptions.Error(
Packit 3a9065
                    _('no package matched: %s') % ', '.join(errors))
Packit 3a9065
            to_check = pkglist_q
Packit 3a9065
Packit 3a9065
        if self.opts.check:
Packit 3a9065
            to_check.filterm(reponame=self.opts.check)
Packit 3a9065
Packit 3a9065
        if arch is not None:
Packit 3a9065
            to_check.filterm(arch=arch)
Packit 3a9065
Packit 3a9065
        if self.base.conf.best:
Packit 3a9065
            available.filterm(latest_per_arch=True)
Packit 3a9065
Packit 3a9065
        available.apply()
Packit 3a9065
        to_check.apply()
Packit 3a9065
Packit 3a9065
        for pkg in to_check:
Packit 3a9065
            unresolved[pkg] = set()
Packit 3a9065
            for req in pkg.requires:
Packit 3a9065
                reqname = str(req)
Packit 3a9065
                # XXX: https://bugzilla.redhat.com/show_bug.cgi?id=1186721
Packit 3a9065
                if reqname.startswith("solvable:") or \
Packit 3a9065
                        reqname.startswith("rpmlib("):
Packit 3a9065
                    continue
Packit 3a9065
                deps.add(req)
Packit 3a9065
                unresolved[pkg].add(req)
Packit 3a9065
Packit 3a9065
        unresolved_deps = set(x for x in deps if not available.filter(provides=x))
Packit 3a9065
Packit 3a9065
        unresolved_transition = {k: set(x for x in v if x in unresolved_deps)
Packit 3a9065
                                 for k, v in unresolved.items()}
Packit 3a9065
        return {k: v for k, v in unresolved_transition.items() if v}
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        parser.add_argument("--arch", default=[], action="append", dest='arches',
Packit 3a9065
                            help=_("check packages of the given archs, can be "
Packit 3a9065
                                   "specified multiple times"))
Packit 3a9065
        parser.add_argument("--check", default=[], action="append",
Packit 3a9065
                            help=_("Specify repositories to check"))
Packit 3a9065
        parser.add_argument("-n", "--newest", action="store_true",
Packit 3a9065
                            help=_("Check only the newest packages in the "
Packit 3a9065
                                   "repos"))
Packit 3a9065
        parser.add_argument("--pkg", default=[], action="append",
Packit 3a9065
                            help=_("Check closure for this package only"),
Packit 3a9065
                            dest="pkglist")