Consider the following scenario. You want to install a package foo, so you run:
Code: Select all
# ebuild -p foo
[ebuild N ] sys-apps/libbar-1.0.0
[ebuild U ] sys-apps/foo-2.0.0 [1.0.0]
A week later you realise you don't need foo anymore.
Code: Select all
emerge unmerge fooRight now two utils come to your help. The first is emerge itself, more precisely "emerge depclean". Quoting the man page:
From my point of view this approach has one limit: emerge considers USE flags as they are defined when you run it, not as they where when you compiled each package.depclean
Determines all packages installed on the system that have no
explicit reason for being there. emerge generates a list of
packages which it expects to be installed by checking the system
package list and the world file. It then compares that list to
the list of packages which are actually installed; the differ-
ences are listed as unnecessary packages and are suggested for
unmerging. WARNING: Removing some packages may cause packages
which link to the removed package to stop working and complain
about missing libraries. Re-emerge the complaining package to
fix this issue. Note that changes in USE flags can drastically
affect the output of depclean.
The other useful tool is qpkg. Run:
Code: Select all
qpkg -I -q fooSince none of these tools completely satisfied me, I decided to write my own. The result is the the following pkdep Python script. It seems to be more aggressive than emerge, so be careful and think twice before removing anything.
Code: Select all
#!/usr/bin/env python
# Copyright 2003-2004 Anakim Border <aborder@users.sourceforge.net>
# This software is released under the terms of the GPL licence.
# Python 2.3+ required
from sets import Set
import os
import re
# Global vars
pkgdir = '/var/db/pkg'
pkguse = 'USE'
pkgprovide = 'PROVIDE'
pkgdepend = 'RDEPEND'
versionex = r'([0-9]+)(?:\.([0-9]+)(?:\.([0-9]+))?)?([a-z]?)(?:_(alpha|beta|pre|rc)([0-9]*))?(?:-r([0-9]+))?\*?'
# Functions
def versionMatch(dep, found):
required = dep.package.version
op = dep.versionFlags
flags = [None, 'alpha', 'beta', 'pre', 'rc']
try:
subverMatch(found.major, op, required.major)
subverMatch(found.middle, op, required.middle)
subverMatch(found.minor, op, required.minor)
subverMatch(found.letter, op, required.letter)
subverMatch(flags.index(found.flag), op, flags.index(required.flag))
subverMatch(found.flagnum, op, required.flagnum)
subverMatch(found.release, op, required.release)
return True
except VersionMatch:
return True
except VersionMismatch:
return False
def subverMatch(x, op, y):
if x is None and y is None:
return
if op & PackageDep.VERSION_LT:
if x is None:
raise VersionMatch
elif y is None:
raise VersionMismatch
elif x < y:
raise VersionMatch
elif op & PackageDep.VERSION_GT != 0:
if x is None:
raise VersionMismatch
elif y is None:
raise VersionMatch
elif x > y:
raise VersionMatch
if op & PackageDep.VERSION_EQ != 0:
if x is None or y is None:
return
elif x == y:
if op & PackageDep.VERSION_NOT != 0:
raise VersionMismatch
else:
return
raise VersionMismatch
class VersionMatch(Exception): pass
class VersionMismatch(Exception): pass
def versionCmp(pkgA, pkgB):
flags = [None, 'alpha', 'beta', 'pre', 'rc']
a, b = pkgA.version, pkgB.version
try:
subverCmp(a.major, b.major)
subverCmp(a.middle, b.middle)
subverCmp(a.minor, b.minor)
subverCmp(a.letter, b.letter)
subverCmp(flags.index(a.flag), flags.index(b.flag))
subverCmp(a.flagnum, b.flagnum)
subverCmp(a.release, b.release)
return 0
except VersionCompared, e:
return e.args[0]
def subverCmp(a, b):
res = cmp(a, b)
if res != 0:
raise VersionCompared, res
class VersionCompared(Exception): pass
# Package classes
class Package(object):
def __init__(self, name):
self.name, self.version = self.parseName(name)
def parseName(self, name):
pos = 0
while True:
pos = name.find('-', pos+1)
if pos < 0: return name, None
if name[pos+1].isdigit():
return name[:pos], PackageVersion(name[pos+1:])
class PackageVersion(object):
__slots__ = ('major', 'middle', 'minor', 'letter', 'flag', 'flagnum', 'release')
parser = re.compile(versionex)
def __init__(self, version = None):
for attr in self.__slots__:
self.__setattr__(attr, None)
if version:
try:
info = self.parser.findall(version)[0]
self.major = int(info[0])
if len(info[1]) > 0: self.middle = int(info[1])
if len(info[2]) > 0: self.minor = int(info[2])
if len(info[3]) > 0: self.letter = info[3]
if len(info[4]) > 0: self.flag = info[4]
if len(info[5]) > 0: self.flagnum = int(info[5])
if len(info[6]) > 0: self.release = int(info[6])
except IndexError:
raise ValueError
def __repr__(self):
return '<PackageVersion: %s %s %s %s %s %s %s>' % (self.major, self.middle, self.minor, self.letter, self.flag, self.flagnum, self.release)
def __str__(self):
res = []
if self.major is not None:
res.append(str(self.major))
if self.middle is not None:
res.append('.%d' % self.middle)
if self.minor is not None:
res.append('.%d' % self.minor)
if self.letter is not None:
res.append(self.letter)
if self.flag is not None:
res.append('_%s' % self.flag)
if self.flagnum is not None:
res.append(str(self.flagnum))
if self.release is not None:
res.append('-r%d' % self.release)
return ''.join(res)
class PackageDep(object):
VERSION_NONE = 0
VERSION_NOT = 1
VERSION_EQ = 2
VERSION_LT = 4
VERSION_GT = 8
def __init__(self):
self.package = None
self.versionFlags = self.VERSION_NONE
class PackageDeps(Package):
def __init__(self, name, basedir):
super(PackageDeps, self).__init__(name)
self.useflags = self.parseUseFlags(os.path.join(basedir, pkguse))
self.aliases = self.parseProvide(os.path.join(basedir, pkgprovide))
self.deps = self.parseDeps(os.path.join(basedir, pkgdepend))
def parseUseFlags(self, usefile):
# ASSUMPTION: USE declaration is on a single row
useline = ' '.join([ l.strip() for l in file(usefile, 'r') ])
flags = {}
for flag in useline.split(' '):
if flag[0] == '-':
flags[flag[1:]] = False
else:
flags[flag] = True
return flags
def parseProvide(self, providefile):
provides = Set()
for line in file(providefile, 'r'):
line = line.strip()
if len(line) == 0: continue
p = Package(line)
provides.add(p.name)
return provides
def parseDeps(self, filename):
deps = {}
for item in self.getDepItem(filename):
idx = item.find('?')
if idx >= 0:
flag = item[:idx]
if flag[0] == '!':
flag = flag[1:]
if self.useflags.has_key(flag) and self.useflags[flag]:
continue
else:
if not self.useflags.has_key(flag) or not self.useflags[flag]:
continue
item = item[idx+1:].strip()
if len(item) == 0: continue
d = PackageDep()
while item[0] in ('!', '~', '=', '<', '>'):
if item[0] == '!':
d.versionFlags |= PackageDep.VERSION_NOT
if item[0] == '=':
d.versionFlags |= PackageDep.VERSION_EQ
elif item[0] == '<':
d.versionFlags |= PackageDep.VERSION_LT
elif item[0] == '>':
d.versionFlags |= PackageDep.VERSION_GT
elif item[0] == '~':
pass # Ignore it
item = item[1:]
if d.versionFlags == d.VERSION_NOT : # '!package' or '!=package'
continue
if item[-1] == '*':
item = item[:-1]
d.package = Package(item)
deps[d.package.name] = d
return deps
def getDepItem(self, filename):
buf = ''
for line in file(filename, 'r'):
buf = ('%s %s' % (buf, line)).lstrip()
while len(buf) > 0:
idx = self.findWhitespace(buf)
if idx == -1:
yield buf
buf = ''
break
if buf[idx-1] == '?':
end = buf.find(')')
if end == -1: break
dep = buf[:idx]
start = buf.find('(')
depbuf = buf[start+1:end].lstrip()
buf = buf[end+1:].lstrip()
while len(depbuf) > 0:
idx = self.findWhitespace(depbuf)
if idx == -1:
yield '%s %s' % (dep, depbuf)
break
else:
yield '%s %s' % (dep, depbuf[:idx])
depbuf = depbuf[idx+1:].lstrip()
else:
yield buf[:idx]
buf = buf[idx+1:].lstrip()
def findWhitespace(self, data):
for idx in range(0, len(data)):
if data[idx] in (' ', '\t', '\n'):
return idx
return -1
# Main entry point
if __name__ == '__main__':
packages = {}
aliases = {}
deps = []
cuts = Set()
# Scan pkgdir and build package list
for category in os.listdir(pkgdir):
dirname = os.path.join(pkgdir, category)
if not os.path.isdir(dirname):
continue
for pkg in os.listdir(dirname):
try:
pd = PackageDeps(os.path.join(category, pkg), os.path.join(dirname, pkg))
packages.setdefault(pd.name, []).append(pd)
for alias in pd.aliases:
aliases.setdefault(pd.name, []).append(pd)
deps.append(pd)
except IOError:
pass
for pd in deps:
# Scan package dependencies
for name, dep in pd.deps.iteritems():
try:
if packages.has_key(name):
if dep.package.version != None:
matches = [ r for r in packages[name] if versionMatch(dep, r.version) ]
else:
matches = packages[name]
if len(matches) > 0:
if len(matches) > 1:
# All dependencies on the highest-numbered release
matches.sort(versionCmp)
cuts.add( (name, matches[-1]) )
else:
cuts.add( (name, matches[0]) )
elif aliases.has_key(name):
for pkg in aliases[name]:
if len(packages[pkg]) > 1:
# All dependencies on the highest-numbered release
packages[pkg].sort(versionCmp)
cuts.add( (pkg, packages[pkg][-1]) )
else:
cuts.add( (pkg, packages[pkg][0]) )
except KeyError:
pass
for name, release in cuts:
packages[name].remove(release)
if len(packages[name]) == 0:
del packages[name]
if len(packages) > 0:
print 'The following packages are not depended upon by anything else:'
print
pkgs = packages.keys()
pkgs.sort()
for name in pkgs:
for release in packages[name]:
print '\t%s-%s' % (name, release.version)Edit #1
I probably found the code responsible for the error reported by Ox-. I've changed both versionMatch and PackageDeps.getDepItem functions.
Edit #2
I've changed pkdep to make it work on systems having multiple releases of the same package installed (for example: Berkeley DB). The output now lists both package names and their versions.






