]> git.decadent.org.uk Git - dak.git/blob - dak/cruft_report.py
Merge remote branch 'twerner/cruft' into merge
[dak.git] / dak / cruft_report.py
1 #!/usr/bin/env python
2
3 """
4 Check for obsolete binary packages
5
6 @contact: Debian FTP Master <ftpmaster@debian.org>
7 @copyright: 2000-2006 James Troup <james@nocrew.org>
8 @copyright: 2009      Torsten Werner <twerner@debian.org>
9 @license: GNU General Public License version 2 or later
10 """
11
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
16
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
26 ################################################################################
27
28 # ``If you're claiming that's a "problem" that needs to be "fixed",
29 #   you might as well write some letters to God about how unfair entropy
30 #   is while you're at it.'' -- 20020802143104.GA5628@azure.humbug.org.au
31
32 ## TODO:  fix NBS looping for version, implement Dubious NBS, fix up output of
33 ##        duplicate source package stuff, improve experimental ?, add overrides,
34 ##        avoid ANAIS for duplicated packages
35
36 ################################################################################
37
38 import commands, os, sys, re
39 import apt_pkg
40
41 from daklib.config import Config
42 from daklib.dbconn import *
43 from daklib import utils
44 from daklib.regexes import re_extract_src_version
45
46 ################################################################################
47
48 no_longer_in_suite = {}; # Really should be static to add_nbs, but I'm lazy
49
50 source_binaries = {}
51 source_versions = {}
52
53 ################################################################################
54
55 def usage(exit_code=0):
56     print """Usage: dak cruft-report
57 Check for obsolete or duplicated packages.
58
59   -h, --help                show this help and exit.
60   -m, --mode=MODE           chose the MODE to run in (full or daily).
61   -s, --suite=SUITE         check suite SUITE.
62   -w, --wanna-build-dump    where to find the copies of http://buildd.debian.org/stats/*.txt"""
63     sys.exit(exit_code)
64
65 ################################################################################
66
67 def add_nbs(nbs_d, source, version, package, suite_id, session):
68     # Ensure the package is still in the suite (someone may have already removed it)
69     if no_longer_in_suite.has_key(package):
70         return
71     else:
72         q = session.execute("""SELECT b.id FROM binaries b, bin_associations ba
73                                 WHERE ba.bin = b.id AND ba.suite = :suite_id
74                                   AND b.package = :package LIMIT 1""", {'suite_id': suite_id,
75                                                                          'package': package})
76         if not q.fetchall():
77             no_longer_in_suite[package] = ""
78             return
79
80     nbs_d.setdefault(source, {})
81     nbs_d[source].setdefault(version, {})
82     nbs_d[source][version][package] = ""
83
84 ################################################################################
85
86 # Check for packages built on architectures they shouldn't be.
87 def do_anais(architecture, binaries_list, source, session):
88     if architecture == "any" or architecture == "all":
89         return ""
90
91     anais_output = ""
92     architectures = {}
93     for arch in architecture.split():
94         architectures[arch.strip()] = ""
95     for binary in binaries_list:
96         q = session.execute("""SELECT a.arch_string, b.version
97                                 FROM binaries b, bin_associations ba, architecture a
98                                WHERE ba.suite = :suiteid AND ba.bin = b.id
99                                  AND b.architecture = a.id AND b.package = :package""",
100                              {'suiteid': suite_id, 'package': binary})
101         ql = q.fetchall()
102         versions = []
103         for i in ql:
104             arch = i[0]
105             version = i[1]
106             if architectures.has_key(arch):
107                 versions.append(version)
108         versions.sort(apt_pkg.VersionCompare)
109         if versions:
110             latest_version = versions.pop()
111         else:
112             latest_version = None
113         # Check for 'invalid' architectures
114         versions_d = {}
115         for i in ql:
116             arch = i[0]
117             version = i[1]
118             if not architectures.has_key(arch):
119                 versions_d.setdefault(version, [])
120                 versions_d[version].append(arch)
121
122         if versions_d != {}:
123             anais_output += "\n (*) %s_%s [%s]: %s\n" % (binary, latest_version, source, architecture)
124             versions = versions_d.keys()
125             versions.sort(apt_pkg.VersionCompare)
126             for version in versions:
127                 arches = versions_d[version]
128                 arches.sort()
129                 anais_output += "    o %s: %s\n" % (version, ", ".join(arches))
130     return anais_output
131
132
133 ################################################################################
134
135 # Check for out-of-date binaries on architectures that do not want to build that
136 # package any more, and have them listed as Not-For-Us
137 def do_nfu(nfu_packages):
138     output = ""
139     
140     a2p = {}
141
142     for architecture in nfu_packages:
143         a2p[architecture] = []
144         for (package,bver,sver) in nfu_packages[architecture]:
145             output += "  * [%s] does not want %s (binary %s, source %s)\n" % (architecture, package, bver, sver)
146             a2p[architecture].append(package)
147
148
149     if output:
150         print "Obsolete by Not-For-Us"
151         print "----------------------"
152         print
153         print output
154
155         print "Suggested commands:"
156         for architecture in a2p:
157             if a2p[architecture]:
158                 print (" dak rm -m \"[auto-cruft] NFU\" -s %s -a %s -b %s" % 
159                     (suite, architecture, " ".join(a2p[architecture])))
160         print
161
162 def parse_nfu(architecture):
163     cnf = Config()
164     # utils/hpodder_1.1.5.0: Not-For-Us [optional:out-of-date]
165     r = re.compile("^\w+/([^_]+)_.*: Not-For-Us")
166
167     ret = set()
168     
169     filename = "%s/%s-all.txt" % (cnf["Cruft-Report::Options::Wanna-Build-Dump"], architecture)
170
171     # Not all architectures may have a wanna-build dump, so we want to ignore missin
172     # files
173     if os.path.exists(filename):
174         f = utils.open_file(filename)
175         for line in f:
176             if line[0] == ' ':
177                 continue
178
179             m = r.match(line)
180             if m:
181                 ret.add(m.group(1))
182
183         f.close()
184     else:
185         utils.warn("No wanna-build dump file for architecture %s" % architecture)
186     return ret
187
188 ################################################################################
189
190 def do_newer_version(lowersuite_name, highersuite_name, code, session):
191     lowersuite = get_suite(lowersuite_name, session)
192     if not lowersuite:
193         return
194
195     highersuite = get_suite(highersuite_name, session)
196     if not highersuite:
197         return
198
199     # Check for packages in $highersuite obsoleted by versions in $lowersuite
200     q = session.execute("""
201 SELECT s.source, s.version AS lower, s2.version AS higher
202   FROM src_associations sa, source s, source s2, src_associations sa2
203   WHERE sa.suite = :highersuite_id AND sa2.suite = :lowersuite_id AND sa.source = s.id
204    AND sa2.source = s2.id AND s.source = s2.source
205    AND s.version < s2.version""", {'lowersuite_id': lowersuite.suite_id,
206                                     'highersuite_id': highersuite.suite_id})
207     ql = q.fetchall()
208     if ql:
209         nv_to_remove = []
210         print "Newer version in %s" % lowersuite.suite_name
211         print "-----------------" + "-" * len(lowersuite.suite_name)
212         print
213         for i in ql:
214             (source, higher_version, lower_version) = i
215             print " o %s (%s, %s)" % (source, higher_version, lower_version)
216             nv_to_remove.append(source)
217         print
218         print "Suggested command:"
219         print " dak rm -m \"[auto-cruft] %s\" -s %s %s" % (code, highersuite.suite_name,
220                                                            " ".join(nv_to_remove))
221         print
222
223 ################################################################################
224
225 def queryWithoutSource(suite_id, session):
226     """searches for arch: all packages from suite that do no longer
227     reference a source package in the same suite
228
229     subquery unique_binaries: selects all packages with only 1 version
230     in suite since 'dak rm' does not allow to specify version numbers"""
231
232     query = """
233     with unique_binaries as
234         (select package, max(version) as version, max(source) as source
235             from bin_associations_binaries
236             where architecture = 2 and suite = :suite_id
237             group by package having count(*) = 1)
238     select ub.package, ub.version
239         from unique_binaries ub
240         left join src_associations_src sas
241             on ub.source = sas.src and sas.suite = :suite_id
242         where sas.id is null
243         order by ub.package"""
244     return session.execute(query, { 'suite_id': suite_id })
245
246 def reportWithoutSource(suite_name, suite_id, session):
247     rows = queryWithoutSource(suite_id, session)
248     title = 'packages without source in suite %s' % suite_name
249     if rows.rowcount > 0:
250         print '%s\n%s\n' % (title, '-' * len(title))
251     message = '"[auto-cruft] no longer built from source"'
252     for row in rows:
253         (package, version) = row
254         print "* package %s in version %s is no longer built from source" % \
255             (package, version)
256         print "  - suggested command:"
257         print "    dak rm -m %s -s %s -a all -p -R -b %s\n" % \
258             (message, suite_name, package)
259
260 def queryNewerAll(suite_name, session):
261     """searches for arch != all packages that have an arch == all
262     package with a higher version in the same suite"""
263
264     query = """
265 select bab1.package, bab1.version as oldver,
266     array_to_string(array_agg(a.arch_string), ',') as oldarch,
267     bab2.version as newver
268     from bin_associations_binaries bab1
269     join bin_associations_binaries bab2
270         on bab1.package = bab2.package and bab1.version < bab2.version and
271         bab1.suite = bab2.suite and bab1.architecture > 2 and
272         bab2.architecture = 2
273     join architecture a on bab1.architecture = a.id
274     join suite s on bab1.suite = s.id
275     where s.suite_name = :suite_name
276     group by bab1.package, oldver, bab1.suite, newver"""
277     return session.execute(query, { 'suite_name': suite_name })
278
279 def reportNewerAll(suite_name, session):
280     rows = queryNewerAll(suite_name, session)
281     title = 'obsolete arch any packages in suite %s' % suite_name
282     if rows.rowcount > 0:
283         print '%s\n%s\n' % (title, '-' * len(title))
284     message = '"[auto-cruft] obsolete arch any package"'
285     for row in rows:
286         (package, oldver, oldarch, newver) = row
287         print "* package %s is arch any in version %s but arch all in version %s" % \
288             (package, oldver, newver)
289         print "  - suggested command:"
290         print "    dak rm -m %s -s %s -a %s -p -b %s\n" % \
291             (message, suite_name, oldarch, package)
292
293 def queryNBS(suite_id, session):
294     """This one is really complex. It searches arch != all packages that
295     are no longer built from current source packages in suite.
296
297     temp table unique_binaries: will be populated with packages that
298     have only one version in suite because 'dak rm' does not allow
299     specifying version numbers
300
301     temp table newest_binaries: will be populated with packages that are
302     built from current sources
303
304     subquery uptodate_arch: returns all architectures built from current
305     sources
306
307     subquery unique_binaries_uptodate_arch: returns all packages in
308     architectures from uptodate_arch
309
310     subquery unique_binaries_uptodate_arch_agg: same as
311     unique_binaries_uptodate_arch but with column architecture
312     aggregated to array
313
314     subquery uptodate_packages: similar to uptodate_arch but returns all
315     packages built from current sources
316
317     subquery outdated_packages: returns all packages with architectures
318     no longer built from current source
319     """
320
321     query = """
322 create temp table unique_binaries (
323     package text not null,
324     architecture integer not null,
325     source integer not null);
326
327 insert into unique_binaries
328     select bab.package, bab.architecture, max(bab.source)
329         from bin_associations_binaries bab
330         where bab.suite = :suite_id and bab.architecture > 2
331         group by package, architecture having count(*) = 1;
332
333 create temp table newest_binaries (
334     package text not null,
335     architecture integer not null,
336     source text not null,
337     version debversion not null);
338
339 insert into newest_binaries
340     select ub.package, ub.architecture, nsa.source, nsa.version
341         from unique_binaries ub
342         join newest_src_association nsa
343             on ub.source = nsa.src and nsa.suite = :suite_id;
344
345 with uptodate_arch as
346     (select architecture, source, version
347         from newest_binaries
348         group by architecture, source, version),
349     unique_binaries_uptodate_arch as
350     (select ub.package, ub.architecture, ua.source, ua.version
351         from unique_binaries ub
352         join source s
353             on ub.source = s.id
354         join uptodate_arch ua
355             on ub.architecture = ua.architecture and s.source = ua.source),
356     unique_binaries_uptodate_arch_agg as
357     (select ubua.package,
358         array(select unnest(array_agg(a.arch_string)) order by 1) as arch_list,
359         ubua.source, ubua.version
360         from unique_binaries_uptodate_arch ubua
361         join architecture a
362             on ubua.architecture = a.id
363         group by ubua.source, ubua.version, ubua.package),
364     uptodate_packages as
365     (select package, source, version
366         from newest_binaries
367         group by package, source, version),
368     outdated_packages as
369     (select array(select unnest(array_agg(package)) order by 1) as pkg_list,
370         arch_list, source, version
371         from unique_binaries_uptodate_arch_agg
372         where package not in
373             (select package from uptodate_packages)
374         group by arch_list, source, version)
375     select * from outdated_packages order by source"""
376     return session.execute(query, { 'suite_id': suite_id })
377
378 def reportNBS(suite_name, suite_id):
379     session = DBConn().session()
380     nbsRows = queryNBS(suite_id, session)
381     title = 'NBS packages in suite %s' % suite_name
382     if nbsRows.rowcount > 0:
383         print '%s\n%s\n' % (title, '-' * len(title))
384     for row in nbsRows:
385         (pkg_list, arch_list, source, version) = row
386         pkg_string = ' '.join(pkg_list)
387         arch_string = ','.join(arch_list)
388         print "* source package %s version %s no longer builds" % \
389             (source, version)
390         print "  binary package(s): %s" % pkg_string
391         print "  on %s" % arch_string
392         print "  - suggested command:"
393         message = '"[auto-cruft] NBS (no longer built by %s)"' % source
394         print "    dak rm -m %s -s %s -a %s -p -R -b %s\n" % \
395             (message, suite_name, arch_string, pkg_string)
396     session.close()
397
398 def reportAllNBS(suite_name, suite_id, session):
399     reportWithoutSource(suite_name, suite_id, session)
400     reportNewerAll(suite_name, session)
401     reportNBS(suite_name, suite_id)
402
403 ################################################################################
404
405 def do_dubious_nbs(dubious_nbs):
406     print "Dubious NBS"
407     print "-----------"
408     print
409
410     dubious_nbs_keys = dubious_nbs.keys()
411     dubious_nbs_keys.sort()
412     for source in dubious_nbs_keys:
413         print " * %s_%s builds: %s" % (source,
414                                        source_versions.get(source, "??"),
415                                        source_binaries.get(source, "(source does not exist)"))
416         print "      won't admit to building:"
417         versions = dubious_nbs[source].keys()
418         versions.sort(apt_pkg.VersionCompare)
419         for version in versions:
420             packages = dubious_nbs[source][version].keys()
421             packages.sort()
422             print "        o %s: %s" % (version, ", ".join(packages))
423
424         print
425
426 ################################################################################
427
428 def obsolete_source(suite_name, session):
429     """returns obsolete source packages for suite_name without binaries
430     in the same suite sorted by install_date; install_date should help
431     detecting source only (or binary throw away) uploads; duplicates in
432     the suite are skipped
433
434     subquery 'source_suite_unique' returns source package names from
435     suite without duplicates; the rationale behind is that neither
436     cruft-report nor rm cannot handle duplicates (yet)"""
437
438     query = """
439 WITH source_suite_unique AS
440     (SELECT source, suite
441         FROM source_suite GROUP BY source, suite HAVING count(*) = 1)
442 SELECT ss.src, ss.source, ss.version,
443     to_char(ss.install_date, 'YYYY-MM-DD') AS install_date
444     FROM source_suite ss
445     JOIN source_suite_unique ssu
446         ON ss.source = ssu.source AND ss.suite = ssu.suite
447     JOIN suite s ON s.id = ss.suite
448     LEFT JOIN bin_associations_binaries bab
449         ON ss.src = bab.source AND ss.suite = bab.suite
450     WHERE s.suite_name = :suite_name AND bab.id IS NULL
451     ORDER BY install_date"""
452     args = { 'suite_name': suite_name }
453     return session.execute(query, args)
454
455 def source_bin(source, session):
456     """returns binaries built by source for all or no suite grouped and
457     ordered by package name"""
458
459     query = """
460 SELECT b.package
461     FROM binaries b
462     JOIN src_associations_src sas ON b.source = sas.src
463     WHERE sas.source = :source
464     GROUP BY b.package
465     ORDER BY b.package"""
466     args = { 'source': source }
467     return session.execute(query, args)
468
469 def newest_source_bab(suite_name, package, session):
470     """returns newest source that builds binary package in suite grouped
471     and sorted by source and package name"""
472
473     query = """
474 SELECT sas.source, MAX(sas.version) AS srcver
475     FROM src_associations_src sas
476     JOIN bin_associations_binaries bab ON sas.src = bab.source
477     JOIN suite s on s.id = bab.suite
478     WHERE s.suite_name = :suite_name AND bab.package = :package
479         GROUP BY sas.source, bab.package
480         ORDER BY sas.source, bab.package"""
481     args = { 'suite_name': suite_name, 'package': package }
482     return session.execute(query, args)
483
484 def report_obsolete_source(suite_name, session):
485     rows = obsolete_source(suite_name, session)
486     if rows.rowcount == 0:
487         return
488     print \
489 """Obsolete source packages in suite %s
490 ----------------------------------%s\n""" % \
491         (suite_name, '-' * len(suite_name))
492     for os_row in rows.fetchall():
493         (src, old_source, version, install_date) = os_row
494         print " * obsolete source %s version %s installed at %s" % \
495             (old_source, version, install_date)
496         for sb_row in source_bin(old_source, session):
497             (package, ) = sb_row
498             print "   - has built binary %s" % package
499             for nsb_row in newest_source_bab(suite_name, package, session):
500                 (new_source, srcver) = nsb_row
501                 print "     currently built by source %s version %s" % \
502                     (new_source, srcver)
503         print "   - suggested command:"
504         rm_opts = "-S -p -m \"[auto-cruft] obsolete source package\""
505         print "     dak rm -s %s %s %s\n" % (suite_name, rm_opts, old_source)
506
507 def get_suite_binaries(suite, session):
508     # Initalize a large hash table of all binary packages
509     binaries = {}
510
511     print "Getting a list of binary packages in %s..." % suite.suite_name
512     q = session.execute("""SELECT distinct b.package
513                              FROM binaries b, bin_associations ba
514                             WHERE ba.suite = :suiteid AND ba.bin = b.id""",
515                            {'suiteid': suite.suite_id})
516     for i in q.fetchall():
517         binaries[i[0]] = ""
518
519     return binaries
520
521 ################################################################################
522
523 def main ():
524     global suite, suite_id, source_binaries, source_versions
525
526     cnf = Config()
527
528     Arguments = [('h',"help","Cruft-Report::Options::Help"),
529                  ('m',"mode","Cruft-Report::Options::Mode", "HasArg"),
530                  ('s',"suite","Cruft-Report::Options::Suite","HasArg"),
531                  ('w',"wanna-build-dump","Cruft-Report::Options::Wanna-Build-Dump","HasArg")]
532     for i in [ "help" ]:
533         if not cnf.has_key("Cruft-Report::Options::%s" % (i)):
534             cnf["Cruft-Report::Options::%s" % (i)] = ""
535     cnf["Cruft-Report::Options::Suite"] = cnf["Dinstall::DefaultSuite"]
536
537     if not cnf.has_key("Cruft-Report::Options::Mode"):
538         cnf["Cruft-Report::Options::Mode"] = "daily"
539
540     if not cnf.has_key("Cruft-Report::Options::Wanna-Build-Dump"):
541         cnf["Cruft-Report::Options::Wanna-Build-Dump"] = "/srv/ftp.debian.org/scripts/nfu"
542
543     apt_pkg.ParseCommandLine(cnf.Cnf, Arguments, sys.argv)
544
545     Options = cnf.SubTree("Cruft-Report::Options")
546     if Options["Help"]:
547         usage()
548
549     # Set up checks based on mode
550     if Options["Mode"] == "daily":
551         checks = [ "nbs", "nviu", "nvit", "obsolete source" ]
552     elif Options["Mode"] == "full":
553         checks = [ "nbs", "nviu", "nvit", "obsolete source", "nfu", "dubious nbs", "bnb", "bms", "anais" ]
554     else:
555         utils.warn("%s is not a recognised mode - only 'full' or 'daily' are understood." % (Options["Mode"]))
556         usage(1)
557
558     session = DBConn().session()
559
560     bin_pkgs = {}
561     src_pkgs = {}
562     bin2source = {}
563     bins_in_suite = {}
564     nbs = {}
565     source_versions = {}
566
567     anais_output = ""
568     duplicate_bins = {}
569
570     nfu_packages = {}
571
572     suite = get_suite(Options["Suite"].lower(), session)
573     suite_id = suite.suite_id
574     suite_name = suite.suite_name.lower()
575
576     if "obsolete source" in checks:
577         report_obsolete_source(suite_name, session)
578
579     if "nbs" in checks:
580         reportAllNBS(suite_name, suite_id, session)
581
582     bin_not_built = {}
583
584     if "bnb" in checks:
585         bins_in_suite = get_suite_binaries(suite, session)
586
587     # Checks based on the Sources files
588     components = cnf.ValueList("Suite::%s::Components" % (suite_name))
589     for component in components:
590         filename = "%s/dists/%s/%s/source/Sources.gz" % (cnf["Dir::Root"], suite_name, component)
591         # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
592         (fd, temp_filename) = utils.temp_filename()
593         (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
594         if (result != 0):
595             sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
596             sys.exit(result)
597         sources = utils.open_file(temp_filename)
598         Sources = apt_pkg.ParseTagFile(sources)
599         while Sources.Step():
600             source = Sources.Section.Find('Package')
601             source_version = Sources.Section.Find('Version')
602             architecture = Sources.Section.Find('Architecture')
603             binaries = Sources.Section.Find('Binary')
604             binaries_list = [ i.strip() for i in  binaries.split(',') ]
605
606             if "bnb" in checks:
607                 # Check for binaries not built on any architecture.
608                 for binary in binaries_list:
609                     if not bins_in_suite.has_key(binary):
610                         bin_not_built.setdefault(source, {})
611                         bin_not_built[source][binary] = ""
612
613             if "anais" in checks:
614                 anais_output += do_anais(architecture, binaries_list, source, session)
615
616             # Check for duplicated packages and build indices for checking "no source" later
617             source_index = component + '/' + source
618             #if src_pkgs.has_key(source):
619             #    print " %s is a duplicated source package (%s and %s)" % (source, source_index, src_pkgs[source])
620             src_pkgs[source] = source_index
621             for binary in binaries_list:
622                 if bin_pkgs.has_key(binary):
623                     key_list = [ source, bin_pkgs[binary] ]
624                     key_list.sort()
625                     key = '_'.join(key_list)
626                     duplicate_bins.setdefault(key, [])
627                     duplicate_bins[key].append(binary)
628                 bin_pkgs[binary] = source
629             source_binaries[source] = binaries
630             source_versions[source] = source_version
631
632         sources.close()
633         os.unlink(temp_filename)
634
635     # Checks based on the Packages files
636     check_components = components[:]
637     if suite_name != "experimental":
638         check_components.append('main/debian-installer');
639
640     for component in check_components:
641         architectures = [ a.arch_string for a in get_suite_architectures(suite_name,
642                                                                          skipsrc=True, skipall=True,
643                                                                          session=session) ]
644         for architecture in architectures:
645             if component == 'main/debian-installer' and re.match("kfreebsd", architecture):
646                 continue
647             filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (cnf["Dir::Root"], suite_name, component, architecture)
648             # apt_pkg.ParseTagFile needs a real file handle
649             (fd, temp_filename) = utils.temp_filename()
650             (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
651             if (result != 0):
652                 sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
653                 sys.exit(result)
654
655             if "nfu" in checks:
656                 nfu_packages.setdefault(architecture,[])
657                 nfu_entries = parse_nfu(architecture)
658
659             packages = utils.open_file(temp_filename)
660             Packages = apt_pkg.ParseTagFile(packages)
661             while Packages.Step():
662                 package = Packages.Section.Find('Package')
663                 source = Packages.Section.Find('Source', "")
664                 version = Packages.Section.Find('Version')
665                 if source == "":
666                     source = package
667                 if bin2source.has_key(package) and \
668                        apt_pkg.VersionCompare(version, bin2source[package]["version"]) > 0:
669                     bin2source[package]["version"] = version
670                     bin2source[package]["source"] = source
671                 else:
672                     bin2source[package] = {}
673                     bin2source[package]["version"] = version
674                     bin2source[package]["source"] = source
675                 if source.find("(") != -1:
676                     m = re_extract_src_version.match(source)
677                     source = m.group(1)
678                     version = m.group(2)
679                 if not bin_pkgs.has_key(package):
680                     nbs.setdefault(source,{})
681                     nbs[source].setdefault(package, {})
682                     nbs[source][package][version] = ""
683                 else:
684                     previous_source = bin_pkgs[package]
685                     if previous_source != source:
686                         key_list = [ source, previous_source ]
687                         key_list.sort()
688                         key = '_'.join(key_list)
689                         duplicate_bins.setdefault(key, [])
690                         if package not in duplicate_bins[key]:
691                             duplicate_bins[key].append(package)
692                     if "nfu" in checks:
693                         if package in nfu_entries and \
694                                version != source_versions[source]: # only suggest to remove out-of-date packages
695                             nfu_packages[architecture].append((package,version,source_versions[source]))
696                     
697             packages.close()
698             os.unlink(temp_filename)
699
700     # Distinguish dubious (version numbers match) and 'real' NBS (they don't)
701     dubious_nbs = {}
702     for source in nbs.keys():
703         for package in nbs[source].keys():
704             versions = nbs[source][package].keys()
705             versions.sort(apt_pkg.VersionCompare)
706             latest_version = versions.pop()
707             source_version = source_versions.get(source,"0")
708             if apt_pkg.VersionCompare(latest_version, source_version) == 0:
709                 add_nbs(dubious_nbs, source, latest_version, package, suite_id, session)
710
711     if "nviu" in checks:
712         do_newer_version('unstable', 'experimental', 'NVIU', session)
713
714     if "nvit" in checks:
715         do_newer_version('testing', 'testing-proposed-updates', 'NVIT', session)
716
717     ###
718
719     if Options["Mode"] == "full":
720         print "="*75
721         print
722
723     if "nfu" in checks:
724         do_nfu(nfu_packages)
725
726     if "bnb" in checks:
727         print "Unbuilt binary packages"
728         print "-----------------------"
729         print
730         keys = bin_not_built.keys()
731         keys.sort()
732         for source in keys:
733             binaries = bin_not_built[source].keys()
734             binaries.sort()
735             print " o %s: %s" % (source, ", ".join(binaries))
736         print
737
738     if "bms" in checks:
739         print "Built from multiple source packages"
740         print "-----------------------------------"
741         print
742         keys = duplicate_bins.keys()
743         keys.sort()
744         for key in keys:
745             (source_a, source_b) = key.split("_")
746             print " o %s & %s => %s" % (source_a, source_b, ", ".join(duplicate_bins[key]))
747         print
748
749     if "anais" in checks:
750         print "Architecture Not Allowed In Source"
751         print "----------------------------------"
752         print anais_output
753         print
754
755     if "dubious nbs" in checks:
756         do_dubious_nbs(dubious_nbs)
757
758
759 ################################################################################
760
761 if __name__ == '__main__':
762     main()