# Checks Debian packages from Incoming
# Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
-# $Id: jennifer,v 1.44 2004-02-27 20:07:40 troup Exp $
+# $Id: jennifer,v 1.47 2004-04-03 02:49:46 troup Exp $
# 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
################################################################################
-import errno, fcntl, gzip, os, re, shutil, stat, sys, time, traceback;
+import commands, errno, fcntl, os, re, shutil, stat, sys, time, tempfile, traceback;
import apt_inst, apt_pkg;
import db_access, katie, logging, utils;
re_is_changes = re.compile(r"(.+?)_(.+?)_(.+?)\.changes$");
re_valid_version = re.compile(r"^([0-9]+:)?[0-9A-Za-z\.\-\+:]+$");
re_valid_pkg_name = re.compile(r"^[\dA-Za-z][\dA-Za-z\+\-\.]+$");
+re_changelog_versions = re.compile(r"^\w[-+0-9a-z.]+ \([^\(\) \t]+\)");
+re_strip_revision = re.compile(r"-([^-]+)$");
################################################################################
# Globals
-jennifer_version = "$Revision: 1.44 $";
+jennifer_version = "$Revision: 1.47 $";
Cnf = None;
Options = None;
def check_changes():
filename = pkg.changes_file;
- # Default in case we bail out
- changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
- changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
+ # Defaults in case we bail out
+ changes["maintainer2047"] = Cnf["Dinstall::MyEmailAddress"];
+ changes["changedby2047"] = Cnf["Dinstall::MyEmailAddress"];
changes["architecture"] = {};
# Parse the .changes field into a dictionary
return 0;
# Check for mandatory fields
- for i in ("source", "binary", "architecture", "version", "distribution", "maintainer", "files"):
+ for i in ("source", "binary", "architecture", "version", "distribution",
+ "maintainer", "files", "changes"):
if not changes.has_key(i):
reject("%s: Missing mandatory field `%s'." % (filename, i));
return 0 # Avoid <undef> errors during later tests
for j in o.split():
changes[i][j] = 1
- # Fix the Maintainer: field to be RFC822 compatible
- (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
-
- # Fix the Changed-By: field to be RFC822 compatible; if it exists.
- (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
+ # Fix the Maintainer: field to be RFC822/2047 compatible
+ try:
+ (changes["maintainer822"], changes["maintainer2047"],
+ changes["maintainername"], changes["maintaineremail"]) = \
+ utils.fix_maintainer (changes["maintainer"]);
+ except utils.ParseMaintError, msg:
+ reject("%s: Maintainer field ('%s') failed to parse: %s" \
+ % (filename, changes["maintainer"], msg));
+
+ # ...likewise for the Changed-By: field if it exists.
+ try:
+ (changes["changedby822"], changes["changedby2047"],
+ changes["changedbyname"], changes["changedbyemail"]) = \
+ utils.fix_maintainer (changes.get("changed-by", ""));
+ except utils.ParseMaintError, msg:
+ reject("%s: Changed-By field ('%s') failed to parse: %s" \
+ % (filename, changes["changed-by"], msg));
# Ensure all the values in Closes: are numbers
if changes.has_key("closes"):
files[file]["byhand"] = 1;
files[file]["type"] = "byhand";
# Checks for a binary package...
- elif utils.re_isadeb.match(file) != None:
+ elif utils.re_isadeb.match(file):
has_binaries = 1;
files[file]["type"] = "deb";
reject("%s: Depends field is empty." % (file));
# Check the section & priority match those given in the .changes (non-fatal)
- if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
+ if control.Find("Section") and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
reject("%s control file lists section as `%s', but changes file has `%s'." % (file, control.Find("Section", ""), files[file]["section"]), "Warning: ");
- if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
+ if control.Find("Priority") and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
reject("%s control file lists priority as `%s', but changes file has `%s'." % (file, control.Find("Priority", ""), files[file]["priority"]),"Warning: ");
files[file]["package"] = package;
# Checks for a source package...
else:
m = utils.re_issource.match(file);
- if m != None:
+ if m:
has_source = 1;
files[file]["package"] = m.group(1);
files[file]["version"] = m.group(2);
###############################################################################
-def check_dsc ():
+def check_dsc():
global reprocess;
+ # Ensure there is source to check
+ if not changes["architecture"].has_key("source"):
+ return;
+
# Find the .dsc
dsc_filename = None;
for file in files.keys():
else:
dsc_filename = file;
- # If there isn't one, we have nothing to do...
+ # If there isn't one, we have nothing to do. (We have reject()ed the upload already)
if not dsc_filename:
return;
for i in ("format", "source", "version", "binary", "maintainer", "architecture", "files"):
if not dsc.has_key(i):
reject("%s: missing mandatory field `%s'." % (dsc_filename, i));
+ return;
# Validate the source and version fields
- if dsc.has_key("source") and not re_valid_pkg_name.match(dsc["source"]):
+ if not re_valid_pkg_name.match(dsc["source"]):
reject("%s: invalid source name '%s'." % (dsc_filename, dsc["source"]));
- if dsc.has_key("version") and not re_valid_version.match(dsc["version"]):
+ if not re_valid_version.match(dsc["version"]):
reject("%s: invalid version number '%s'." % (dsc_filename, dsc["version"]));
# Bumping the version number of the .dsc breaks extraction by stable's
if dsc["format"] != "1.0":
reject("%s: incompatible 'Format' version produced by a broken version of dpkg-dev 1.9.1{3,4}." % (dsc_filename));
+ # Validate the Maintainer field
+ try:
+ utils.fix_maintainer (dsc["maintainer"]);
+ except utils.ParseMaintError, msg:
+ reject("%s: Maintainer field ('%s') failed to parse: %s" \
+ % (dsc_filename, changes["changed-by"], msg));
+
# Validate the build-depends field(s)
for field_name in [ "build-depends", "build-depends-indep" ]:
field = dsc.get(field_name);
pass;
# Ensure the version number in the .dsc matches the version number in the .changes
- epochless_dsc_version = utils.re_no_epoch.sub('', dsc.get("version"));
+ epochless_dsc_version = utils.re_no_epoch.sub('', dsc["version"]);
changes_version = files[dsc_filename]["version"];
if epochless_dsc_version != files[dsc_filename]["version"]:
reject("version ('%s') in .dsc does not match version ('%s') in .changes." % (epochless_dsc_version, changes_version));
################################################################################
-# dpkg-source broke .diff.gz generation in dpkg 1.8.x; detect the
-# resulting bad source packages and reject them.
+def get_changelog_versions(source_dir):
+ """Extracts a the source package and (optionally) grabs the
+ version history out of debian/changelog for the BTS."""
-def check_diff ():
- for filename in files.keys():
- if files[filename]["type"] == "diff.gz":
- file = gzip.GzipFile(filename, 'r');
- for line in file.readlines():
- if re_bad_diff.search(line):
- reject("%s: invalid .diff.gz produced by a broken version of dpkg-dev 1.8.x." % (filename));
- break;
+ # Find the .dsc (again)
+ dsc_filename = None;
+ for file in files.keys():
+ if files[file]["type"] == "dsc":
+ dsc_filename = file;
+
+ # If there isn't one, we have nothing to do. (We have reject()ed the upload already)
+ if not dsc_filename:
+ return;
+
+ # Create a symlink mirror of the source files in our temporary directory
+ for f in files.keys():
+ m = utils.re_issource.match(f);
+ if m:
+ src = os.path.join(source_dir, f);
+ # If a file is missing for whatever reason, give up.
+ if not os.path.exists(src):
+ return;
+ type = m.group(3);
+ if type == "orig.tar.gz" and pkg.orig_tar_gz:
+ continue;
+ dest = os.path.join(os.getcwd(), f);
+ os.symlink(src, dest);
+
+ # If the orig.tar.gz is not a part of the upload, create a symlink to the
+ # existing copy.
+ if pkg.orig_tar_gz:
+ dest = os.path.join(os.getcwd(), os.path.basename(pkg.orig_tar_gz));
+ os.symlink(pkg.orig_tar_gz, dest);
+
+ # Extract the source
+ cmd = "dpkg-source -sn -x %s" % (dsc_filename);
+ (result, output) = commands.getstatusoutput(cmd);
+ if (result != 0):
+ reject("'dpkg-source -x' failed for %s [return code: %s]." % (dsc_filename, result));
+ reject(utils.prefix_multi_line_string(output, " [dpkg-source output:] "), "");
+ return;
+
+ if not Cnf.Find("Dir::Queue::BTSVersionTrack"):
+ return;
+
+ # Get the upstream version
+ upstr_version = utils.re_no_epoch.sub('', dsc["version"]);
+ if re_strip_revision.search(upstr_version):
+ upstr_version = re_strip_revision.sub('', upstr_version);
+
+ # Ensure the changelog file exists
+ changelog_filename = "%s-%s/debian/changelog" % (dsc["source"], upstr_version);
+ if not os.path.exists(changelog_filename):
+ reject("%s: debian/changelog not found in extracted source." % (dsc_filename));
+ return;
+
+ # Parse the changelog
+ dsc["bts changelog"] = "";
+ changelog_file = utils.open_file(changelog_filename);
+ for line in changelog_file.readlines():
+ m = re_changelog_versions.match(line);
+ if m:
+ dsc["bts changelog"] += line;
+ changelog_file.close();
+
+ # Check we found at least one revision in the changelog
+ if not dsc["bts changelog"]:
+ reject("%s: changelog format not recognised (empty version tree)." % (dsc_filename));
+
+########################################
+
+def check_source():
+ # Bail out if:
+ # a) there's no source
+ # or b) reprocess is 2 - we will do this check next time when orig.tar.gz is in 'files'
+ # or c) the orig.tar.gz is MIA
+ if not changes["architecture"].has_key("source") or reprocess == 2 \
+ or pkg.orig_tar_gz == -1:
+ return;
+
+ # Create a temporary directory to extract the source into
+ if Options["No-Action"]:
+ tmpdir = tempfile.mktemp();
+ else:
+ # We're in queue/holding and can create a random directory.
+ tmpdir = "%s" % (os.getpid());
+ os.mkdir(tmpdir);
+
+ # Move into the temporary directory
+ cwd = os.getcwd();
+ os.chdir(tmpdir);
+
+ # Get the changelog version history
+ get_changelog_versions(cwd);
+
+ # Move back and cleanup the temporary tree
+ os.chdir(cwd);
+ shutil.rmtree(tmpdir);
################################################################################
check_distributions();
check_files();
check_dsc();
- check_diff();
+ check_source();
check_md5sums();
check_urgency();
check_timestamps();
if accept_count:
sets = "set"
if accept_count > 1:
- sets = "sets"
+ sets = "sets";
print "Accepted %d package %s, %s." % (accept_count, sets, utils.size_type(int(accept_bytes)));
Logger.log(["total",accept_count,accept_bytes]);