X-Git-Url: https://git.decadent.org.uk/gitweb/?p=dak.git;a=blobdiff_plain;f=dak%2Fcruft_report.py;h=5c6ee8d0c3270c5ca7d846ac8967799b1529cf38;hp=63374859fa8019a10165ec318dea5cea14c38ce2;hb=5a61a05250b1bf5d54661103e8999eb7c7fc9207;hpb=add275e4b0d5b93fbc7774078d1d7e9dcbb66de9 diff --git a/dak/cruft_report.py b/dak/cruft_report.py index 63374859..5c6ee8d0 100755 --- a/dak/cruft_report.py +++ b/dak/cruft_report.py @@ -1,7 +1,13 @@ #!/usr/bin/env python -""" Check for obsolete binary packages """ -# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006 James Troup +""" +Check for obsolete binary packages + +@contact: Debian FTP Master +@copyright: 2000-2006 James Troup +@copyright: 2009 Torsten Werner +@license: GNU General Public License version 2 or later +""" # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -36,6 +42,7 @@ from daklib.config import Config from daklib.dbconn import * from daklib import utils from daklib.regexes import re_extract_src_version +from daklib.cruft import * ################################################################################ @@ -51,9 +58,10 @@ def usage(exit_code=0): 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). + -m, --mode=MODE chose the MODE to run in (full, daily, bdo). -s, --suite=SUITE check suite SUITE. - -w, --wanna-build-dump where to find the copies of http://buildd.debian.org/stats/*.txt""" + -R, --rdep-check check reverse dependencies + -w, --wanna-build-dump where to find the copies of https://buildd.debian.org/stats/*.txt""" sys.exit(exit_code) ################################################################################ @@ -92,13 +100,14 @@ def do_anais(architecture, binaries_list, source, session): WHERE ba.suite = :suiteid AND ba.bin = b.id AND b.architecture = a.id AND b.package = :package""", {'suiteid': suite_id, 'package': binary}) + ql = q.fetchall() versions = [] - for i in q.fetchall(): + for i in ql: arch = i[0] version = i[1] if architectures.has_key(arch): versions.append(version) - versions.sort(apt_pkg.VersionCompare) + versions.sort(apt_pkg.version_compare) if versions: latest_version = versions.pop() else: @@ -115,7 +124,7 @@ def do_anais(architecture, binaries_list, source, session): if versions_d != {}: anais_output += "\n (*) %s_%s [%s]: %s\n" % (binary, latest_version, source, architecture) versions = versions_d.keys() - versions.sort(apt_pkg.VersionCompare) + versions.sort(apt_pkg.version_compare) for version in versions: arches = versions_d[version] arches.sort() @@ -149,7 +158,7 @@ def do_nfu(nfu_packages): 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]))) + (suite.suite_name, architecture, " ".join(a2p[architecture]))) print def parse_nfu(architecture): @@ -181,70 +190,113 @@ def parse_nfu(architecture): ################################################################################ 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 $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 = :highersuite_id AND sa2.suite = :lowersuite_id AND sa.source = s.id - AND sa2.source = s2.id AND s.source = s2.source - AND s.version < s2.version""", {'lowersuite_id': lowersuite.suite_id, - 'highersuite_id': highersuite.suite_id}) - ql = q.fetchall() - if ql: + list = newer_version(lowersuite_name, highersuite_name, session) + if len(list) > 0: nv_to_remove = [] - print "Newer version in %s" % lowersuite.suite_name - print "-----------------" + "-" * len(lowersuite.suite_name) + title = "Newer version in %s" % lowersuite_name + print title + print "-" * len(title) print - for i in ql: + for i in list: (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] %s\" -s %s %s" % (code, highersuite.suite_name, + print " dak rm -m \"[auto-cruft] %s\" -s %s %s" % (code, highersuite_name, " ".join(nv_to_remove)) print ################################################################################ -def do_nbs(real_nbs): - output = "Not Built from Source\n" - output += "---------------------\n\n" - - cmd_output = "" - nbs_keys = real_nbs.keys() - nbs_keys.sort() - for source in nbs_keys: - output += " * %s_%s builds: %s\n" % (source, - source_versions.get(source, "??"), - source_binaries.get(source, "(source does not exist)")) - 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() - 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" +def reportWithoutSource(suite_name, suite_id, session, rdeps=False): + rows = query_without_source(suite_id, session) + title = 'packages without source in suite %s' % suite_name + if rows.rowcount > 0: + print '%s\n%s\n' % (title, '-' * len(title)) + message = '"[auto-cruft] no longer built from source"' + for row in rows: + (package, version) = row + print "* package %s in version %s is no longer built from source" % \ + (package, version) + print " - suggested command:" + print " dak rm -m %s -s %s -a all -p -R -b %s" % \ + (message, suite_name, package) + if rdeps: + if utils.check_reverse_depends([package], suite_name, [], session, True): + print + else: + print " - No dependency problem found\n" + else: + print + +def queryNewerAll(suite_name, session): + """searches for arch != all packages that have an arch == all + package with a higher version in the same suite""" + + query = """ +select bab1.package, bab1.version as oldver, + array_to_string(array_agg(a.arch_string), ',') as oldarch, + bab2.version as newver + from bin_associations_binaries bab1 + join bin_associations_binaries bab2 + on bab1.package = bab2.package and bab1.version < bab2.version and + bab1.suite = bab2.suite and bab1.architecture > 2 and + bab2.architecture = 2 + join architecture a on bab1.architecture = a.id + join suite s on bab1.suite = s.id + where s.suite_name = :suite_name + group by bab1.package, oldver, bab1.suite, newver""" + return session.execute(query, { 'suite_name': suite_name }) + +def reportNewerAll(suite_name, session): + rows = queryNewerAll(suite_name, session) + title = 'obsolete arch any packages in suite %s' % suite_name + if rows.rowcount > 0: + print '%s\n%s\n' % (title, '-' * len(title)) + message = '"[auto-cruft] obsolete arch any package"' + for row in rows: + (package, oldver, oldarch, newver) = row + print "* package %s is arch any in version %s but arch all in version %s" % \ + (package, oldver, newver) + print " - suggested command:" + print " dak rm -m %s -s %s -a %s -p -b %s\n" % \ + (message, suite_name, oldarch, package) + + + +def reportNBS(suite_name, suite_id, rdeps=False): + session = DBConn().session() + nbsRows = queryNBS(suite_id, session) + title = 'NBS packages in suite %s' % suite_name + if nbsRows.rowcount > 0: + print '%s\n%s\n' % (title, '-' * len(title)) + for row in nbsRows: + (pkg_list, arch_list, source, version) = row + pkg_string = ' '.join(pkg_list) + arch_string = ','.join(arch_list) + print "* source package %s version %s no longer builds" % \ + (source, version) + print " binary package(s): %s" % pkg_string + print " on %s" % arch_string + print " - suggested command:" + message = '"[auto-cruft] NBS (no longer built by %s)"' % source + print " dak rm -m %s -s %s -a %s -p -R -b %s" % \ + (message, suite_name, arch_string, pkg_string) + if rdeps: + if utils.check_reverse_depends(pkg_list, suite_name, arch_list, session, True): + print + else: + print " - No dependency problem found\n" + else: + print + session.close() - if len(cmd_output): - print output - print "Suggested commands:\n" - print cmd_output +def reportAllNBS(suite_name, suite_id, session, rdeps=False): + reportWithoutSource(suite_name, suite_id, session, rdeps) + reportNewerAll(suite_name, session) + reportNBS(suite_name, suite_id, rdeps) ################################################################################ @@ -261,7 +313,7 @@ def do_dubious_nbs(dubious_nbs): source_binaries.get(source, "(source does not exist)")) print " won't admit to building:" versions = dubious_nbs[source].keys() - versions.sort(apt_pkg.VersionCompare) + versions.sort(apt_pkg.version_compare) for version in versions: packages = dubious_nbs[source][version].keys() packages.sort() @@ -271,47 +323,84 @@ def do_dubious_nbs(dubious_nbs): ################################################################################ -def do_obsolete_source(duplicate_bins, bin2source): - obsolete = {} - for key in duplicate_bins.keys(): - (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] = [ 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 - if binary in obsolete[source]: - obsolete[source].remove(binary) - - to_remove = [] - output = "Obsolete source package\n" - output += "-----------------------\n\n" - obsolete_keys = obsolete.keys() - obsolete_keys.sort() - for source in obsolete_keys: - if not obsolete[source]: - to_remove.append(source) - output += " * %s (%s)\n" % (source, source_versions[source]) - 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"], - bin2source[binary]["source"]) - else: - output += " o %s is not built.\n" % binary - output += "\n" - - if to_remove: - print output - - print "Suggested command:" - print " dak rm -S -p -m \"[auto-cruft] obsolete source package\" %s" % (" ".join(to_remove)) - print +def obsolete_source(suite_name, session): + """returns obsolete source packages for suite_name without binaries + in the same suite sorted by install_date; install_date should help + detecting source only (or binary throw away) uploads; duplicates in + the suite are skipped + + subquery 'source_suite_unique' returns source package names from + suite without duplicates; the rationale behind is that neither + cruft-report nor rm cannot handle duplicates (yet)""" + + query = """ +WITH source_suite_unique AS + (SELECT source, suite + FROM source_suite GROUP BY source, suite HAVING count(*) = 1) +SELECT ss.src, ss.source, ss.version, + to_char(ss.install_date, 'YYYY-MM-DD') AS install_date + FROM source_suite ss + JOIN source_suite_unique ssu + ON ss.source = ssu.source AND ss.suite = ssu.suite + JOIN suite s ON s.id = ss.suite + LEFT JOIN bin_associations_binaries bab + ON ss.src = bab.source AND ss.suite = bab.suite + WHERE s.suite_name = :suite_name AND bab.id IS NULL + ORDER BY install_date""" + args = { 'suite_name': suite_name } + return session.execute(query, args) + +def source_bin(source, session): + """returns binaries built by source for all or no suite grouped and + ordered by package name""" + + query = """ +SELECT b.package + FROM binaries b + JOIN src_associations_src sas ON b.source = sas.src + WHERE sas.source = :source + GROUP BY b.package + ORDER BY b.package""" + args = { 'source': source } + return session.execute(query, args) + +def newest_source_bab(suite_name, package, session): + """returns newest source that builds binary package in suite grouped + and sorted by source and package name""" + + query = """ +SELECT sas.source, MAX(sas.version) AS srcver + FROM src_associations_src sas + JOIN bin_associations_binaries bab ON sas.src = bab.source + JOIN suite s on s.id = bab.suite + WHERE s.suite_name = :suite_name AND bab.package = :package + GROUP BY sas.source, bab.package + ORDER BY sas.source, bab.package""" + args = { 'suite_name': suite_name, 'package': package } + return session.execute(query, args) + +def report_obsolete_source(suite_name, session): + rows = obsolete_source(suite_name, session) + if rows.rowcount == 0: + return + print \ +"""Obsolete source packages in suite %s +----------------------------------%s\n""" % \ + (suite_name, '-' * len(suite_name)) + for os_row in rows.fetchall(): + (src, old_source, version, install_date) = os_row + print " * obsolete source %s version %s installed at %s" % \ + (old_source, version, install_date) + for sb_row in source_bin(old_source, session): + (package, ) = sb_row + print " - has built binary %s" % package + for nsb_row in newest_source_bab(suite_name, package, session): + (new_source, srcver) = nsb_row + print " currently built by source %s version %s" % \ + (new_source, srcver) + print " - suggested command:" + rm_opts = "-S -p -m \"[auto-cruft] obsolete source package\"" + print " dak rm -s %s %s %s\n" % (suite_name, rm_opts, old_source) def get_suite_binaries(suite, session): # Initalize a large hash table of all binary packages @@ -329,6 +418,84 @@ def get_suite_binaries(suite, session): ################################################################################ +def report_outdated_nonfree(suite, session, rdeps=False): + + packages = {} + query = """WITH outdated_sources AS ( + SELECT s.source, s.version, s.id + FROM source s + JOIN src_associations sa ON sa.source = s.id + WHERE sa.suite IN ( + SELECT id + FROM suite + WHERE suite_name = :suite ) + AND sa.created < (now() - interval :delay) + EXCEPT SELECT s.source, max(s.version) AS version, max(s.id) + FROM source s + JOIN src_associations sa ON sa.source = s.id + WHERE sa.suite IN ( + SELECT id + FROM suite + WHERE suite_name = :suite ) + AND sa.created < (now() - interval :delay) + GROUP BY s.source ), + binaries AS ( + SELECT b.package, s.source, ( + SELECT a.arch_string + FROM architecture a + WHERE a.id = b.architecture ) AS arch + FROM binaries b + JOIN outdated_sources s ON s.id = b.source + JOIN bin_associations ba ON ba.bin = b.id + JOIN override o ON o.package = b.package AND o.suite = ba.suite + WHERE ba.suite IN ( + SELECT id + FROM suite + WHERE suite_name = :suite ) + AND o.component IN ( + SELECT id + FROM component + WHERE name = 'non-free' ) ) + SELECT DISTINCT package, source, arch + FROM binaries + ORDER BY source, package, arch""" + + res = session.execute(query, {'suite': suite, 'delay': "'15 days'"}) + for package in res: + binary = package[0] + source = package[1] + arch = package[2] + if arch == 'all': + continue + if not source in packages: + packages[source] = {} + if not binary in packages[source]: + packages[source][binary] = set() + packages[source][binary].add(arch) + if packages: + title = 'Outdated non-free binaries in suite %s' % suite + message = '"[auto-cruft] outdated non-free binaries"' + print '%s\n%s\n' % (title, '-' * len(title)) + for source in sorted(packages): + archs = set() + binaries = set() + print '* package %s has outdated non-free binaries' % source + print ' - suggested command:' + for binary in sorted(packages[source]): + binaries.add(binary) + archs = archs.union(packages[source][binary]) + print ' dak rm -m %s -s %s -a %s -p -R -b %s' % \ + (message, suite, ','.join(archs), ' '.join(binaries)) + if rdeps: + if utils.check_reverse_depends(list(binaries), suite, archs, session, True): + print + else: + print " - No dependency problem found\n" + else: + print + +################################################################################ + def main (): global suite, suite_id, source_binaries, source_versions @@ -336,32 +503,41 @@ def main (): Arguments = [('h',"help","Cruft-Report::Options::Help"), ('m',"mode","Cruft-Report::Options::Mode", "HasArg"), + ('R',"rdep-check", "Cruft-Report::Options::Rdep-Check"), ('s',"suite","Cruft-Report::Options::Suite","HasArg"), ('w',"wanna-build-dump","Cruft-Report::Options::Wanna-Build-Dump","HasArg")] - for i in [ "help" ]: + for i in [ "help", "Rdep-Check" ]: if not cnf.has_key("Cruft-Report::Options::%s" % (i)): cnf["Cruft-Report::Options::%s" % (i)] = "" - cnf["Cruft-Report::Options::Suite"] = cnf["Dinstall::DefaultSuite"] + + cnf["Cruft-Report::Options::Suite"] = cnf.get("Dinstall::DefaultSuite", "unstable") 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" + cnf["Cruft-Report::Options::Wanna-Build-Dump"] = "/srv/ftp-master.debian.org/scripts/nfu" - apt_pkg.ParseCommandLine(cnf.Cnf, Arguments, sys.argv) + apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) - Options = cnf.SubTree("Cruft-Report::Options") + Options = cnf.subtree("Cruft-Report::Options") if Options["Help"]: usage() + if Options["Rdep-Check"]: + rdeps = True + else: + rdeps = False + # Set up checks based on mode if Options["Mode"] == "daily": - checks = [ "nbs", "nviu", "obsolete source" ] + checks = [ "nbs", "nviu", "nvit", "obsolete source", "outdated non-free", "nfu" ] elif Options["Mode"] == "full": - checks = [ "nbs", "nviu", "obsolete source", "nfu", "dubious nbs", "bnb", "bms", "anais" ] + checks = [ "nbs", "nviu", "nvit", "obsolete source", "outdated non-free", "nfu", "dubious nbs", "bnb", "bms", "anais" ] + elif Options["Mode"] == "bdo": + checks = [ "nbs", "obsolete source" ] else: - 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', 'daily' or 'bdo' are understood." % (Options["Mode"])) usage(1) session = DBConn().session() @@ -374,36 +550,47 @@ def main (): source_versions = {} anais_output = "" - duplicate_bins = {} nfu_packages = {} suite = get_suite(Options["Suite"].lower(), session) + if not suite: + utils.fubar("Cannot find suite %s" % Options["Suite"].lower()) + suite_id = suite.suite_id suite_name = suite.suite_name.lower() + if "obsolete source" in checks: + report_obsolete_source(suite_name, session) + + if "nbs" in checks: + reportAllNBS(suite_name, suite_id, session, rdeps) + + if "outdated non-free" in checks: + report_outdated_nonfree(suite_name, session, rdeps) + bin_not_built = {} if "bnb" in checks: - bins_in_suite = get_suite_binaries(suite_name, session) + bins_in_suite = get_suite_binaries(suite, session) # Checks based on the Sources files - components = cnf.ValueList("Suite::%s::Components" % (suite_name)) + components = get_component_names(session) for component in components: - 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... + filename = "%s/dists/%s/%s/source/Sources.gz" % (suite.archive.path, suite_name, component) + # apt_pkg.TagFile needs a real file handle and can't handle a GzipFile instance... (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 = 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') + Sources = apt_pkg.TagFile(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 = [ i.strip() for i in binaries.split(',') ] if "bnb" in checks: @@ -416,18 +603,10 @@ def main (): if "anais" in checks: anais_output += do_anais(architecture, binaries_list, source, session) - # Check for duplicated packages and build indices for checking "no source" later + # build indices for checking "no source" later source_index = component + '/' + source - if src_pkgs.has_key(source): - print " %s is a duplicated source package (%s and %s)" % (source, source_index, src_pkgs[source]) src_pkgs[source] = source_index for binary in binaries_list: - if bin_pkgs.has_key(binary): - key_list = [ source, bin_pkgs[binary] ] - key_list.sort() - key = '_'.join(key_list) - duplicate_bins.setdefault(key, []) - duplicate_bins[key].append(binary) bin_pkgs[binary] = source source_binaries[source] = binaries source_versions[source] = source_version @@ -447,8 +626,8 @@ def main (): for architecture in architectures: 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 + filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (suite.archive.path, suite_name, component, architecture) + # apt_pkg.TagFile needs a real file handle (fd, temp_filename) = utils.temp_filename() (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename)) if (result != 0): @@ -460,15 +639,15 @@ def main (): nfu_entries = parse_nfu(architecture) packages = utils.open_file(temp_filename) - Packages = apt_pkg.ParseTagFile(packages) - while Packages.Step(): - package = Packages.Section.Find('Package') - source = Packages.Section.Find('Source', "") - version = Packages.Section.Find('Version') + Packages = apt_pkg.TagFile(packages) + while Packages.step(): + package = Packages.section.find('Package') + source = Packages.section.find('Source', "") + version = Packages.section.find('Version') if source == "": source = package if bin2source.has_key(package) and \ - apt_pkg.VersionCompare(version, bin2source[package]["version"]) > 0: + apt_pkg.version_compare(version, bin2source[package]["version"]) > 0: bin2source[package]["version"] = version bin2source[package]["source"] = source else: @@ -484,14 +663,6 @@ def main (): nbs[source].setdefault(package, {}) nbs[source][package][version] = "" else: - previous_source = bin_pkgs[package] - if previous_source != source: - key_list = [ source, previous_source ] - key_list.sort() - 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 @@ -500,28 +671,22 @@ def main (): packages.close() os.unlink(temp_filename) - if "obsolete source" in checks: - do_obsolete_source(duplicate_bins, bin2source) - # Distinguish dubious (version numbers match) and 'real' NBS (they don't) dubious_nbs = {} - real_nbs = {} for source in nbs.keys(): for package in nbs[source].keys(): versions = nbs[source][package].keys() - versions.sort(apt_pkg.VersionCompare) + versions.sort(apt_pkg.version_compare) latest_version = versions.pop() source_version = source_versions.get(source,"0") - if apt_pkg.VersionCompare(latest_version, source_version) == 0: + if apt_pkg.version_compare(latest_version, source_version) == 0: add_nbs(dubious_nbs, source, latest_version, package, suite_id, session) - else: - add_nbs(real_nbs, source, latest_version, package, suite_id, session) if "nviu" in checks: do_newer_version('unstable', 'experimental', 'NVIU', session) - if "nbs" in checks: - do_nbs(real_nbs) + if "nvit" in checks: + do_newer_version('testing', 'testing-proposed-updates', 'NVIT', session) ### @@ -545,15 +710,7 @@ def main (): print if "bms" in checks: - print "Built from multiple source packages" - print "-----------------------------------" - print - keys = duplicate_bins.keys() - keys.sort() - for key in keys: - (source_a, source_b) = key.split("_") - print " o %s & %s => %s" % (source_a, source_b, ", ".join(duplicate_bins[key])) - print + report_multiple_source(suite) if "anais" in checks: print "Architecture Not Allowed In Source"