]> git.decadent.org.uk Git - dak.git/blobdiff - dak/rm.py
Merge branch 'master' into content_generation, make changes based on Joerg's review
[dak.git] / dak / rm.py
old mode 100644 (file)
new mode 100755 (executable)
index 262039b..6844738
--- a/dak/rm.py
+++ b/dak/rm.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# General purpose package removal tool for ftpmaster
+""" General purpose package removal tool for ftpmaster """
 # Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
 
 # This program is free software; you can redistribute it and/or modify
 
 ################################################################################
 
-import commands, os, pg, re, sys
-import apt_pkg, apt_inst
-import daklib.database
-import daklib.utils
-
-################################################################################
-
-re_strip_source_version = re.compile (r'\s+.*$')
-re_build_dep_arch = re.compile(r"\[[^]]+\]")
+import commands
+import os
+import pg
+import re
+import sys
+import apt_pkg
+import apt_inst
+from daklib import database
+from daklib import utils
+from daklib.dak_exceptions import *
+from daklib.regexes import re_strip_source_version, re_build_dep_arch
 
 ################################################################################
 
@@ -87,34 +89,45 @@ ARCH, BUG#, COMPONENT and SUITE can be comma (or space) separated lists, e.g.
 #  the fuck are we gonna do now? What are we gonna do?"
 
 def game_over():
-    answer = daklib.utils.our_raw_input("Continue (y/N)? ").lower()
+    answer = utils.our_raw_input("Continue (y/N)? ").lower()
     if answer != "y":
         print "Aborted."
         sys.exit(1)
 
 ################################################################################
 
-def reverse_depends_check(removals, suites):
+def reverse_depends_check(removals, suites, arches=None):
     print "Checking reverse dependencies..."
     components = Cnf.ValueList("Suite::%s::Components" % suites[0])
     dep_problem = 0
     p2c = {}
-    for architecture in Cnf.ValueList("Suite::%s::Architectures" % suites[0]):
-        if architecture in ["source", "all"]:
-            continue
+    all_broken = {}
+    if arches:
+        all_arches = set(arches)
+    else:
+        all_arches = set(database.get_suite_architectures(suites[0]))
+    all_arches -= set(["source", "all"])
+    for architecture in all_arches:
         deps = {}
+        sources = {}
         virtual_packages = {}
         for component in components:
             filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (Cnf["Dir::Root"], suites[0], component, architecture)
             # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
-            temp_filename = daklib.utils.temp_filename()
+            (fd, temp_filename) = utils.temp_filename()
             (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
             if (result != 0):
-                daklib.utils.fubar("Gunzip invocation failed!\n%s\n" % (output), result)
-            packages = daklib.utils.open_file(temp_filename)
+                utils.fubar("Gunzip invocation failed!\n%s\n" % (output), result)
+            packages = utils.open_file(temp_filename)
             Packages = apt_pkg.ParseTagFile(packages)
             while Packages.Step():
                 package = Packages.Section.Find("Package")
+                source = Packages.Section.Find("Source")
+                if not source:
+                    source = package
+                elif ' ' in source:
+                    source = source.split(' ', 1)[0]
+                sources[package] = source
                 depends = Packages.Section.Find("Depends")
                 if depends:
                     deps[package] = depends
@@ -161,23 +174,37 @@ def reverse_depends_check(removals, suites):
                         unsat += 1
                 if unsat == len(dep):
                     component = p2c[package]
+                    source = sources[package]
                     if component != "main":
-                        what = "%s/%s" % (package, component)
-                    else:
-                        what = "** %s" % (package)
-                    print "%s has an unsatisfied dependency on %s: %s" % (what, architecture, daklib.utils.pp_deps(dep))
+                        source = "%s/%s" % (source, component)
+                    all_broken.setdefault(source, {}).setdefault(package, set()).add(architecture)
                     dep_problem = 1
 
+    if all_broken:
+        print "# Broken Depends:"
+        for source, bindict in sorted(all_broken.items()):
+            lines = []
+            for binary, arches in sorted(bindict.items()):
+                if arches == all_arches:
+                    lines.append(binary)
+                else:
+                    lines.append('%s [%s]' % (binary, ' '.join(sorted(arches))))
+            print '%s: %s' % (source, lines[0])
+            for line in lines[1:]:
+                print ' ' * (len(source) + 2) + line
+        print
+
     # Check source dependencies (Build-Depends and Build-Depends-Indep)
+    all_broken.clear()
     for component in components:
         filename = "%s/dists/%s/%s/source/Sources.gz" % (Cnf["Dir::Root"], suites[0], component)
         # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
-        temp_filename = daklib.utils.temp_filename()
+        (fd, temp_filename) = utils.temp_filename()
         result, output = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
         if result != 0:
             sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
             sys.exit(result)
-        sources = daklib.utils.open_file(temp_filename, "r")
+        sources = utils.open_file(temp_filename, "r")
         Sources = apt_pkg.ParseTagFile(sources)
         while Sources.Step():
             source = Sources.Section.Find("Package")
@@ -200,13 +227,20 @@ def reverse_depends_check(removals, suites):
                 if unsat == len(dep):
                     if component != "main":
                         source = "%s/%s" % (source, component)
-                    else:
-                        source = "** %s" % (source)
-                    print "%s has an unsatisfied build-dependency: %s" % (source, daklib.utils.pp_deps(dep))
+                    all_broken.setdefault(source, set()).add(utils.pp_deps(dep))
                     dep_problem = 1
         sources.close()
         os.unlink(temp_filename)
 
+    if all_broken:
+        print "# Broken Build-Depends:"
+        for source, bdeps in sorted(all_broken.items()):
+            bdeps = sorted(bdeps)
+            print '%s: %s' % (source, bdeps[0])
+            for bdep in bdeps[1:]:
+                print ' ' * (len(source) + 2) + bdep
+        print
+
     if dep_problem:
         print "Dependency problem found."
         if not Options["No-Action"]:
@@ -220,7 +254,7 @@ def reverse_depends_check(removals, suites):
 def main ():
     global Cnf, Options, projectB
 
-    Cnf = daklib.utils.get_conf()
+    Cnf = utils.get_conf()
 
     Arguments = [('h',"help","Rm::Options::Help"),
                  ('a',"architecture","Rm::Options::Architecture", "HasArg"),
@@ -251,19 +285,19 @@ def main ():
         usage()
 
     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
-    daklib.database.init(Cnf, projectB)
+    database.init(Cnf, projectB)
 
     # Sanity check options
     if not arguments:
-        daklib.utils.fubar("need at least one package name as an argument.")
+        utils.fubar("need at least one package name as an argument.")
     if Options["Architecture"] and Options["Source-Only"]:
-        daklib.utils.fubar("can't use -a/--architecutre and -S/--source-only options simultaneously.")
+        utils.fubar("can't use -a/--architecture and -S/--source-only options simultaneously.")
     if Options["Binary-Only"] and Options["Source-Only"]:
-        daklib.utils.fubar("can't use -b/--binary-only and -S/--source-only options simultaneously.")
+        utils.fubar("can't use -b/--binary-only and -S/--source-only options simultaneously.")
     if Options.has_key("Carbon-Copy") and not Options.has_key("Done"):
-        daklib.utils.fubar("can't use -C/--carbon-copy without also using -d/--done option.")
+        utils.fubar("can't use -C/--carbon-copy without also using -d/--done option.")
     if Options["Architecture"] and not Options["Partial"]:
-        daklib.utils.warn("-a/--architecture implies -p/--partial.")
+        utils.warn("-a/--architecture implies -p/--partial.")
         Options["Partial"] = "true"
 
     # Force the admin to tell someone if we're not doing a 'dak
@@ -271,7 +305,7 @@ def main ():
     # as telling someone).
     if not Options["No-Action"] and not Options["Carbon-Copy"] \
            and not Options["Done"] and Options["Reason"].find("[auto-cruft]") == -1:
-        daklib.utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal.")
+        utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal.")
 
     # Process -C/--carbon-copy
     #
@@ -281,7 +315,7 @@ def main ():
     #  3) contains a '@' - assumed to be an email address, used unmofidied
     #
     carbon_copy = []
-    for copy_to in daklib.utils.split_args(Options.get("Carbon-Copy")):
+    for copy_to in utils.split_args(Options.get("Carbon-Copy")):
         if copy_to.isdigit():
             carbon_copy.append(copy_to + "@" + Cnf["Dinstall::BugServer"])
         elif copy_to == 'package':
@@ -292,7 +326,7 @@ def main ():
         elif '@' in copy_to:
             carbon_copy.append(copy_to)
         else:
-            daklib.utils.fubar("Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." % (copy_to))
+            utils.fubar("Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." % (copy_to))
 
     if Options["Binary-Only"]:
         field = "b.package"
@@ -301,15 +335,15 @@ def main ():
     con_packages = "AND %s IN (%s)" % (field, ", ".join([ repr(i) for i in arguments ]))
 
     (con_suites, con_architectures, con_components, check_source) = \
-                 daklib.utils.parse_args(Options)
+                 utils.parse_args(Options)
 
     # Additional suite checks
     suite_ids_list = []
-    suites = daklib.utils.split_args(Options["Suite"])
-    suites_list = daklib.utils.join_with_commas_and(suites)
+    suites = utils.split_args(Options["Suite"])
+    suites_list = utils.join_with_commas_and(suites)
     if not Options["No-Action"]:
         for suite in suites:
-            suite_id = daklib.database.get_suite_id(suite)
+            suite_id = database.get_suite_id(suite)
             if suite_id != -1:
                 suite_ids_list.append(suite_id)
             if suite == "stable":
@@ -325,7 +359,7 @@ def main ():
 
     # Additional architecture checks
     if Options["Architecture"] and check_source:
-        daklib.utils.warn("'source' in -a/--argument makes no sense and is ignored.")
+        utils.warn("'source' in -a/--argument makes no sense and is ignored.")
 
     # Additional component processing
     over_con_components = con_components.replace("c.id", "component")
@@ -363,9 +397,9 @@ def main ():
             for i in source_packages.keys():
                 filename = "/".join(source_packages[i])
                 try:
-                    dsc = daklib.utils.parse_changes(filename)
-                except daklib.utils.cant_open_exc:
-                    daklib.utils.warn("couldn't open '%s'." % (filename))
+                    dsc = utils.parse_changes(filename)
+                except CantOpenError:
+                    utils.warn("couldn't open '%s'." % (filename))
                     continue
                 for package in dsc.get("binary").split(','):
                     package = package.strip()
@@ -378,7 +412,7 @@ def main ():
                 q = projectB.query("SELECT l.path, f.filename, b.package, b.version, a.arch_string, b.id, b.maintainer FROM binaries b, bin_associations ba, architecture a, suite su, files f, location l, component c WHERE ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id AND b.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s AND b.package = '%s'" % (con_suites, con_components, con_architectures, package))
                 for i in q.getresult():
                     filename = "/".join(i[:2])
-                    control = apt_pkg.ParseSection(apt_inst.debExtractControl(daklib.utils.open_file(filename)))
+                    control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(filename)))
                     source = control.Find("Source", control.Find("Package"))
                     source = re_strip_source_version.sub('', source)
                     if source_packages.has_key(source):
@@ -392,12 +426,12 @@ def main ():
     # If we don't have a reason; spawn an editor so the user can add one
     # Write the rejection email out as the <foo>.reason file
     if not Options["Reason"] and not Options["No-Action"]:
-        temp_filename = daklib.utils.temp_filename()
+        (fd, temp_filename) = utils.temp_filename()
         editor = os.environ.get("EDITOR","vi")
         result = os.system("%s %s" % (editor, temp_filename))
         if result != 0:
-            daklib.utils.fubar ("vi invocation failed for `%s'!" % (temp_filename), result)
-        temp_file = daklib.utils.open_file(temp_filename)
+            utils.fubar ("vi invocation failed for `%s'!" % (temp_filename), result)
+        temp_file = utils.open_file(temp_filename)
         for line in temp_file.readlines():
             Options["Reason"] += line
         temp_file.close()
@@ -420,7 +454,7 @@ def main ():
 
     maintainer_list = []
     for maintainer_id in maintainers.keys():
-        maintainer_list.append(daklib.database.get_maintainer(maintainer_id))
+        maintainer_list.append(database.get_maintainer(maintainer_id))
     summary = ""
     removals = d.keys()
     removals.sort()
@@ -428,7 +462,7 @@ def main ():
         versions = d[package].keys()
         versions.sort(apt_pkg.VersionCompare)
         for version in versions:
-            d[package][version].sort(daklib.utils.arch_compare_sw)
+            d[package][version].sort(utils.arch_compare_sw)
             summary += "%10s | %10s | %s\n" % (package, version, ", ".join(d[package][version]))
     print "Will remove the following packages from %s:" % (suites_list)
     print
@@ -445,7 +479,8 @@ def main ():
     print
 
     if Options["Rdep-Check"]:
-        reverse_depends_check(removals, suites)
+        arches = utils.split_args(Options["Architecture"])
+        reverse_depends_check(removals, suites, arches)
 
     # If -n/--no-action, drop out here
     if Options["No-Action"]:
@@ -454,11 +489,11 @@ def main ():
     print "Going to remove the packages now."
     game_over()
 
-    whoami = daklib.utils.whoami()
+    whoami = utils.whoami()
     date = commands.getoutput('date -R')
 
     # Log first; if it all falls apart I want a record that we at least tried.
-    logfile = daklib.utils.open_file(Cnf["Rm::LogFile"], 'a')
+    logfile = utils.open_file(Cnf["Rm::LogFile"], 'a')
     logfile.write("=========================================================================\n")
     logfile.write("[Date: %s] [ftpmaster: %s]\n" % (date, whoami))
     logfile.write("Removed the following packages from %s:\n\n%s" % (suites_list, summary))
@@ -468,8 +503,8 @@ def main ():
     logfile.write("----------------------------------------------\n")
     logfile.flush()
 
-    dsc_type_id = daklib.database.get_override_type_id('dsc')
-    deb_type_id = daklib.database.get_override_type_id('deb')
+    dsc_type_id = database.get_override_type_id('dsc')
+    deb_type_id = database.get_override_type_id('deb')
 
     # Do the actual deletion
     print "Deleting...",
@@ -510,7 +545,7 @@ def main ():
             Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
         else:
             Subst["__BCC__"] = "X-Filler: 42"
-        Subst["__CC__"] = "X-DAK: dak rm\nX-Katie: melanie $Revision: 1.44 $"
+        Subst["__CC__"] = "X-DAK: dak rm\nX-Katie: melanie"
         if carbon_copy:
             Subst["__CC__"] += "\nCc: " + ", ".join(carbon_copy)
         Subst["__SUITE_LIST__"] = suites_list
@@ -518,14 +553,14 @@ def main ():
         Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
         Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
         Subst["__WHOAMI__"] = whoami
-        whereami = daklib.utils.where_am_i()
+        whereami = utils.where_am_i()
         Archive = Cnf.SubTree("Archive::%s" % (whereami))
         Subst["__MASTER_ARCHIVE__"] = Archive["OriginServer"]
         Subst["__PRIMARY_MIRROR__"] = Archive["PrimaryMirror"]
-        for bug in daklib.utils.split_args(Options["Done"]):
+        for bug in utils.split_args(Options["Done"]):
             Subst["__BUG_NUMBER__"] = bug
-            mail_message = daklib.utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/rm.bug-close")
-            daklib.utils.send_mail(mail_message)
+            mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/rm.bug-close")
+            utils.send_mail(mail_message)
 
     logfile.write("=========================================================================\n")
     logfile.close()