#!/usr/bin/python from __future__ import print_function # List 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. # Licence: LGPLv2 # Author: # http://www.pixelbeat.org/ # 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. # Changes: # V1.0 17 Jan 2006 Initial release # V1.1 01 Feb 2010 # http://github.com/pixelb/scripts/commits/master/scripts/whatrequires #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 import errno # The following exits cleanly on Ctrl-C or EPIPE # while treating other exceptions as before. def std_exceptions(etype, value, tb): sys.excepthook=sys.__excepthook__ if issubclass(etype, KeyboardInterrupt): pass elif issubclass(etype, IOError) and value.errno == errno.EPIPE: pass else: sys.__excepthook__(etype, value, tb) sys.excepthook=std_exceptions # 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/-[0-9]\{1,\}:/-/; " #strip epoch cmd += r" 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.stderr.close() 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.stderr.close() 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) sys.stdout.close()