Blame plugins/leaves.py

Packit 3a9065
# leaves.py
Packit 3a9065
# DNF plugin for listing installed packages not required by any other
Packit 3a9065
# installed package.
Packit 3a9065
#
Packit 3a9065
# Copyright (C) 2015 Emil Renner Berthing
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.
Packit 3a9065
#
Packit 3a9065
import dnf
Packit 3a9065
import dnf.sack
Packit 3a9065
import dnf.cli
Packit 3a9065
from dnfpluginscore import _
Packit 3a9065
Packit 3a9065
Packit 3a9065
class Leaves(dnf.Plugin):
Packit 3a9065
    name = 'leaves'
Packit 3a9065
Packit 3a9065
    def __init__(self, base, cli):
Packit 3a9065
        super(Leaves, self).__init__(base, cli)
Packit 3a9065
        if cli:
Packit 3a9065
            cli.register_command(LeavesCommand)
Packit 3a9065
Packit 3a9065
Packit 3a9065
class LeavesCommand(dnf.cli.Command):
Packit 3a9065
    aliases = ('leaves',)
Packit 3a9065
    summary = _('List installed packages not required by any other package')
Packit 3a9065
Packit 3a9065
    def buildgraph(self):
Packit 3a9065
        """
Packit 3a9065
        Load the list of installed packages and their dependencies using
Packit 3a9065
        hawkey, and build the dependency graph and the graph of reverse
Packit 3a9065
        dependencies.
Packit 3a9065
        """
Packit 3a9065
        query = dnf.sack._rpmdb_sack(self.base).query().apply()
Packit 3a9065
        pkgmap = dict()
Packit 3a9065
        packages = []
Packit 3a9065
        depends = []
Packit 3a9065
        rdepends = []
Packit 3a9065
        deps = set()
Packit 3a9065
        providers = set()
Packit 3a9065
Packit 3a9065
        for i, pkg in enumerate(query):
Packit 3a9065
            pkgmap[pkg] = i
Packit 3a9065
            packages.append(pkg)
Packit 3a9065
            rdepends.append([])
Packit 3a9065
Packit 3a9065
        for i, pkg in enumerate(packages):
Packit 3a9065
            for req in pkg.requires:
Packit 3a9065
                sreq = str(req)
Packit 3a9065
                if sreq.startswith('rpmlib(') or sreq == 'solvable:prereqmarker':
Packit 3a9065
                    continue
Packit 3a9065
                for dpkg in query.filter(provides=req):
Packit 3a9065
                    providers.add(pkgmap[dpkg])
Packit 3a9065
                if len(providers) == 1 and i not in providers:
Packit 3a9065
                    deps.update(providers)
Packit 3a9065
                providers.clear()
Packit 3a9065
Packit 3a9065
            deplist = list(deps)
Packit 3a9065
            deps.clear()
Packit 3a9065
            depends.append(deplist)
Packit 3a9065
            for j in deplist:
Packit 3a9065
                rdepends[j].append(i)
Packit 3a9065
Packit 3a9065
        return (packages, depends, rdepends)
Packit 3a9065
Packit 3a9065
    def kosaraju(self, graph, rgraph):
Packit 3a9065
        """
Packit 3a9065
        Run Kosaraju's algorithm to find strongly connected components
Packit 3a9065
        in the graph, and return the list of nodes in the components
Packit 3a9065
        without any incoming edges.
Packit 3a9065
        """
Packit 3a9065
        N = len(graph)
Packit 3a9065
        rstack = []
Packit 3a9065
        stack = []
Packit 3a9065
        idx = []
Packit 3a9065
        tag = [False] * N
Packit 3a9065
Packit 3a9065
        # do depth-first searches in the graph
Packit 3a9065
        # and push nodes to rstack "on the way up"
Packit 3a9065
        # until all nodes have been pushed.
Packit 3a9065
        # tag nodes so we don't visit them more than once
Packit 3a9065
        for u in range(N):
Packit 3a9065
            if tag[u]:
Packit 3a9065
                continue
Packit 3a9065
Packit 3a9065
            stack.append(u)
Packit 3a9065
            idx.append(len(graph[u]))
Packit 3a9065
            tag[u] = True
Packit 3a9065
            while stack:
Packit 3a9065
                u = stack[-1]
Packit 3a9065
                i = idx[-1]
Packit 3a9065
Packit 3a9065
                if i:
Packit 3a9065
                    i -= 1
Packit 3a9065
                    idx[-1] = i
Packit 3a9065
                    v = graph[u][i]
Packit 3a9065
                    if not tag[v]:
Packit 3a9065
                        stack.append(v)
Packit 3a9065
                        idx.append(len(graph[v]))
Packit 3a9065
                        tag[v] = True
Packit 3a9065
                else:
Packit 3a9065
                    stack.pop()
Packit 3a9065
                    idx.pop()
Packit 3a9065
                    rstack.append(u)
Packit 3a9065
Packit 3a9065
        # now searches beginning at nodes popped from
Packit 3a9065
        # rstack in the graph with all edges reversed
Packit 3a9065
        # will give us the strongly connected components.
Packit 3a9065
        # the incoming edges to each component is the
Packit 3a9065
        # union of incoming edges to each node in the
Packit 3a9065
        # component minus the incoming edges from
Packit 3a9065
        # component nodes themselves.
Packit 3a9065
        # now all nodes are tagged, so this time let's
Packit 3a9065
        # remove the tags as we visit each node.
Packit 3a9065
        leaves = []
Packit 3a9065
        scc = []
Packit 3a9065
        sccredges = set()
Packit 3a9065
        while rstack:
Packit 3a9065
            v = rstack.pop()
Packit 3a9065
            if not tag[v]:
Packit 3a9065
                continue
Packit 3a9065
Packit 3a9065
            stack.append(v)
Packit 3a9065
            tag[v] = False
Packit 3a9065
            while stack:
Packit 3a9065
                v = stack.pop()
Packit 3a9065
                redges = rgraph[v]
Packit 3a9065
                scc.append(v)
Packit 3a9065
                sccredges.update(redges)
Packit 3a9065
                for u in redges:
Packit 3a9065
                    if tag[u]:
Packit 3a9065
                        stack.append(u)
Packit 3a9065
                        tag[u] = False
Packit 3a9065
Packit 3a9065
            sccredges.difference_update(scc)
Packit 3a9065
            if not sccredges:
Packit 3a9065
                leaves.extend(scc)
Packit 3a9065
            del scc[:]
Packit 3a9065
            sccredges.clear()
Packit 3a9065
Packit 3a9065
        return leaves
Packit 3a9065
Packit 3a9065
    def findleaves(self):
Packit 3a9065
        (packages, depends, rdepends) = self.buildgraph()
Packit 3a9065
        return [packages[i] for i in self.kosaraju(depends, rdepends)]
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        for pkg in sorted(map(str, self.findleaves())):
Packit 3a9065
            print(pkg)