]> git.decadent.org.uk Git - dak.git/blob - dak/override.py
override.py: check override compliance
[dak.git] / dak / override.py
1 #!/usr/bin/env python
2
3 """ Microscopic modification and query tool for overrides in projectb """
4 # Copyright (C) 2004, 2006  Daniel Silverstone <dsilvers@digital-scurf.org>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21 ################################################################################
22 ## So line up your soldiers and she'll shoot them all down
23 ## Coz Alisha Rules The World
24 ## You think you found a dream, then it shatters and it seems,
25 ## That Alisha Rules The World
26 ################################################################################
27
28 import os
29 import sys
30 import apt_pkg
31 import commands
32
33 from daklib.config import Config
34 from daklib.dbconn import *
35 from daklib import daklog
36 from daklib import utils
37
38 ################################################################################
39
40 # Shamelessly stolen from 'dak rm'. Should probably end up in utils.py
41 def game_over():
42     answer = utils.our_raw_input("Continue (y/N)? ").lower()
43     if answer != "y":
44         print "Aborted."
45         sys.exit(1)
46
47
48 def usage (exit_code=0):
49     print """Usage: dak override [OPTIONS] package [section] [priority]
50 Make microchanges or microqueries of the binary overrides
51
52   -h, --help                 show this help and exit
53   -c, --check                chech override compliance
54   -d, --done=BUG#            send priority/section change as closure to bug#
55   -n, --no-action            don't do anything
56   -s, --suite                specify the suite to use
57 """
58     sys.exit(exit_code)
59
60 def check_override_compliance(package, priority, suite, cnf, session):
61     print "Checking compliance with related overrides..."
62
63     depends = set()
64     rdepends = set()
65     components = cnf.ValueList("Suite::%s::Components" % suite)
66     arches = set([x.arch_string for x in get_suite_architectures(suite)])
67     arches -= set(["source", "all"])
68     for arch in arches:
69         for component in components:
70             filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (cnf["Dir::Root"], suite, component, arch)
71             (fd, temp_filename) = utils.temp_filename()
72             (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
73             if (result != 0):
74                 utils.fubar("Gunzip invocation failed!\n%s\n" % (output), result)
75             filename = "%s/dists/%s/%s/debian-installer/binary-%s/Packages.gz" % (cnf["Dir::Root"], suite, component, arch)
76             if os.path.exists(filename):
77                 (result, output) = commands.getstatusoutput("gunzip -c %s >> %s" % (filename, temp_filename))
78                 if (result != 0):
79                     utils.fubar("Gunzip invocation failed!\n%s\n" % (output), result)
80             packages = utils.open_file(temp_filename)
81             Packages = apt_pkg.ParseTagFile(packages)
82             while Packages.Step():
83                 package_name = Packages.Section.Find("Package")
84                 dep_list = Packages.Section.Find("Depends")
85                 if dep_list:
86                     if package_name == package:
87                         for d in apt_pkg.ParseDepends(dep_list):
88                             for i in d:
89                                 depends.add(i[0])
90                     else:
91                         for d in apt_pkg.ParseDepends(dep_list):
92                             for i in d:
93                                 if i[0] == package:
94                                     rdepends.add(package_name)
95             os.unlink(temp_filename)
96
97     query = """SELECT o.package, p.level, p.priority
98                FROM override o
99                JOIN suite s ON s.id = o.suite
100                JOIN priority p ON p.id = o.priority
101                WHERE s.suite_name = '%s'
102                AND o.package in ('%s')""" \
103                % (suite, "', '".join(depends.union(rdepends)))
104     packages = session.execute(query)
105
106     excuses = []
107     for p in packages:
108         if p[0] == package or not p[1]:
109             continue
110         if p[0] in depends:
111             if priority.level < p[1]:
112                 excuses.append("%s would have priority %s, its dependency %s has priority %s" \
113                       % (package, priority.priority, p[0], p[2]))
114         if p[0] in rdepends:
115             if priority.level > p[1]:
116                 excuses.append("%s would have priority %s, its reverse dependency %s has priority %s" \
117                       % (package, priority.priority, p[0], p[2]))
118
119     if excuses:
120         for ex in excuses:
121             print ex
122     else:
123         print "Proposed override change complies with Debian Policy"
124
125 def main ():
126     cnf = Config()
127
128     Arguments = [('h',"help","Override::Options::Help"),
129                  ('c',"check","Override::Options::Check"),
130                  ('d',"done","Override::Options::Done", "HasArg"),
131                  ('n',"no-action","Override::Options::No-Action"),
132                  ('s',"suite","Override::Options::Suite", "HasArg"),
133                  ]
134     for i in ["help", "check", "no-action"]:
135         if not cnf.has_key("Override::Options::%s" % (i)):
136             cnf["Override::Options::%s" % (i)] = ""
137     if not cnf.has_key("Override::Options::Suite"):
138         cnf["Override::Options::Suite"] = "unstable"
139
140     arguments = apt_pkg.ParseCommandLine(cnf.Cnf, Arguments, sys.argv)
141     Options = cnf.SubTree("Override::Options")
142
143     if Options["Help"]:
144         usage()
145
146     session = DBConn().session()
147
148     if not arguments:
149         utils.fubar("package name is a required argument.")
150
151     package = arguments.pop(0)
152     suite = Options["Suite"]
153     if arguments and len(arguments) > 2:
154         utils.fubar("Too many arguments")
155
156     if arguments and len(arguments) == 1:
157         # Determine if the argument is a priority or a section...
158         arg = arguments.pop()
159         q = session.execute("""
160         SELECT ( SELECT COUNT(*) FROM section WHERE section = :arg ) AS secs,
161                ( SELECT COUNT(*) FROM priority WHERE priority = :arg ) AS prios
162                """, {'arg': arg})
163         r = q.fetchall()
164         if r[0][0] == 1:
165             arguments = (arg, ".")
166         elif r[0][1] == 1:
167             arguments = (".", arg)
168         else:
169             utils.fubar("%s is not a valid section or priority" % (arg))
170
171     # Retrieve current section/priority...
172     oldsection, oldsourcesection, oldpriority = None, None, None
173     for packagetype in ['source', 'binary']:
174         eqdsc = '!='
175         if packagetype == 'source':
176             eqdsc = '='
177         q = session.execute("""
178     SELECT priority.priority AS prio, section.section AS sect, override_type.type AS type
179       FROM override, priority, section, suite, override_type
180      WHERE override.priority = priority.id
181        AND override.type = override_type.id
182        AND override_type.type %s 'dsc'
183        AND override.section = section.id
184        AND override.package = :package
185        AND override.suite = suite.id
186        AND suite.suite_name = :suite
187         """ % (eqdsc), {'package': package, 'suite': suite})
188
189         if q.rowcount == 0:
190             continue
191         if q.rowcount > 1:
192             utils.fubar("%s is ambiguous. Matches %d packages" % (package,q.rowcount))
193
194         r = q.fetchone()
195         if packagetype == 'binary':
196             oldsection = r[1]
197             oldpriority = r[0]
198         else:
199             oldsourcesection = r[1]
200             oldpriority = 'source'
201
202     if not oldpriority and not oldsourcesection:
203         utils.fubar("Unable to find package %s" % (package))
204
205     if oldsection and oldsourcesection and oldsection != oldsourcesection:
206         # When setting overrides, both source & binary will become the same section
207         utils.warn("Source is in section '%s' instead of '%s'" % (oldsourcesection, oldsection))
208
209     if not oldsection:
210         oldsection = oldsourcesection
211
212     if not arguments:
213         print "%s is in section '%s' at priority '%s'" % (
214             package, oldsection, oldpriority)
215         sys.exit(0)
216
217     # At this point, we have a new section and priority... check they're valid...
218     newsection, newpriority = arguments
219
220     if newsection == ".":
221         newsection = oldsection
222     if newpriority == ".":
223         newpriority = oldpriority
224
225     s = get_section(newsection, session)
226     if s is None:
227         utils.fubar("Supplied section %s is invalid" % (newsection))
228     newsecid = s.section_id
229
230     p = get_priority(newpriority, session)
231     if p is None:
232         utils.fubar("Supplied priority %s is invalid" % (newpriority))
233     newprioid = p.priority_id
234
235     if newpriority == oldpriority and newsection == oldsection:
236         print "I: Doing nothing"
237         sys.exit(0)
238
239     if oldpriority == 'source' and newpriority != 'source':
240         utils.fubar("Trying to change priority of a source-only package")
241
242     if Options["Check"] and newpriority != oldpriority:
243         check_override_compliance(package, p, suite, cnf, session)
244
245     # If we're in no-action mode
246     if Options["No-Action"]:
247         if newpriority != oldpriority:
248             print "I: Would change priority from %s to %s" % (oldpriority,newpriority)
249         if newsection != oldsection:
250             print "I: Would change section from %s to %s" % (oldsection,newsection)
251         if Options.has_key("Done"):
252             print "I: Would also close bug(s): %s" % (Options["Done"])
253
254         sys.exit(0)
255
256     if newpriority != oldpriority:
257         print "I: Will change priority from %s to %s" % (oldpriority,newpriority)
258
259     if newsection != oldsection:
260         print "I: Will change section from %s to %s" % (oldsection,newsection)
261
262     if not Options.has_key("Done"):
263         pass
264         #utils.warn("No bugs to close have been specified. Noone will know you have done this.")
265     else:
266         print "I: Will close bug(s): %s" % (Options["Done"])
267
268     game_over()
269
270     Logger = daklog.Logger(cnf.Cnf, "override")
271
272     dsc_otype_id = get_override_type('dsc').overridetype_id
273
274     # We're already in a transaction
275     # We're in "do it" mode, we have something to do... do it
276     if newpriority != oldpriority:
277         session.execute("""
278         UPDATE override
279            SET priority = :newprioid
280          WHERE package = :package
281            AND override.type != :otypedsc
282            AND suite = (SELECT id FROM suite WHERE suite_name = :suite)""",
283            {'newprioid': newprioid, 'package': package,
284             'otypedsc':  dsc_otype_id, 'suite': suite})
285
286         Logger.log(["changed priority", package, oldpriority, newpriority])
287
288     if newsection != oldsection:
289         q = session.execute("""
290         UPDATE override
291            SET section = :newsecid
292          WHERE package = :package
293            AND suite = (SELECT id FROM suite WHERE suite_name = :suite)""",
294            {'newsecid': newsecid, 'package': package,
295             'suite': suite})
296
297         Logger.log(["changed section", package, oldsection, newsection])
298
299     session.commit()
300
301     if Options.has_key("Done"):
302         Subst = {}
303         Subst["__OVERRIDE_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
304         Subst["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
305         bcc = []
306         if cnf.Find("Dinstall::Bcc") != "":
307             bcc.append(cnf["Dinstall::Bcc"])
308         if bcc:
309             Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
310         else:
311             Subst["__BCC__"] = "X-Filler: 42"
312         Subst["__CC__"] = "Cc: " + package + "@" + cnf["Dinstall::PackagesServer"] + "\nX-DAK: dak override"
313         Subst["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
314         Subst["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
315         Subst["__WHOAMI__"] = utils.whoami()
316         Subst["__SOURCE__"] = package
317
318         summary = "Concerning package %s...\n" % (package)
319         summary += "Operating on the %s suite\n" % (suite)
320         if newpriority != oldpriority:
321             summary += "Changed priority from %s to %s\n" % (oldpriority,newpriority)
322         if newsection != oldsection:
323             summary += "Changed section from %s to %s\n" % (oldsection,newsection)
324         Subst["__SUMMARY__"] = summary
325
326         template = os.path.join(cnf["Dir::Templates"], "override.bug-close")
327         for bug in utils.split_args(Options["Done"]):
328             Subst["__BUG_NUMBER__"] = bug
329             mail_message = utils.TemplateSubst(Subst, template)
330             utils.send_mail(mail_message)
331             Logger.log(["closed bug", bug])
332
333     Logger.close()
334
335 #################################################################################
336
337 if __name__ == '__main__':
338     main()