]> 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
index aac75a36f526916328f889fc9e656bcfa5ba2309..6844738fda4c2a5509ecb3a2fe7acf9832e2638c 100755 (executable)
--- 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
@@ -158,26 +171,40 @@ 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, 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")
@@ -196,17 +223,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, 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"]:
@@ -214,13 +248,13 @@ def reverse_depends_check(removals, suites):
     else:
         print "No dependency problem found."
     print
-    
+
 ################################################################################
 
 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"),
@@ -239,31 +273,31 @@ def main ():
     for i in [ "architecture", "binary-only", "carbon-copy", "component",
                "done", "help", "no-action", "partial", "rdep-check", "reason",
                "source-only" ]:
-       if not Cnf.has_key("Rm::Options::%s" % (i)):
-           Cnf["Rm::Options::%s" % (i)] = ""
+        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"
+        Cnf["Rm::Options::Suite"] = "unstable"
 
     arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
     Options = Cnf.SubTree("Rm::Options")
 
     if Options["Help"]:
-       usage()
+        usage()
 
     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
     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,8 +315,8 @@ 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")):
-        if daklib.utils.str_isnum(copy_to):
+    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':
             for package in arguments:
@@ -292,21 +326,21 @@ 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"
     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) = \
-                 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 = database.get_suite_id(suite)
@@ -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()
@@ -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))
@@ -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: this header is obsolete"
+        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()
@@ -534,4 +569,3 @@ def main ():
 
 if __name__ == '__main__':
     main()
-