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.7 2002-11-26 02:51:52 rdonald 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
80 ################################################################################
85 Cnf = utils.get_conf()
86 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
87 db_access.init(Cnf, projectB);
91 ################################################################################
93 def usage (exit_code=0):
94 print """Usage: fernanda [PACKAGE]...
97 -h, --help show this help and exit
99 PACKAGE can be a .changes, .dsc, .deb or .udeb filename."""
103 ################################################################################
105 def get_depends_parts(depend) :
106 v_match = re_version.match(depend)
108 d_parts = { 'name' : v_match.group(1), 'version' : v_match.group(2) }
110 d_parts = { 'name' : depend , 'version' : '' }
113 def get_or_list(depend) :
114 or_list = string.split(depend, "|")
117 def get_comma_list(depend) :
118 dep_list = string.split(depend, ",")
122 def split_depends (d_str) :
123 # creates a list of lists of dictionaries of depends (package,version relation)
125 d_str = re_spacestrip.sub('',d_str)
127 # first split depends string up amongs comma delimiter
128 dep_list = get_comma_list(d_str)
130 while d < len(dep_list):
131 # put depends into their own list
132 depends_tree.append([dep_list[d]])
135 while d < len(depends_tree) :
137 # split up Or'd depends into a multi-item list
138 depends_tree[d] = get_or_list(depends_tree[d][0])
139 while k < len(depends_tree[d]) :
140 # split depends into {package, version relation}
141 depends_tree[d][k] = get_depends_parts(depends_tree[d][k])
146 def read_control (filename) :
157 deb_file = utils.open_file(filename);
159 extracts = apt_inst.debExtractControl(deb_file)
160 control = apt_pkg.ParseSection(extracts)
163 print "can't parse control info"
169 control_keys = control.keys()
177 if control.has_key("Depends"):
178 depends_str = control.Find("Depends")
179 # create list of dependancy lists
180 depends = split_depends(depends_str)
183 if control.has_key("Recommends"):
184 recommends_str = control.Find("Recommends")
185 recommends = split_depends(recommends_str)
187 if control.has_key("Section"):
188 section_str = control.Find("Section")
190 c_match = re_contrib.search(section_str)
191 nf_match = re_nonfree.search(section_str)
194 section = contrib_colour + section_str + end_colour
198 section = nonfree_colour + section_str + end_colour
201 section = main_colour + section_str + end_colour
202 if control.has_key("Achitecture"):
203 arch_str = control.Find("Architecture")
204 arch = arch_colour + arch_str + end_colour
206 if control.has_key("Maintainer"):
207 maintainer = control.Find("Maintainer")
208 localhost = re_localhost.search(maintainer)
211 maintainer = maintainer_colour + maintainer + end_colour
214 return (control, control_keys, section, depends, recommends, arch, maintainer)
217 def read_dsc (dsc_filename) :
224 dsc_file = utils.open_file(dsc_filename)
226 dsc.update(utils.parse_changes(dsc_filename, dsc_whitespace_rules=0))
227 dsc_files.update(utils.build_file_list(dsc, is_a_dsc=1))
230 print "can't parse control info"
236 filecontents = dsc["filecontents"]
238 if dsc.has_key("build-depends"):
240 builddep = split_depends(dsc["build-depends"])
241 builddepstr = create_depends_string(builddep)
242 filecontents = re_builddep.sub("Build-Depends: "+builddepstr, filecontents)
245 if dsc.has_key("build-depends-indep") :
246 builddepindstr = create_depends_string(split_depends(dsc["build-depends-indep"]))
247 filecontents = re_builddepind.sub("Build-Depends-Indep: "+builddepindstr, filecontents)
252 if dsc.has_key("architecture") :
254 if (dsc["architecture"] != "any") :
255 newarch = arch_colour + dsc["architecture"] + end_colour
256 filecontents = re_arch.sub("Architecture: "+newarch, filecontents)
258 return (filecontents)
260 def create_depends_string(depends_tree) :
261 # just look up unstable for now. possibly pull from .changes later
265 for l in depends_tree:
266 if (comma_count >= 2) :
267 result = result + ", "
270 if (or_count >= 2 ) :
271 result = result + " | "
272 # doesn't do version lookup yet.
274 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))
280 if i[2] == "contrib" :
281 result = result + contrib_colour + d['name']
282 elif i[2] == "non-free" :
283 result = result + nonfree_colour + d['name']
285 result = result + main_colour + d['name']
287 if d['version'] != '' :
288 result = result + " (" + d['version'] + ")"
289 result = result +end_colour
291 result = result + bold_colour + d['name']
292 if d['version'] != '' :
293 result = result + " (" + d['version'] + ")"
294 result = result + end_colour
295 or_count = or_count + 1
296 comma_count = comma_count + 1
300 def output_deb_info(filename):
303 (control, control_keys, section, depends, recommends, arch, maintainer) = read_control(filename)
306 print "no control info"
310 for key in control_keys :
312 output = " " + key + ": "
313 if key == 'Depends' :
314 output = output + create_depends_string(depends)
315 elif key == 'Recommends' :
316 output = output + create_depends_string(recommends)
317 elif key == 'Section' :
318 output = output + section
319 elif key == 'Architecture' :
320 output = output + arch
321 elif key == 'Maintainer' :
322 output = output + maintainer
323 elif key == 'Description' :
324 desc = control.Find(key)
325 desc = re_newlinespace.sub('\n ', desc)
326 output = output + desc
328 output = output + control.Find(key)
332 def do_command (command, filename):
333 o = os.popen("%s %s" % (command, filename));
336 def print_copyright (deb_filename):
337 package = re_package.sub(r'\1', deb_filename);
338 o = os.popen("ar p %s data.tar.gz | tar tzvf - | egrep 'usr(/share)?/doc/[^/]*/copyright' | awk '{ print $6 }' | head -n 1" % (deb_filename));
339 copyright = o.read()[:-1];
342 print "WARNING: No copyright found, please check package manually."
345 doc_directory = re_doc_directory.sub(r'\1', copyright);
346 if package != doc_directory:
347 print "WARNING: wrong doc directory (expected %s, got %s)." % (package, doc_directory);
350 o = os.popen("ar p %s data.tar.gz | tar xzOf - %s" % (deb_filename, copyright));
353 def check_dsc (dsc_filename):
354 print "---- .dsc file for %s ----" % (dsc_filename);
355 (dsc) = read_dsc(dsc_filename)
362 def check_deb (deb_filename):
363 filename = os.path.basename(deb_filename);
365 if filename.endswith(".udeb"):
370 print "---- control file for %s ----" % (filename);
371 #do_command ("dpkg -I", deb_filename);
372 output_deb_info(deb_filename)
375 print "---- skipping lintian check for µdeb ----";
378 print "---- lintian check for %s ----" % (filename);
379 do_command ("lintian", deb_filename);
380 print "---- linda check for %s ----" % (filename);
381 do_command ("linda", deb_filename);
383 print "---- contents of %s ----" % (filename);
384 do_command ("dpkg -c", deb_filename);
387 print "---- skipping copyright for µdeb ----";
389 print "---- copyright of %s ----" % (filename);
390 print_copyright(deb_filename);
392 print "---- file listing of %s ----" % (filename);
393 do_command ("ls -l", deb_filename);
395 def display_changes (changes_filename):
396 print "---- .changes file for %s ----" % (changes_filename);
397 file = utils.open_file (changes_filename);
398 for line in file.readlines():
403 def check_changes (changes_filename):
404 display_changes(changes_filename);
406 changes = utils.parse_changes (changes_filename);
407 files = utils.build_file_list(changes);
408 for file in files.keys():
409 if file.endswith(".deb") or file.endswith(".udeb"):
411 if file.endswith(".dsc"):
416 global Cnf, projectB, db_files, waste, excluded;
418 # Cnf = utils.get_conf()
420 Arguments = [('h',"help","Fernanda::Options::Help")];
422 if not Cnf.has_key("Frenanda::Options::%s" % (i)):
423 Cnf["Fernanda::Options::%s" % (i)] = "";
425 args = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
426 Options = Cnf.SubTree("Fernanda::Options")
431 stdout_fd = sys.stdout;
435 # Pipe output for each argument through less
436 less_fd = os.popen("less -R -", 'w', 0);
437 # -R added to display raw control chars for colour
438 sys.stdout = less_fd;
441 if file.endswith(".changes"):
443 elif file.endswith(".deb") or file.endswith(".udeb"):
445 elif file.endswith(".dsc"):
448 utils.fubar("Unrecognised file type: '%s'." % (file));
450 # Reset stdout here so future less invocations aren't FUBAR
452 sys.stdout = stdout_fd;
454 if errno.errorcode[e.errno] == 'EPIPE':
455 utils.warn("[fernanda] Caught EPIPE; skipping.");
459 except KeyboardInterrupt:
460 utils.warn("[fernanda] Caught C-c; skipping.");
463 #######################################################################################
465 if __name__ == '__main__':