|
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)
|