3 # Script to automate some parts of checking NEW packages
4 # Copyright (C) 2000, 2001, 2002 James Troup <james@nocrew.org>
5 # $Id: fernanda.py,v 1.6 2002-11-26 02:21:46 anonymous Exp $
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.
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.
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
21 ################################################################################
23 # <Omnic> elmo wrote docs?!!?!?!?!?!?!
24 # <aj> as if he wasn't scary enough before!!
25 # * aj imagines a little red furry toy sitting hunched over a computer
26 # tapping furiously and giggling to himself
27 # <aj> eventually he stops, and his heads slowly spins around and you
28 # see this really evil grin and then he sees you, and picks up a
29 # knife from beside the keyboard and throws it at you, and as you
30 # breathe your last breath, he starts giggling again
31 # <aj> but i should be telling this to my psychiatrist, not you guys,
34 ################################################################################
36 import errno, os, re, sys
38 import apt_pkg, apt_inst
39 import pg, string, db_access
41 ################################################################################
44 re_package = re.compile(r"^(.+?)_.*");
45 re_doc_directory = re.compile(r".*/doc/([^/]*).*");
48 re_contrib = re.compile('^contrib/')
49 re_nonfree = re.compile('^non\-free/')
51 re_arch = re.compile("Architecture: .*")
52 re_builddep = re.compile("Build-Depends: .*")
53 re_builddepind = re.compile("Build-Depends-Indep: .*")
55 re_localhost = re.compile("localhost\.localdomain")
56 re_version = re.compile('^(.*)\((.*)\)')
58 re_newlinespace = re.compile('\n')
59 re_spacestrip = re.compile('(\s)')
61 ##############################################################################################################
66 main_colour = "\033[36m"
68 contrib_colour = "\033[33m"
70 nonfree_colour = "\033[31m"
72 arch_colour = "\033[32m"
74 end_colour = "\033[0m"
76 bold_colour = "\033[1m"
78 maintainer_colour = arch_colour
82 ################################################################################
87 Cnf = utils.get_conf()
88 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
89 db_access.init(Cnf, projectB);
93 ################################################################################
95 def usage (exit_code=0):
96 print """Usage: fernanda [PACKAGE]...
99 -h, --help show this help and exit
101 PACKAGE can be a .changes, .dsc, .deb or .udeb filename."""
105 ################################################################################
107 def get_depends_parts(depend) :
108 v_match = re_version.match(depend)
110 d_parts = { 'name' : v_match.group(1), 'version' : v_match.group(2) }
112 d_parts = { 'name' : depend , 'version' : '' }
115 def get_or_list(depend) :
116 or_list = string.split(depend, "|")
119 def get_comma_list(depend) :
120 dep_list = string.split(depend, ",")
124 def split_depends (d_str) :
125 # creates a list of lists of dictionaries of depends (package,version relation)
127 d_str = re_spacestrip.sub('',d_str)
129 # first split depends string up amongs comma delimiter
130 dep_list = get_comma_list(d_str)
132 while d < len(dep_list):
133 # put depends into their own list
134 depends_tree.append([dep_list[d]])
137 while d < len(depends_tree) :
139 # split up Or'd depends into a multi-item list
140 depends_tree[d] = get_or_list(depends_tree[d][0])
141 while k < len(depends_tree[d]) :
142 # split depends into {package, version relation}
143 depends_tree[d][k] = get_depends_parts(depends_tree[d][k])
148 def read_control (filename) :
159 deb_file = utils.open_file(filename);
161 extracts = apt_inst.debExtractControl(deb_file)
162 control = apt_pkg.ParseSection(extracts)
165 print "can't parse control info"
171 control_keys = control.keys()
179 if control.has_key("Depends"):
180 depends_str = control.Find("Depends")
181 # create list of dependancy lists
182 depends = split_depends(depends_str)
185 if control.has_key("Recommends"):
186 recommends_str = control.Find("Recommends")
187 recommends = split_depends(recommends_str)
189 if control.has_key("Section"):
190 section_str = control.Find("Section")
192 c_match = re_contrib.search(section_str)
193 nf_match = re_nonfree.search(section_str)
196 section = contrib_colour + section_str + end_colour
200 section = nonfree_colour + section_str + end_colour
203 section = main_colour + section_str + end_colour
204 if control.has_key("Achitecture"):
205 arch_str = control.Find("Architecture")
206 arch = arch_colour + arch_str + end_colour
208 if control.has_key("Maintainer"):
209 maintainer = control.Find("Maintainer")
210 localhost = re_localhost.search(maintainer)
213 maintainer = maintainer_colour + maintainer + end_colour
216 return (control, control_keys, section, depends, recommends, arch, maintainer)
219 def read_dsc (dsc_filename) :
226 dsc_file = utils.open_file(dsc_filename)
228 dsc.update(utils.parse_changes(dsc_filename, dsc_whitespace_rules=0))
229 dsc_files.update(utils.build_file_list(dsc, is_a_dsc=1))
232 print "can't parse control info"
238 filecontents = dsc["filecontents"]
240 if dsc.has_key("build-depends"):
242 builddep = split_depends(dsc["build-depends"])
243 builddepstr = create_depends_string(builddep)
244 filecontents = re_builddep.sub("Build-Depends: "+builddepstr, filecontents)
247 if dsc.has_key("build-depends-indep") :
248 builddepindstr = create_depends_string(split_depends(dsc["build-depends-indep"]))
249 filecontents = re_builddepind.sub("Build-Depends-Indep: "+builddepindstr, filecontents)
254 if dsc.has_key("architecture") :
256 if (dsc["architecture"] != "any") :
257 newarch = arch_colour + dsc["architecture"] + end_colour
258 filecontents = re_arch.sub("Architecture: "+newarch, filecontents)
260 return (filecontents)
262 def create_depends_string(depends_tree) :
263 # just look up unstable for now. possibly pull from .changes later
267 for l in depends_tree:
268 if (comma_count >= 2) :
269 result = result + ", "
272 if (or_count >= 2 ) :
273 result = result + " | "
274 # doesn't do version lookup yet.
276 q = projectB.query("SELECT DISTINCT(b.package), b.version, c.name, su.suite_name FROM binaries b, files fi, location l, component c, bin_associations ba, suite su WHERE b.package='%s' AND b.file = fi.id AND fi.location = l.id AND l.component = c.id AND ba.bin=b.id AND ba.suite = su.id AND su.suite_name='%s' ORDER BY b.version desc" % (d['name'], suite))
282 if i[2] == "contrib" :
283 result = result + contrib_colour + d['name']
284 elif i[2] == "non-free" :
285 result = result + nonfree_colour + d['name']
287 result = result + main_colour + d['name']
289 if d['version'] != '' :
290 result = result + " (" + d['version'] + ")"
291 result = result +end_colour
293 result = result + bold_colour + d['name']
294 if d['version'] != '' :
295 result = result + " (" + d['version'] + ")"
296 result = result + end_colour
297 or_count = or_count + 1
298 comma_count = comma_count + 1
302 def output_deb_info(filename):
305 (control, control_keys, section, depends, recommends, arch, maintainer) = read_control(filename)
308 print "no control info"
312 for key in control_keys :
314 output = " " + key + ": "
315 if key == 'Depends' :
316 output = output + create_depends_string(depends)
317 elif key == 'Recommends' :
318 output = output + create_depends_string(recommends)
319 elif key == 'Section' :
320 output = output + section
321 elif key == 'Architecture' :
322 output = output + arch
323 elif key == 'Maintainer' :
324 output = output + maintainer
325 elif key == 'Description' :
326 desc = control.Find(key)
327 desc = re_newlinespace.sub('\n ', desc)
328 output = output + desc
330 output = output + control.Find(key)
334 def do_command (command, filename):
335 o = os.popen("%s %s" % (command, filename));
338 def print_copyright (deb_filename):
339 package = re_package.sub(r'\1', deb_filename);
340 o = os.popen("ar p %s data.tar.gz | tar tzvf - | egrep 'usr(/share)?/doc/[^/]*/copyright' | awk '{ print $6 }' | head -n 1" % (deb_filename));
341 copyright = o.read()[:-1];
344 print "WARNING: No copyright found, please check package manually."
347 doc_directory = re_doc_directory.sub(r'\1', copyright);
348 if package != doc_directory:
349 print "WARNING: wrong doc directory (expected %s, got %s)." % (package, doc_directory);
352 o = os.popen("ar p %s data.tar.gz | tar xzOf - %s" % (deb_filename, copyright));
355 def check_dsc (dsc_filename):
356 print "---- .dsc file for %s ----" % (dsc_filename);
357 (dsc) = read_dsc(dsc_filename)
364 def check_deb (deb_filename):
365 filename = os.path.basename(deb_filename);
367 if filename.endswith(".udeb"):
372 print "---- control file for %s ----" % (filename);
373 #do_command ("dpkg -I", deb_filename);
374 output_deb_info(deb_filename)
377 print "---- skipping lintian check for µdeb ----";
380 print "---- lintian check for %s ----" % (filename);
381 do_command ("lintian", deb_filename);
382 print "---- linda check for %s ----" % (filename);
383 do_command ("linda", deb_filename);
385 print "---- contents of %s ----" % (filename);
386 do_command ("dpkg -c", deb_filename);
389 print "---- skipping copyright for µdeb ----";
391 print "---- copyright of %s ----" % (filename);
392 print_copyright(deb_filename);
394 print "---- file listing of %s ----" % (filename);
395 do_command ("ls -l", deb_filename);
397 def display_changes (changes_filename):
398 print "---- .changes file for %s ----" % (changes_filename);
399 file = utils.open_file (changes_filename);
400 for line in file.readlines():
405 def check_changes (changes_filename):
406 display_changes(changes_filename);
408 changes = utils.parse_changes (changes_filename);
409 files = utils.build_file_list(changes);
410 for file in files.keys():
411 if file.endswith(".deb") or file.endswith(".udeb"):
413 if file.endswith(".dsc"):
418 global Cnf, projectB, db_files, waste, excluded;
420 # Cnf = utils.get_conf()
422 Arguments = [('h',"help","Fernanda::Options::Help")];
424 if not Cnf.has_key("Frenanda::Options::%s" % (i)):
425 Cnf["Fernanda::Options::%s" % (i)] = "";
427 args = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
428 Options = Cnf.SubTree("Fernanda::Options")
433 stdout_fd = sys.stdout;
437 # Pipe output for each argument through less
438 less_fd = os.popen("less -R -", 'w', 0);
439 # -R added to display raw control chars for colour
440 sys.stdout = less_fd;
443 if file.endswith(".changes"):
445 elif file.endswith(".deb") or file.endswith(".udeb"):
447 elif file.endswith(".dsc"):
450 utils.fubar("Unrecognised file type: '%s'." % (file));
452 # Reset stdout here so future less invocations aren't FUBAR
454 sys.stdout = stdout_fd;
456 if errno.errorcode[e.errno] == 'EPIPE':
457 utils.warn("[fernanda] Caught EPIPE; skipping.");
461 except KeyboardInterrupt:
462 utils.warn("[fernanda] Caught C-c; skipping.");
465 #######################################################################################
467 if __name__ == '__main__':