]> git.decadent.org.uk Git - dak.git/blobdiff - dak/rm.py
Revert "Merge commit 'stew/content_generation' into merge"
[dak.git] / dak / rm.py
index 10d1dd016dfea90322ede4d1c0ae628ef813adee..903e138e8929e1f6aec25e2518f9f5adde58eb61 100755 (executable)
--- a/dak/rm.py
+++ b/dak/rm.py
@@ -1,8 +1,7 @@
 #!/usr/bin/env python
 
-# General purpose package removal tool for ftpmaster
-# Copyright (C) 2000, 2001, 2002, 2003, 2004  James Troup <james@nocrew.org>
-# $Id: melanie,v 1.44 2005-11-15 09:50:32 ajt Exp $
+""" 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
 # it under the terms of the GNU General Public License as published by
 ################################################################################
 
 import commands, os, pg, re, sys
-import utils, db_access
 import apt_pkg, apt_inst
-
-################################################################################
-
-re_strip_source_version = re.compile (r'\s+.*$')
-re_build_dep_arch = re.compile(r"\[[^]]+\]")
+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
 
 ################################################################################
 
@@ -58,7 +55,7 @@ projectB = None
 ################################################################################
 
 def usage (exit_code=0):
-    print """Usage: melanie [OPTIONS] PACKAGE[...]
+    print """Usage: dak rm [OPTIONS] PACKAGE[...]
 Remove PACKAGE(s) from suite(s).
 
   -a, --architecture=ARCH    only act on this architecture
@@ -94,20 +91,25 @@ def game_over():
 
 ################################################################################
 
-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 = utils.temp_filename()
+            (fd, temp_filename) = utils.temp_filename()
             (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
             if (result != 0):
                 utils.fubar("Gunzip invocation failed!\n%s\n" % (output), result)
@@ -115,6 +117,12 @@ def reverse_depends_check(removals, suites):
             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
@@ -158,21 +166,35 @@ def reverse_depends_check(removals, suites):
                 unsat = 0
                 for dep_package, _, _ in dep:
                     if dep_package in removals:
-                            unsat += 1
+                        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, 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 = 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))
@@ -196,17 +218,24 @@ def reverse_depends_check(removals, suites):
                 unsat = 0
                 for dep_package, _, _ in dep:
                     if dep_package in removals:
-                            unsat += 1
+                        unsat += 1
                 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, 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"]:
@@ -214,7 +243,7 @@ def reverse_depends_check(removals, suites):
     else:
         print "No dependency problem found."
     print
-    
+
 ################################################################################
 
 def main ():
@@ -222,42 +251,42 @@ def main ():
 
     Cnf = utils.get_conf()
 
-    Arguments = [('h',"help","Melanie::Options::Help"),
-                 ('a',"architecture","Melanie::Options::Architecture", "HasArg"),
-                 ('b',"binary", "Melanie::Options::Binary-Only"),
-                 ('c',"component", "Melanie::Options::Component", "HasArg"),
-                 ('C',"carbon-copy", "Melanie::Options::Carbon-Copy", "HasArg"), # Bugs to Cc
-                 ('d',"done","Melanie::Options::Done", "HasArg"), # Bugs fixed
-                 ('R',"rdep-check", "Melanie::Options::Rdep-Check"),
-                 ('m',"reason", "Melanie::Options::Reason", "HasArg"), # Hysterical raisins; -m is old-dinstall option for rejection reason
-                 ('n',"no-action","Melanie::Options::No-Action"),
-                 ('p',"partial", "Melanie::Options::Partial"),
-                 ('s',"suite","Melanie::Options::Suite", "HasArg"),
-                 ('S',"source-only", "Melanie::Options::Source-Only"),
+    Arguments = [('h',"help","Rm::Options::Help"),
+                 ('a',"architecture","Rm::Options::Architecture", "HasArg"),
+                 ('b',"binary", "Rm::Options::Binary-Only"),
+                 ('c',"component", "Rm::Options::Component", "HasArg"),
+                 ('C',"carbon-copy", "Rm::Options::Carbon-Copy", "HasArg"), # Bugs to Cc
+                 ('d',"done","Rm::Options::Done", "HasArg"), # Bugs fixed
+                 ('R',"rdep-check", "Rm::Options::Rdep-Check"),
+                 ('m',"reason", "Rm::Options::Reason", "HasArg"), # Hysterical raisins; -m is old-dinstall option for rejection reason
+                 ('n',"no-action","Rm::Options::No-Action"),
+                 ('p',"partial", "Rm::Options::Partial"),
+                 ('s',"suite","Rm::Options::Suite", "HasArg"),
+                 ('S',"source-only", "Rm::Options::Source-Only"),
                  ]
 
     for i in [ "architecture", "binary-only", "carbon-copy", "component",
                "done", "help", "no-action", "partial", "rdep-check", "reason",
                "source-only" ]:
-       if not Cnf.has_key("Melanie::Options::%s" % (i)):
-           Cnf["Melanie::Options::%s" % (i)] = ""
-    if not Cnf.has_key("Melanie::Options::Suite"):
-       Cnf["Melanie::Options::Suite"] = "unstable"
+        if not Cnf.has_key("Rm::Options::%s" % (i)):
+            Cnf["Rm::Options::%s" % (i)] = ""
+    if not Cnf.has_key("Rm::Options::Suite"):
+        Cnf["Rm::Options::Suite"] = "unstable"
 
     arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
-    Options = Cnf.SubTree("Melanie::Options")
+    Options = Cnf.SubTree("Rm::Options")
 
     if Options["Help"]:
-       usage()
+        usage()
 
     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
-    db_access.init(Cnf, projectB)
+    database.init(Cnf, projectB)
 
     # Sanity check options
     if not arguments:
         utils.fubar("need at least one package name as an argument.")
     if Options["Architecture"] and Options["Source-Only"]:
-        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"]:
         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"):
@@ -266,11 +295,12 @@ def main ():
         utils.warn("-a/--architecture implies -p/--partial.")
         Options["Partial"] = "true"
 
-    # Force the admin to tell someone if we're not doing a rene-led removal
-    # (or closing a bug, which counts as telling someone).
+    # Force the admin to tell someone if we're not doing a 'dak
+    # cruft-report' inspired removal (or closing a bug, which counts
+    # as telling someone).
     if not Options["No-Action"] and not Options["Carbon-Copy"] \
-           and not Options["Done"] and Options["Reason"].find("[rene]") == -1:
-        utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a rene-led removal.")
+           and not Options["Done"] and Options["Reason"].find("[auto-cruft]") == -1:
+        utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal.")
 
     # Process -C/--carbon-copy
     #
@@ -281,7 +311,7 @@ def main ():
     #
     carbon_copy = []
     for copy_to in utils.split_args(Options.get("Carbon-Copy")):
-        if utils.str_isnum(copy_to):
+        if copy_to.isdigit():
             carbon_copy.append(copy_to + "@" + Cnf["Dinstall::BugServer"])
         elif copy_to == 'package':
             for package in arguments:
@@ -297,7 +327,7 @@ def main ():
         field = "b.package"
     else:
         field = "s.source"
-    con_packages = "AND %s IN (%s)" % (field, ", ".join(map(repr, arguments)))
+    con_packages = "AND %s IN (%s)" % (field, ", ".join([ repr(i) for i in arguments ]))
 
     (con_suites, con_architectures, con_components, check_source) = \
                  utils.parse_args(Options)
@@ -308,7 +338,7 @@ def main ():
     suites_list = utils.join_with_commas_and(suites)
     if not Options["No-Action"]:
         for suite in suites:
-            suite_id = db_access.get_suite_id(suite)
+            suite_id = database.get_suite_id(suite)
             if suite_id != -1:
                 suite_ids_list.append(suite_id)
             if suite == "stable":
@@ -363,7 +393,7 @@ def main ():
                 filename = "/".join(source_packages[i])
                 try:
                     dsc = utils.parse_changes(filename)
-                except utils.cant_open_exc:
+                except CantOpenError:
                     utils.warn("couldn't open '%s'." % (filename))
                     continue
                 for package in dsc.get("binary").split(','):
@@ -391,7 +421,7 @@ 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 = 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:
@@ -419,7 +449,7 @@ def main ():
 
     maintainer_list = []
     for maintainer_id in maintainers.keys():
-        maintainer_list.append(db_access.get_maintainer(maintainer_id))
+        maintainer_list.append(database.get_maintainer(maintainer_id))
     summary = ""
     removals = d.keys()
     removals.sort()
@@ -444,7 +474,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"]:
@@ -457,7 +488,7 @@ def main ():
     date = commands.getoutput('date -R')
 
     # Log first; if it all falls apart I want a record that we at least tried.
-    logfile = utils.open_file(Cnf["Melanie::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))
@@ -467,8 +498,8 @@ def main ():
     logfile.write("----------------------------------------------\n")
     logfile.flush()
 
-    dsc_type_id = db_access.get_override_type_id('dsc')
-    deb_type_id = db_access.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...",
@@ -498,18 +529,18 @@ def main ():
     # Send the bug closing messages
     if Options["Done"]:
         Subst = {}
-        Subst["__MELANIE_ADDRESS__"] = Cnf["Melanie::MyEmailAddress"]
+        Subst["__RM_ADDRESS__"] = Cnf["Rm::MyEmailAddress"]
         Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
         bcc = []
         if Cnf.Find("Dinstall::Bcc") != "":
             bcc.append(Cnf["Dinstall::Bcc"])
-        if Cnf.Find("Melanie::Bcc") != "":
-            bcc.append(Cnf["Melanie::Bcc"])
+        if Cnf.Find("Rm::Bcc") != "":
+            bcc.append(Cnf["Rm::Bcc"])
         if bcc:
             Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
         else:
             Subst["__BCC__"] = "X-Filler: 42"
-        Subst["__CC__"] = "X-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
@@ -523,7 +554,7 @@ def main ():
         Subst["__PRIMARY_MIRROR__"] = Archive["PrimaryMirror"]
         for bug in utils.split_args(Options["Done"]):
             Subst["__BUG_NUMBER__"] = bug
-            mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/melanie.bug-close")
+            mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/rm.bug-close")
             utils.send_mail(mail_message)
 
     logfile.write("=========================================================================\n")
@@ -533,4 +564,3 @@ def main ():
 
 if __name__ == '__main__':
     main()
-