#!/usr/bin/python # This lists all reverse dependencies (recursively) for the specified packages. # I.E. it displays what packages break if you remove the specified packages. # In other words what packages should you remove in addition to those specified. # Author: P@draigBrady.com # V1.0 17 Jan 2006 Initial release # NOTES: # # 1. It works on both rpm and deb distros. # # 2. We do a (recursive) simulated erase on the specified packages, as that # seems to be the only way to get the appropriate dependent packages as: # * rpm -q --whatrequires package # . Doesn't have a --recurse option. # . Doesn't include file dependencies. # . Doesn't handle provides appropriately. # For e.g. pygtk2 depends on the python2 package which # the python package provides. However `rpm -q --whatrequires python` # does not list the pygtk2 package (on fedora core 3 at least). # * apt-cache --recurse --installed rdepends package # . This includes all types of dependencies (suggests, conflicts, ...). # There is an --important option, but as of apt-0.6.40.1ubuntu9 # that option is ignored for the rdepends subcommand. #TODO, Auto handle absolute paths as well as packages. #TODO, rpm specifies versions while dpkg doesn't. Should make consistent. debug=False import sys import os # The following exits cleanly on Ctrl-C, # while treating other exceptions as before. def cli_exception(type, value, tb): if not issubclass(type, KeyboardInterrupt): sys.__excepthook__(type, value, tb) if sys.stdin.isatty(): sys.excepthook=cli_exception # Determine what type of distro we're on. class distroType: def __init__(self): self.rpm = self.deb = False if os.path.exists("/etc/redhat-release"): self.rpm = True elif os.path.exists("/etc/debian_version"): self.deb = True else: self.rpm = (os.system("rpm --version >/dev/null 2>&1") == 0) if not self.rpm: self.deb = (os.system("dpkg --version >/dev/null 2>&1") == 0) dist_type=distroType() checked_pkgs={} def whatRequires(packages,level=0): if not packages: return for package in packages: if debug: print "\t"*level+package checked_pkgs[package]='' if dist_type.rpm: cmd = r"rpm -e --test " + ' '.join(packages) + r" 2>&1 | " cmd += r"sed -n 's/.*is needed by (installed) \(.*\)/\1/p' | " cmd += r"LANG=C sort | uniq" elif dist_type.deb: cmd = r"dpkg --purge --dry-run " + ' '.join(packages) + r" 2>&1 | " cmd += r"sed -n 's/ \(.*\) depends on.*/\1/p' | " cmd += r"LANG=C sort | uniq" else: raise "unknown distro" process = os.popen(cmd) requires = process.read() del(process) new_packages = [p for p in requires.split() if not checked_pkgs.has_key(p)] whatRequires(new_packages,level+1) def notInstalled(packages): if dist_type.rpm: cmd = r"rpm -q " + ' '.join(packages) + r" 2>&1 | " cmd += r"sed -n 's/.* \(.*\) is not installed/\1/p'" elif dist_type.deb: cmd = r"dpkg -l " + ' '.join(packages) + r" 2>&1 | " cmd += r"sed -n 's/^No packages found matching \(.*\)\./\1/p'" process = os.popen(cmd) ni_list = process.read() del(process) return ni_list.split() packages=sys.argv[1:] if "--help" in packages or len(packages)==0: sys.stderr.write("Usage: %s package1 [package2 ...]\n" % os.path.split(sys.argv[0])[1]) sys.exit(1) ni_list = notInstalled(packages) if ni_list: sys.stderr.write("Error: The following packages are not installed:\n") sys.stderr.write("%s\n"%'\n'.join(ni_list)) sys.exit(1) whatRequires(packages) requires_packages = '\n'.join([p for p in checked_pkgs if p not in packages]) if requires_packages: sys.stdout.write("%s\n"%requires_packages)