X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=dak%2Fcruft_report.py;h=63374859fa8019a10165ec318dea5cea14c38ce2;hb=731c2232d9964d0f441ff5ea386e5e9937aa77ab;hp=e13b11ce1393e1c6d0424f4c554a8fdf57cad7d6;hpb=06ce98c8111a8b09e5603dbbd34324a216412d69;p=dak.git diff --git a/dak/cruft_report.py b/dak/cruft_report.py index e13b11ce..63374859 100755 --- a/dak/cruft_report.py +++ b/dak/cruft_report.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Check for obsolete binary packages +""" Check for obsolete binary packages """ # Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006 James Troup # This program is free software; you can redistribute it and/or modify @@ -23,19 +23,22 @@ # you might as well write some letters to God about how unfair entropy # is while you're at it.'' -- 20020802143104.GA5628@azure.humbug.org.au -## TODO: fix NBS looping for version, implement Dubious NBS, fix up output of duplicate source package stuff, improve experimental ?, add support for non-US ?, add overrides, avoid ANAIS for duplicated packages +## TODO: fix NBS looping for version, implement Dubious NBS, fix up output of +## duplicate source package stuff, improve experimental ?, add overrides, +## avoid ANAIS for duplicated packages ################################################################################ -import commands, pg, os, string, sys, time -import dak.lib.utils, dak.lib.database +import commands, os, sys, re import apt_pkg +from daklib.config import Config +from daklib.dbconn import * +from daklib import utils +from daklib.regexes import re_extract_src_version + ################################################################################ -Cnf = None -projectB = None -suite_id = None no_longer_in_suite = {}; # Really should be static to add_nbs, but I'm lazy source_binaries = {} @@ -49,18 +52,22 @@ Check for obsolete or duplicated packages. -h, --help show this help and exit. -m, --mode=MODE chose the MODE to run in (full or daily). - -s, --suite=SUITE check suite SUITE.""" + -s, --suite=SUITE check suite SUITE. + -w, --wanna-build-dump where to find the copies of http://buildd.debian.org/stats/*.txt""" sys.exit(exit_code) ################################################################################ -def add_nbs(nbs_d, source, version, package): +def add_nbs(nbs_d, source, version, package, suite_id, session): # Ensure the package is still in the suite (someone may have already removed it) if no_longer_in_suite.has_key(package): return else: - q = projectB.query("SELECT b.id FROM binaries b, bin_associations ba WHERE ba.bin = b.id AND ba.suite = %s AND b.package = '%s' LIMIT 1" % (suite_id, package)) - if not q.getresult(): + q = session.execute("""SELECT b.id FROM binaries b, bin_associations ba + WHERE ba.bin = b.id AND ba.suite = :suite_id + AND b.package = :package LIMIT 1""", {'suite_id': suite_id, + 'package': package}) + if not q.fetchall(): no_longer_in_suite[package] = "" return @@ -71,7 +78,7 @@ def add_nbs(nbs_d, source, version, package): ################################################################################ # Check for packages built on architectures they shouldn't be. -def do_anais(architecture, binaries_list, source): +def do_anais(architecture, binaries_list, source, session): if architecture == "any" or architecture == "all": return "" @@ -80,10 +87,13 @@ def do_anais(architecture, binaries_list, source): for arch in architecture.split(): architectures[arch.strip()] = "" for binary in binaries_list: - q = projectB.query("SELECT a.arch_string, b.version FROM binaries b, bin_associations ba, architecture a WHERE ba.suite = %s AND ba.bin = b.id AND b.architecture = a.id AND b.package = '%s'" % (suite_id, binary)) - ql = q.getresult() + q = session.execute("""SELECT a.arch_string, b.version + FROM binaries b, bin_associations ba, architecture a + WHERE ba.suite = :suiteid AND ba.bin = b.id + AND b.architecture = a.id AND b.package = :package""", + {'suiteid': suite_id, 'package': binary}) versions = [] - for i in ql: + for i in q.fetchall(): arch = i[0] version = i[1] if architectures.has_key(arch): @@ -112,33 +122,95 @@ def do_anais(architecture, binaries_list, source): anais_output += " o %s: %s\n" % (version, ", ".join(arches)) return anais_output + +################################################################################ + +# Check for out-of-date binaries on architectures that do not want to build that +# package any more, and have them listed as Not-For-Us +def do_nfu(nfu_packages): + output = "" + + a2p = {} + + for architecture in nfu_packages: + a2p[architecture] = [] + for (package,bver,sver) in nfu_packages[architecture]: + output += " * [%s] does not want %s (binary %s, source %s)\n" % (architecture, package, bver, sver) + a2p[architecture].append(package) + + + if output: + print "Obsolete by Not-For-Us" + print "----------------------" + print + print output + + print "Suggested commands:" + for architecture in a2p: + if a2p[architecture]: + print (" dak rm -m \"[auto-cruft] NFU\" -s %s -a %s -b %s" % + (suite, architecture, " ".join(a2p[architecture]))) + print + +def parse_nfu(architecture): + cnf = Config() + # utils/hpodder_1.1.5.0: Not-For-Us [optional:out-of-date] + r = re.compile("^\w+/([^_]+)_.*: Not-For-Us") + + ret = set() + + filename = "%s/%s-all.txt" % (cnf["Cruft-Report::Options::Wanna-Build-Dump"], architecture) + + # Not all architectures may have a wanna-build dump, so we want to ignore missin + # files + if os.path.exists(filename): + f = utils.open_file(filename) + for line in f: + if line[0] == ' ': + continue + + m = r.match(line) + if m: + ret.add(m.group(1)) + + f.close() + else: + utils.warn("No wanna-build dump file for architecture %s" % architecture) + return ret + ################################################################################ -def do_nviu(): - experimental_id = dak.lib.database.get_suite_id("experimental") - if experimental_id == -1: +def do_newer_version(lowersuite_name, highersuite_name, code, session): + lowersuite = get_suite(lowersuite_name, session) + if not lowersuite: + return + + highersuite = get_suite(highersuite_name, session) + if not highersuite: return - # Check for packages in experimental obsoleted by versions in unstable - q = projectB.query(""" -SELECT s.source, s.version AS experimental, s2.version AS unstable + + # Check for packages in $highersuite obsoleted by versions in $lowersuite + q = session.execute(""" +SELECT s.source, s.version AS lower, s2.version AS higher FROM src_associations sa, source s, source s2, src_associations sa2 - WHERE sa.suite = %s AND sa2.suite = %d AND sa.source = s.id + WHERE sa.suite = :highersuite_id AND sa2.suite = :lowersuite_id AND sa.source = s.id AND sa2.source = s2.id AND s.source = s2.source - AND versioncmp(s.version, s2.version) < 0""" % (experimental_id, - dak.lib.database.get_suite_id("unstable"))) - ql = q.getresult() + AND s.version < s2.version""", {'lowersuite_id': lowersuite.suite_id, + 'highersuite_id': highersuite.suite_id}) + ql = q.fetchall() if ql: - nviu_to_remove = [] - print "Newer version in unstable" - print "-------------------------" - print + nv_to_remove = [] + print "Newer version in %s" % lowersuite.suite_name + print "-----------------" + "-" * len(lowersuite.suite_name) + print for i in ql: - (source, experimental_version, unstable_version) = i - print " o %s (%s, %s)" % (source, experimental_version, unstable_version) - nviu_to_remove.append(source) + (source, higher_version, lower_version) = i + print " o %s (%s, %s)" % (source, higher_version, lower_version) + nv_to_remove.append(source) print print "Suggested command:" - print " dak rm -m \"[auto-cruft] NVIU\" -s experimental %s" % (" ".join(nviu_to_remove)) + print " dak rm -m \"[auto-cruft] %s\" -s %s %s" % (code, highersuite.suite_name, + " ".join(nv_to_remove)) print ################################################################################ @@ -147,7 +219,7 @@ def do_nbs(real_nbs): output = "Not Built from Source\n" output += "---------------------\n\n" - nbs_to_remove = [] + cmd_output = "" nbs_keys = real_nbs.keys() nbs_keys.sort() for source in nbs_keys: @@ -157,28 +229,29 @@ def do_nbs(real_nbs): output += " but no longer builds:\n" versions = real_nbs[source].keys() versions.sort(apt_pkg.VersionCompare) + all_packages = [] for version in versions: packages = real_nbs[source][version].keys() packages.sort() - for pkg in packages: - nbs_to_remove.append(pkg) + all_packages.extend(packages) output += " o %s: %s\n" % (version, ", ".join(packages)) + if all_packages: + all_packages.sort() + cmd_output += " dak rm -m \"[auto-cruft] NBS (was built by %s)\" -s %s -b %s\n\n" % (source, suite.suite_name, " ".join(all_packages)) output += "\n" - if nbs_to_remove: + if len(cmd_output): print output - - print "Suggested command:" - print " dak rm -m \"[auto-cruft] NBS\" -b %s" % (" ".join(nbs_to_remove)) - print + print "Suggested commands:\n" + print cmd_output ################################################################################ def do_dubious_nbs(dubious_nbs): print "Dubious NBS" print "-----------" - print + print dubious_nbs_keys = dubious_nbs.keys() dubious_nbs_keys.sort() @@ -194,22 +267,21 @@ def do_dubious_nbs(dubious_nbs): packages.sort() print " o %s: %s" % (version, ", ".join(packages)) - print + print ################################################################################ def do_obsolete_source(duplicate_bins, bin2source): obsolete = {} for key in duplicate_bins.keys(): - (source_a, source_b) = key.split('~') + (source_a, source_b) = key.split('_') for source in [ source_a, source_b ]: if not obsolete.has_key(source): if not source_binaries.has_key(source): # Source has already been removed continue else: - obsolete[source] = map(string.strip, - source_binaries[source].split(',')) + obsolete[source] = [ i.strip() for i in source_binaries[source].split(',') ] for binary in duplicate_bins[key]: if bin2source.has_key(binary) and bin2source[binary]["source"] == source: continue @@ -225,7 +297,7 @@ def do_obsolete_source(duplicate_bins, bin2source): if not obsolete[source]: to_remove.append(source) output += " * %s (%s)\n" % (source, source_versions[source]) - for binary in map(string.strip, source_binaries[source].split(',')): + for binary in [ i.strip() for i in source_binaries[source].split(',') ]: if bin2source.has_key(binary): output += " o %s (%s) is built by %s.\n" \ % (binary, bin2source[binary]["version"], @@ -241,41 +313,58 @@ def do_obsolete_source(duplicate_bins, bin2source): print " dak rm -S -p -m \"[auto-cruft] obsolete source package\" %s" % (" ".join(to_remove)) print +def get_suite_binaries(suite, session): + # Initalize a large hash table of all binary packages + binaries = {} + + print "Getting a list of binary packages in %s..." % suite.suite_name + q = session.execute("""SELECT distinct b.package + FROM binaries b, bin_associations ba + WHERE ba.suite = :suiteid AND ba.bin = b.id""", + {'suiteid': suite.suite_id}) + for i in q.fetchall(): + binaries[i[0]] = "" + + return binaries + ################################################################################ def main (): - global Cnf, projectB, suite_id, source_binaries, source_versions + global suite, suite_id, source_binaries, source_versions - Cnf = dak.lib.utils.get_conf() + cnf = Config() Arguments = [('h',"help","Cruft-Report::Options::Help"), ('m',"mode","Cruft-Report::Options::Mode", "HasArg"), - ('s',"suite","Cruft-Report::Options::Suite","HasArg")] + ('s',"suite","Cruft-Report::Options::Suite","HasArg"), + ('w',"wanna-build-dump","Cruft-Report::Options::Wanna-Build-Dump","HasArg")] for i in [ "help" ]: - if not Cnf.has_key("Cruft-Report::Options::%s" % (i)): - Cnf["Cruft-Report::Options::%s" % (i)] = "" - Cnf["Cruft-Report::Options::Suite"] = Cnf["Dinstall::DefaultSuite"] + if not cnf.has_key("Cruft-Report::Options::%s" % (i)): + cnf["Cruft-Report::Options::%s" % (i)] = "" + cnf["Cruft-Report::Options::Suite"] = cnf["Dinstall::DefaultSuite"] + + if not cnf.has_key("Cruft-Report::Options::Mode"): + cnf["Cruft-Report::Options::Mode"] = "daily" - if not Cnf.has_key("Cruft-Report::Options::Mode"): - Cnf["Cruft-Report::Options::Mode"] = "daily" + if not cnf.has_key("Cruft-Report::Options::Wanna-Build-Dump"): + cnf["Cruft-Report::Options::Wanna-Build-Dump"] = "/srv/ftp.debian.org/scripts/nfu" - apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv) + apt_pkg.ParseCommandLine(cnf.Cnf, Arguments, sys.argv) - Options = Cnf.SubTree("Cruft-Report::Options") + Options = cnf.SubTree("Cruft-Report::Options") if Options["Help"]: - usage() + usage() # Set up checks based on mode if Options["Mode"] == "daily": checks = [ "nbs", "nviu", "obsolete source" ] elif Options["Mode"] == "full": - checks = [ "nbs", "nviu", "obsolete source", "dubious nbs", "bnb", "bms", "anais" ] + checks = [ "nbs", "nviu", "obsolete source", "nfu", "dubious nbs", "bnb", "bms", "anais" ] else: - dak.lib.utils.warn("%s is not a recognised mode - only 'full' or 'daily' are understood." % (Options["Mode"])) + utils.warn("%s is not a recognised mode - only 'full' or 'daily' are understood." % (Options["Mode"])) usage(1) - projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"])) - dak.lib.database.init(Cnf, projectB) + session = DBConn().session() bin_pkgs = {} src_pkgs = {} @@ -287,39 +376,35 @@ def main (): anais_output = "" duplicate_bins = {} - suite = Options["Suite"] - suite_id = dak.lib.database.get_suite_id(suite) + nfu_packages = {} + + suite = get_suite(Options["Suite"].lower(), session) + suite_id = suite.suite_id + suite_name = suite.suite_name.lower() bin_not_built = {} if "bnb" in checks: - # Initalize a large hash table of all binary packages - before = time.time() - sys.stderr.write("[Getting a list of binary packages in %s..." % (suite)) - q = projectB.query("SELECT distinct b.package FROM binaries b, bin_associations ba WHERE ba.suite = %s AND ba.bin = b.id" % (suite_id)) - ql = q.getresult() - sys.stderr.write("done. (%d seconds)]\n" % (int(time.time()-before))) - for i in ql: - bins_in_suite[i[0]] = "" + bins_in_suite = get_suite_binaries(suite_name, session) # Checks based on the Sources files - components = Cnf.ValueList("Suite::%s::Components" % (suite)) + components = cnf.ValueList("Suite::%s::Components" % (suite_name)) for component in components: - filename = "%s/dists/%s/%s/source/Sources.gz" % (Cnf["Dir::Root"], suite, component) + filename = "%s/dists/%s/%s/source/Sources.gz" % (cnf["Dir::Root"], suite_name, component) # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance... - temp_filename = dak.lib.utils.temp_filename() + (fd, temp_filename) = utils.temp_filename() (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename)) if (result != 0): sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output)) sys.exit(result) - sources = dak.lib.utils.open_file(temp_filename) + sources = utils.open_file(temp_filename) Sources = apt_pkg.ParseTagFile(sources) while Sources.Step(): source = Sources.Section.Find('Package') source_version = Sources.Section.Find('Version') architecture = Sources.Section.Find('Architecture') binaries = Sources.Section.Find('Binary') - binaries_list = map(string.strip, binaries.split(',')) + binaries_list = [ i.strip() for i in binaries.split(',') ] if "bnb" in checks: # Check for binaries not built on any architecture. @@ -329,7 +414,7 @@ def main (): bin_not_built[source][binary] = "" if "anais" in checks: - anais_output += do_anais(architecture, binaries_list, source) + anais_output += do_anais(architecture, binaries_list, source, session) # Check for duplicated packages and build indices for checking "no source" later source_index = component + '/' + source @@ -340,7 +425,7 @@ def main (): if bin_pkgs.has_key(binary): key_list = [ source, bin_pkgs[binary] ] key_list.sort() - key = '~'.join(key_list) + key = '_'.join(key_list) duplicate_bins.setdefault(key, []) duplicate_bins[key].append(binary) bin_pkgs[binary] = source @@ -351,17 +436,30 @@ def main (): os.unlink(temp_filename) # Checks based on the Packages files - for component in components + ['main/debian-installer']: - architectures = filter(dak.lib.utils.real_arch, Cnf.ValueList("Suite::%s::Architectures" % (suite))) + check_components = components[:] + if suite_name != "experimental": + check_components.append('main/debian-installer'); + + for component in check_components: + architectures = [ a.arch_string for a in get_suite_architectures(suite_name, + skipsrc=True, skipall=True, + session=session) ] for architecture in architectures: - filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (Cnf["Dir::Root"], suite, component, architecture) + if component == 'main/debian-installer' and re.match("kfreebsd", architecture): + continue + filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (cnf["Dir::Root"], suite_name, component, architecture) # apt_pkg.ParseTagFile needs a real file handle - temp_filename = dak.lib.utils.temp_filename() + (fd, temp_filename) = utils.temp_filename() (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename)) if (result != 0): sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output)) sys.exit(result) - packages = dak.lib.utils.open_file(temp_filename) + + if "nfu" in checks: + nfu_packages.setdefault(architecture,[]) + nfu_entries = parse_nfu(architecture) + + packages = utils.open_file(temp_filename) Packages = apt_pkg.ParseTagFile(packages) while Packages.Step(): package = Packages.Section.Find('Package') @@ -378,7 +476,7 @@ def main (): bin2source[package]["version"] = version bin2source[package]["source"] = source if source.find("(") != -1: - m = dak.lib.utils.re_extract_src_version.match(source) + m = re_extract_src_version.match(source) source = m.group(1) version = m.group(2) if not bin_pkgs.has_key(package): @@ -390,13 +488,18 @@ def main (): if previous_source != source: key_list = [ source, previous_source ] key_list.sort() - key = '~'.join(key_list) + key = '_'.join(key_list) duplicate_bins.setdefault(key, []) if package not in duplicate_bins[key]: duplicate_bins[key].append(package) + if "nfu" in checks: + if package in nfu_entries and \ + version != source_versions[source]: # only suggest to remove out-of-date packages + nfu_packages[architecture].append((package,version,source_versions[source])) + packages.close() os.unlink(temp_filename) - + if "obsolete source" in checks: do_obsolete_source(duplicate_bins, bin2source) @@ -410,12 +513,12 @@ def main (): latest_version = versions.pop() source_version = source_versions.get(source,"0") if apt_pkg.VersionCompare(latest_version, source_version) == 0: - add_nbs(dubious_nbs, source, latest_version, package) + add_nbs(dubious_nbs, source, latest_version, package, suite_id, session) else: - add_nbs(real_nbs, source, latest_version, package) + add_nbs(real_nbs, source, latest_version, package, suite_id, session) if "nviu" in checks: - do_nviu() + do_newer_version('unstable', 'experimental', 'NVIU', session) if "nbs" in checks: do_nbs(real_nbs) @@ -426,6 +529,9 @@ def main (): print "="*75 print + if "nfu" in checks: + do_nfu(nfu_packages) + if "bnb" in checks: print "Unbuilt binary packages" print "-----------------------" @@ -436,24 +542,24 @@ def main (): binaries = bin_not_built[source].keys() binaries.sort() print " o %s: %s" % (source, ", ".join(binaries)) - print + print if "bms" in checks: print "Built from multiple source packages" print "-----------------------------------" - print + print keys = duplicate_bins.keys() keys.sort() for key in keys: - (source_a, source_b) = key.split("~") + (source_a, source_b) = key.split("_") print " o %s & %s => %s" % (source_a, source_b, ", ".join(duplicate_bins[key])) - print + print if "anais" in checks: print "Architecture Not Allowed In Source" print "----------------------------------" print anais_output - print + print if "dubious nbs" in checks: do_dubious_nbs(dubious_nbs)