/usr/bin/which-pkg-broke is in debian-goodies 0.63.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #! /usr/bin/python
#  which-pkg-broke: help find offending packages when something breaks
#  Placed in the public domain by Bill Gribble <grib@billgribble.com>
import sys
import os
import subprocess
import time
from string import *
from stat import *
def pkgdeps(pkg):
    apt_cache = subprocess.Popen(
        ['apt-cache', 'depends', pkg],
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
        env={} # force POSIX locale
    )
    deps = []
    myline = apt_cache.stdout.readline()
    while(myline != ''):
        elts = map(strip, myline.split(':'))
        if len(elts) == 2:
            how, pkg = elts
            how = how.replace('|', '')
            if how in ('Depends', 'PreDepends'):
                deps.append(pkg)
        myline = apt_cache.stdout.readline()
    apt_cache.wait()
    return deps
def alldeps(pkg, ignore):
    deps = {}
    imm_deps = pkgdeps(pkg)
    for i in imm_deps:
        if ignore.get(i) is None:
            deps[i] = 1
            ignore[i] = 1
            childeps = alldeps(i, ignore)
            for c in childeps:
                deps[c] = 1
                ignore[i] = 1
    dlist = deps.keys()
    return dlist
def localarchitectures():
    architectures = []
    dpkg_arch = subprocess.Popen(
        ['dpkg', '--print-architecture'],
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
        env={} # force POSIX locale
    )
    for arch in dpkg_arch.stdout.readlines():
        architectures.append(arch.rstrip())
    try:
        dpkg_archs = subprocess.Popen(
            ['dpkg', '--print-foreign-architecture'],
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
            env={} # force POSIX locale
            )
        for arch in dpkg_archs.stdout.readlines():
            architectures.append(arch.rstrip())
    except OSError:
        pass # non-multiarchy dpkg, which is ok
    return architectures
def pkginstalltime(pkg, architectures):
    times = []
    for arch in architectures:
        listfile = '/var/lib/dpkg/info/' + pkg + ':' + arch + '.list'
        try:
            times.append([pkg + ':' + arch, os.stat(listfile)[ST_MTIME]])
        except OSError:
            pass
    if not times:
        listfile = '/var/lib/dpkg/info/' + pkg + '.list'
        try:
            times.append([pkg, os.stat(listfile)[ST_MTIME]])
        except OSError:
            print "Package", pkg, "has no install time info"
    return times
def what_broke(pname):
    def sortfun(a, b):
        return cmp(a[1], b[1])
    pkgs = [ pname ]
    pkgs.extend(alldeps(sys.argv[1], {}))
    architectures = localarchitectures()
    itimes = []
    for p in pkgs:
        itimes.extend(pkginstalltime(p, architectures))
    itimes.sort(sortfun)
    for i in itimes:
        p, t = i
        if t is not None:
            print ljust(p, 54), time.asctime(time.localtime(float(t)))
if (len(sys.argv) != 2 or sys.argv[1][0] == '-'):
    print "Usage: which-pkg-broke <pkg-name>"
    sys.exit(-1)
else:
    what_broke(sys.argv[1])
    sys.exit(0)
 |