]> git.decadent.org.uk Git - dak.git/blob - rene
2005-01-14 Anthony Towns <ajt@debian.org> * kelly: when UNACCEPTing, don't double...
[dak.git] / rene
1 #!/usr/bin/env python
2
3 # Check for obsolete binary packages
4 # Copyright (C) 2000, 2001, 2002, 2003  James Troup <james@nocrew.org>
5 # $Id: rene,v 1.22 2003-10-03 16:39:20 troup Exp $
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ################################################################################
22
23 # ``If you're claiming that's a "problem" that needs to be "fixed",
24 #   you might as well write some letters to God about how unfair entropy
25 #   is while you're at it.'' -- 20020802143104.GA5628@azure.humbug.org.au
26
27 ## 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
28
29 ################################################################################
30
31 import commands, pg, os, string, sys, tempfile, time;
32 import utils, db_access;
33 import apt_pkg;
34
35 ################################################################################
36
37 Cnf = None;
38 projectB = None;
39 suite_id = None;
40 no_longer_in_suite = {}; # Really should be static to add_nbs, but I'm lazy
41
42 source_binaries = {};
43 source_versions = {};
44
45 ################################################################################
46
47 def usage(exit_code=0):
48     print """Usage: rene
49 Check for obsolete or duplicated packages.
50
51   -h, --help                show this help and exit.
52   -m, --mode=MODE           chose the MODE to run in (full or daily)."""
53     sys.exit(exit_code)
54
55 ################################################################################
56
57 def add_nbs(nbs_d, source, version, package):
58     # Ensure the package is still unstable (someone may have already removed it)
59     if no_longer_in_suite.has_key(package):
60         return;
61     else:
62         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));
63         if not q.getresult():
64             no_longer_in_suite[package] = "";
65             return;
66
67     if not nbs_d.has_key(source):
68         nbs_d[source] = {};
69     if not nbs_d[source].has_key(version):
70         nbs_d[source][version] = {};
71     nbs_d[source][version][package] = "";
72
73 ################################################################################
74
75 # Check for packages built on architectures they shouldn't be.
76 def do_anais(architecture, binaries_list, source):
77     if architecture == "any" or architecture == "all":
78         return "";
79
80     anais_output = "";
81     architectures = {};
82     for arch in architecture.split():
83         architectures[arch.strip()] = "";
84     for binary in binaries_list:
85         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));
86         ql = q.getresult();
87         versions = [];
88         for i in ql:
89             arch = i[0];
90             version = i[1];
91             if architectures.has_key(arch):
92                 versions.append(version);
93         versions.sort(apt_pkg.VersionCompare);
94         if versions:
95             latest_version = versions.pop()
96         else:
97             latest_version = None;
98         # Check for 'invalid' architectures
99         versions_d = {}
100         for i in ql:
101             arch = i[0];
102             version = i[1];
103             if not architectures.has_key(arch):
104                 if not versions_d.has_key(version):
105                     versions_d[version] = [];
106                 versions_d[version].append(arch)
107
108         if versions_d != {}:
109             anais_output += "\n (*) %s_%s [%s]: %s\n" % (binary, latest_version, source, architecture);
110             versions = versions_d.keys();
111             versions.sort(apt_pkg.VersionCompare);
112             for version in versions:
113                 arches = versions_d[version];
114                 arches.sort();
115                 anais_output += "    o %s: %s\n" % (version, ", ".join(arches));
116     return anais_output;
117
118 ################################################################################
119
120 def do_nviu():
121     experimental_id = db_access.get_suite_id("experimental");
122     if experimental_id == -1:
123         return;
124     # Check for packages in experimental obsoleted by versions in unstable
125     q = projectB.query("""
126 SELECT s.source, s.version AS experimental, s2.version AS unstable
127   FROM src_associations sa, source s, source s2, src_associations sa2
128   WHERE sa.suite = %s AND sa2.suite = %d AND sa.source = s.id
129    AND sa2.source = s2.id AND s.source = s2.source
130    AND versioncmp(s.version, s2.version) < 0""" % (experimental_id,
131                                                    db_access.get_suite_id("unstable")));
132     ql = q.getresult();
133     if ql:
134         nviu_to_remove = [];
135         print "Newer version in unstable";
136         print "-------------------------";
137         print ;
138         for i in ql:
139             (source, experimental_version, unstable_version) = i;
140             print " o %s (%s, %s)" % (source, experimental_version, unstable_version);
141             nviu_to_remove.append(source);
142         print
143         print "Suggested command:"
144         print " melanie -m \"[rene] NVIU\" -s experimental %s" % (" ".join(nviu_to_remove));
145         print
146
147 ################################################################################
148
149 def do_nbs(real_nbs):
150     output = "";
151     output += "Not Built from Source\n";
152     output += "---------------------\n\n";
153
154     nbs_to_remove = [];
155     nbs_keys = real_nbs.keys();
156     nbs_keys.sort();
157     for source in nbs_keys:
158         output += " * %s_%s builds: %s\n" % (source,
159                                        source_versions.get(source, "??"),
160                                        source_binaries.get(source, "(source does not exist)"));
161         output += "      but no longer builds:\n"
162         versions = real_nbs[source].keys();
163         versions.sort(apt_pkg.VersionCompare);
164         for version in versions:
165             packages = real_nbs[source][version].keys();
166             packages.sort();
167             for pkg in packages:
168                 # *cough* FIXME
169                 if pkg.find("pcmcia") == -1:
170                     nbs_to_remove.append(pkg);
171             output += "        o %s: %s\n" % (version, ", ".join(packages));
172
173         output += "\n";
174
175     if nbs_to_remove:
176         print output;
177
178         print "Suggested command:"
179         print " melanie -m \"[rene] NBS\" -b %s" % (" ".join(nbs_to_remove));
180         print
181
182 ################################################################################
183
184 def do_dubious_nbs(dubious_nbs):
185     print "Dubious NBS";
186     print "-----------";
187     print ;
188
189     dubious_nbs_keys = dubious_nbs.keys();
190     dubious_nbs_keys.sort();
191     for source in dubious_nbs_keys:
192         print " * %s_%s builds: %s" % (source,
193                                        source_versions.get(source, "??"),
194                                        source_binaries.get(source, "(source does not exist)"));
195         print "      won't admit to building:"
196         versions = dubious_nbs[source].keys();
197         versions.sort(apt_pkg.VersionCompare);
198         for version in versions:
199             packages = dubious_nbs[source][version].keys();
200             packages.sort();
201             print "        o %s: %s" % (version, ", ".join(packages));
202
203         print ;
204
205 ################################################################################
206
207 def main ():
208     global Cnf, projectB, suite_id, source_binaries, source_versions;
209
210     Cnf = utils.get_conf();
211
212     Arguments = [('h',"help","Rene::Options::Help"),
213                  ('m',"mode","Rene::Options::Mode", "HasArg")];
214     for i in [ "help" ]:
215         if not Cnf.has_key("Rene::Options::%s" % (i)):
216             Cnf["Rene::Options::%s" % (i)] = "";
217
218     if not Cnf.has_key("Rene::Options::Mode"):
219         Cnf["Rene::Options::Mode"] = "daily";
220
221     apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv);
222
223     Options = Cnf.SubTree("Rene::Options")
224     if Options["Help"]:
225         usage();
226
227     # Set up checks based on mode
228     if Options["Mode"] == "daily":
229         checks = [ "nbs", "nviu" ];
230     elif Options["Mode"] == "full":
231         checks = [ "nbs", "nviu", "dubious nbs", "bnb", "bms", "anais" ];
232     else:
233         utils.warn("%s is not a recognised mode - only 'full' or 'daily' are understood." % (Options["Mode"]));
234         usage(1);
235
236     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]));
237     db_access.init(Cnf, projectB);
238
239     bin_pkgs = {};
240     src_pkgs = {};
241     bins_in_suite = {};
242     nbs = {};
243     source_versions = {};
244
245     anais_output = "";
246     duplicate_bins = {};
247
248     suite = "unstable";
249     suite_id = db_access.get_suite_id(suite);
250
251     bin_not_built = {};
252
253     if "bnb" in checks:
254         # Initalize a large hash table of all binary packages
255         before = time.time();
256         sys.stderr.write("[Getting a list of binary packages in %s..." % (suite));
257         q = projectB.query("SELECT distinct b.package FROM binaries b, bin_associations ba WHERE ba.suite = %s AND ba.bin = b.id" % (suite_id));
258         ql = q.getresult();
259         sys.stderr.write("done. (%d seconds)]\n" % (int(time.time()-before)));
260         for i in ql:
261             bins_in_suite[i[0]] = "";
262
263     # Checks based on the Sources files
264     components = Cnf.ValueList("Suite::%s::Components" % (suite));
265     for component in components:
266         filename = "%s/dists/%s/%s/source/Sources.gz" % (Cnf["Dir::Root"], suite, component);
267         # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
268         temp_filename = tempfile.mktemp();
269         fd = os.open(temp_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
270         os.close(fd);
271         (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename));
272         if (result != 0):
273             sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output));
274             sys.exit(result);
275         sources = utils.open_file(temp_filename);
276         Sources = apt_pkg.ParseTagFile(sources);
277         while Sources.Step():
278             source = Sources.Section.Find('Package');
279             source_version = Sources.Section.Find('Version');
280             architecture = Sources.Section.Find('Architecture');
281             binaries = Sources.Section.Find('Binary');
282             binaries_list = map(string.strip, binaries.split(','));
283
284             if "bnb" in checks:
285                 # Check for binaries not built on any architecture.
286                 for binary in binaries_list:
287                     if not bins_in_suite.has_key(binary):
288                         if not bin_not_built.has_key(source):
289                             bin_not_built[source] = {};
290                         bin_not_built[source][binary] = "";
291
292             if "anais" in checks:
293                 anais_output += do_anais(architecture, binaries_list, source);
294
295             # Check for duplicated packages and build indices for checking "no source" later
296             source_index = component + '/' + source;
297             if src_pkgs.has_key(source):
298                 print " %s is a duplicated source package (%s and %s)" % (source, source_index, src_pkgs[source]);
299             src_pkgs[source] = source_index;
300             for binary in binaries_list:
301                 if bin_pkgs.has_key(binary):
302                     key = "%s~%s" % (source, bin_pkgs[binary]);
303                     if not duplicate_bins.has_key(key):
304                         duplicate_bins[key] = [];
305                     duplicate_bins[key].append(binary);
306                 bin_pkgs[binary] = source;
307             source_binaries[source] = binaries;
308             source_versions[source] = source_version;
309
310         sources.close();
311         os.unlink(temp_filename);
312
313     # Checks based on the Packages files
314     for component in components + ['main/debian-installer']:
315         architectures = filter(utils.real_arch, Cnf.ValueList("Suite::%s::Architectures" % (suite)));
316         for architecture in architectures:
317             filename = "%s/dists/%s/%s/binary-%s/Packages" % (Cnf["Dir::Root"], suite, component, architecture);
318             packages = utils.open_file(filename);
319             Packages = apt_pkg.ParseTagFile(packages);
320             while Packages.Step():
321                 package = Packages.Section.Find('Package');
322                 source = Packages.Section.Find('Source', "");
323                 version = Packages.Section.Find('Version');
324                 if source == "":
325                     source = package;
326                 if source.find("(") != -1:
327                     m = utils.re_extract_src_version.match(source);
328                     source = m.group(1);
329                     version = m.group(2);
330                 if not bin_pkgs.has_key(package):
331                     if not nbs.has_key(source):
332                         nbs[source] = {};
333                     if not nbs[source].has_key(package):
334                         nbs[source][package] = {};
335                     nbs[source][package][version] = "";
336             packages.close();
337
338     dubious_nbs = {};
339     real_nbs = {};
340     for source in nbs.keys():
341         for package in nbs[source].keys():
342             versions = nbs[source][package].keys();
343             versions.sort(apt_pkg.VersionCompare);
344             latest_version = versions.pop();
345             source_version = source_versions.get(source,"0");
346             # *cough* FIXME
347             if apt_pkg.VersionCompare(latest_version, source_version) == 0 or source == "pcmcia-cs":
348                 add_nbs(dubious_nbs, source, latest_version, package);
349             else:
350                 add_nbs(real_nbs, source, latest_version, package);
351
352     if "nviu" in checks:
353         do_nviu();
354
355     if "nbs" in checks:
356         do_nbs(real_nbs);
357
358     ###
359
360     if Options["Mode"] == "full":
361         print "="*75
362         print
363
364     if "bnb" in checks:
365         print "Unbuilt binary packages";
366         print "-----------------------";
367         print
368         keys = bin_not_built.keys();
369         keys.sort();
370         for source in keys:
371             binaries = bin_not_built[source].keys();
372             binaries.sort();
373             print " o %s: %s" % (source, ", ".join(binaries));
374         print ;
375
376     if "bms" in checks:
377         print "Built from multiple source packages";
378         print "-----------------------------------";
379         print ;
380         keys = duplicate_bins.keys();
381         keys.sort();
382         for key in keys:
383             (source_a, source_b) = key.split("~");
384             print " o %s & %s => %s" % (source_a, source_b, ", ".join(duplicate_bins[key]));
385         print ;
386
387     if "anais" in checks:
388         print "Architecture Not Allowed In Source";
389         print "----------------------------------";
390         print anais_output;
391         print ;
392
393     if "dubious nbs" in checks:
394         do_dubious_nbs(dubious_nbs);
395
396
397 ################################################################################
398
399 if __name__ == '__main__':
400     main()