Devuan deployment of britney2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

731 lines
32 KiB

  1. #!/usr/bin/env python2.4
  2. # -*- coding: utf-8 -*-
  3. # Copyright (C) 2001-2004 Anthony Towns <ajt@debian.org>
  4. # Andreas Barth <aba@debian.org>
  5. # Fabio Tranchitella <kobold@debian.org>
  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. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. import os
  15. import re
  16. import sys
  17. import string
  18. import time
  19. import optparse
  20. import apt_pkg
  21. from excuse import Excuse
  22. VERSION = '2.0.alpha1'
  23. class Britney:
  24. """Debian testing updater script"""
  25. BINARY_FIELDS = ('Version', 'Pre-Depends', 'Depends', 'Conflicts', 'Provides', 'Source', 'Architecture', 'Version')
  26. SOURCE_FIELDS = ('Version', 'Maintainer', 'Section')
  27. HINTS_STANDARD = ("easy", "hint", "remove", "block", "unblock", "urgent", "approve")
  28. HINTS_ALL = ("force", "force-hint", "block-all") + HINTS_STANDARD
  29. def __init__(self):
  30. """Class constructor method: initialize and populate the data lists"""
  31. self.__parse_arguments()
  32. apt_pkg.init()
  33. self.date_now = int(((time.time() / (60*60)) - 15) / 24)
  34. self.sources = {'testing': self.read_sources(self.options.testing),
  35. 'unstable': self.read_sources(self.options.unstable),
  36. 'tpu': self.read_sources(self.options.tpu),}
  37. self.binaries = {'testing': {}, 'unstable': {}, 'tpu': {}}
  38. for arch in self.options.architectures.split():
  39. self.binaries['testing'][arch] = self.read_binaries(self.options.testing, "testing", arch)
  40. self.binaries['unstable'][arch] = self.read_binaries(self.options.unstable, "unstable", arch)
  41. self.binaries['tpu'][arch] = self.read_binaries(self.options.tpu, "tpu", arch)
  42. self.bugs = {'unstable': self.read_bugs(self.options.unstable),
  43. 'testing': self.read_bugs(self.options.testing),}
  44. self.normalize_bugs()
  45. self.dates = self.read_dates(self.options.testing)
  46. self.urgencies = self.read_urgencies(self.options.testing)
  47. self.approvals = self.read_approvals(self.options.tpu)
  48. self.hints = self.read_hints(self.options.unstable)
  49. self.excuses = []
  50. def __parse_arguments(self):
  51. """Parse command line arguments"""
  52. self.parser = optparse.OptionParser(version="%prog " + VERSION)
  53. self.parser.add_option("-v", "", action="count", dest="verbose", help="enable verbose output")
  54. self.parser.add_option("-c", "--config", action="store", dest="config",
  55. default="/etc/britney.conf", help="path for the configuration file")
  56. (self.options, self.args) = self.parser.parse_args()
  57. if not os.path.isfile(self.options.config):
  58. self.__log("Unable to read the configuration file (%s), exiting!" % self.options.config, type="E")
  59. sys.exit(1)
  60. self.MINDAYS = {}
  61. self.HINTS = {}
  62. for k, v in [map(string.strip,r.split('=', 1)) for r in file(self.options.config) if '=' in r and not r.strip().startswith('#')]:
  63. if k.startswith("MINDAYS_"):
  64. self.MINDAYS[k.split("_")[1].lower()] = int(v)
  65. elif k.startswith("HINTS_"):
  66. self.HINTS[k.split("_")[1].lower()] = \
  67. reduce(lambda x,y: x+y, [hasattr(self, "HINTS_" + i) and getattr(self, "HINTS_" + i) or (i,) for i in v.split()])
  68. else:
  69. setattr(self.options, k.lower(), v)
  70. def __log(self, msg, type="I"):
  71. """Print info messages according to verbosity level"""
  72. if self.options.verbose or type in ("E", "W"):
  73. print "%s: [%s] - %s" % (type, time.asctime(), msg)
  74. # Data reading/writing
  75. def read_sources(self, basedir):
  76. """Read the list of source packages from the specified directory"""
  77. sources = {}
  78. package = None
  79. filename = os.path.join(basedir, "Sources")
  80. self.__log("Loading source packages from %s" % filename)
  81. for l in open(filename):
  82. if l.startswith(' ') or ':' not in l: continue
  83. fields = map(string.strip, l.split(":",1))
  84. if fields[0] == 'Package':
  85. package = fields[1]
  86. sources[package] = dict([(k.lower(), None) for k in self.SOURCE_FIELDS] + [('binaries', [])])
  87. elif fields[0] in self.SOURCE_FIELDS:
  88. sources[package][fields[0].lower()] = fields[1]
  89. return sources
  90. def read_bugs(self, basedir):
  91. """Read the RC bugs count from the specified directory"""
  92. bugs = {}
  93. filename = os.path.join(basedir, "Bugs")
  94. self.__log("Loading RC bugs count from %s" % filename)
  95. for line in open(filename):
  96. l = line.strip().split()
  97. if len(l) != 2: continue
  98. try:
  99. bugs[l[0]] = int(l[1])
  100. except ValueError:
  101. self.__log("Bugs, unable to parse \"%s\"" % line, type="E")
  102. return bugs
  103. def maxver(self, pkg, dist):
  104. maxver = None
  105. if self.sources[dist].has_key(pkg):
  106. maxver = self.sources[dist][pkg]['version']
  107. for arch in self.options.architectures.split():
  108. if not self.binaries[dist][arch][0].has_key(pkg): continue
  109. pkgv = self.binaries[dist][arch][0][pkg]['version']
  110. if maxver == None or apt_pkg.VersionCompare(pkgv, maxver) > 0:
  111. maxver = pkgv
  112. return maxver
  113. def normalize_bugs(self):
  114. """Normalize the RC bugs count for testing and unstable"""
  115. for pkg in set(self.bugs['testing'].keys() + self.bugs['unstable'].keys()):
  116. if not self.bugs['testing'].has_key(pkg):
  117. self.bugs['testing'][pkg] = 0
  118. elif not self.bugs['unstable'].has_key(pkg):
  119. self.bugs['unstable'][pkg] = 0
  120. maxvert = self.maxver(pkg, 'testing')
  121. if maxvert == None or \
  122. self.bugs['testing'][pkg] == self.bugs['unstable'][pkg]:
  123. continue
  124. maxveru = self.maxver(pkg, 'unstable')
  125. if maxveru == None:
  126. continue
  127. elif apt_pkg.VersionCompare(maxvert, maxveru) >= 0:
  128. self.bugs['testing'][pkg] = self.bugs['unstable'][pkg]
  129. def read_dates(self, basedir):
  130. """Read the upload data for the packages from the specified directory"""
  131. dates = {}
  132. filename = os.path.join(basedir, "Dates")
  133. self.__log("Loading upload data from %s" % filename)
  134. for line in open(filename):
  135. l = line.strip().split()
  136. if len(l) != 3: continue
  137. try:
  138. dates[l[0]] = (l[1], int(l[2]))
  139. except ValueError:
  140. self.__log("Dates, unable to parse \"%s\"" % line, type="E")
  141. return dates
  142. def read_urgencies(self, basedir):
  143. """Read the upload urgency of the packages from the specified directory"""
  144. urgencies = {}
  145. filename = os.path.join(basedir, "Urgency")
  146. self.__log("Loading upload urgencies from %s" % filename)
  147. for line in open(filename):
  148. l = line.strip().split()
  149. if len(l) != 3: continue
  150. urgency_old = urgencies.get(l[0], self.options.default_urgency)
  151. mindays_old = self.MINDAYS.get(urgency_old, self.MINDAYS[self.options.default_urgency])
  152. mindays_new = self.MINDAYS.get(l[2], self.MINDAYS[self.options.default_urgency])
  153. if mindays_old <= mindays_new:
  154. continue
  155. tsrcv = self.sources['testing'].get(l[0], None)
  156. if tsrcv and apt_pkg.VersionCompare(tsrcv['version'], l[1]) >= 0:
  157. continue
  158. usrcv = self.sources['unstable'].get(l[0], None)
  159. if not usrcv or apt_pkg.VersionCompare(usrcv['version'], l[1]) < 0:
  160. continue
  161. urgencies[l[0]] = l[2]
  162. return urgencies
  163. def read_approvals(self, basedir):
  164. """Read the approvals data from the specified directory"""
  165. approvals = {}
  166. for approver in self.options.approvers.split():
  167. filename = os.path.join(basedir, "Approved", approver)
  168. self.__log("Loading approvals list from %s" % filename)
  169. for line in open(filename):
  170. l = line.strip().split()
  171. if len(l) != 2: continue
  172. approvals["%s_%s" % (l[0], l[1])] = approver
  173. return approvals
  174. def read_hints(self, basedir):
  175. """Read the approvals data from the specified directory"""
  176. hints = dict([(k,[]) for k in self.HINTS_ALL])
  177. for who in self.HINTS.keys():
  178. filename = os.path.join(basedir, "Hints", who)
  179. self.__log("Loading hints list from %s" % filename)
  180. for line in open(filename):
  181. line = line.strip()
  182. if line == "": continue
  183. l = line.split()
  184. if l[0] == 'finished':
  185. break
  186. elif l[0] not in self.HINTS[who]:
  187. continue
  188. elif l[0] in ["easy", "hint", "force-hint"]:
  189. hints[l[0]].append((who, [k.split("/") for k in l if "/" in k]))
  190. elif l[0] in ["block-all"]:
  191. hints[l[0]].extend([(y, who) for y in l[1:]])
  192. elif l[0] in ["block"]:
  193. hints[l[0]].extend([(y, who) for y in l[1:]])
  194. elif l[0] in ["remove", "approve", "unblock", "force", "urgent"]:
  195. hints[l[0]].extend([(k.split("/")[0], (k.split("/")[1],who) ) for k in l if "/" in k])
  196. for x in ["block", "block-all", "unblock", "force", "urgent", "remove"]:
  197. z = {}
  198. for a, b in hints[x]:
  199. if z.has_key(a):
  200. self.__log("Overriding %s[%s] = %s with %s" % (x, a, z[a], b), type="W")
  201. z[a] = b
  202. hints[x] = z
  203. return hints
  204. def read_binaries(self, basedir, distribution, arch):
  205. """Read the list of binary packages from the specified directory"""
  206. packages = {}
  207. package = None
  208. filename = os.path.join(basedir, "Packages_%s" % arch)
  209. self.__log("Loading binary packages from %s" % filename)
  210. for l in open(filename):
  211. if l.startswith(' ') or ':' not in l: continue
  212. fields = map(string.strip, l.split(":",1))
  213. if fields[0] == 'Package':
  214. package = fields[1]
  215. packages[package] = dict([(k.lower(), None) for k in self.BINARY_FIELDS] + [('rdepends', [])])
  216. packages[package]['source'] = package
  217. packages[package]['source-ver'] = None
  218. elif fields[0] == 'Source':
  219. packages[package][fields[0].lower()] = fields[1].split(" ")[0]
  220. if "(" in fields[1]:
  221. packages[package]['source-ver'] = fields[1].split("(")[1].split(")")[0]
  222. elif fields[0] in self.BINARY_FIELDS:
  223. packages[package][fields[0].lower()] = fields[1]
  224. provides = {}
  225. for pkgname in packages:
  226. if not packages[pkgname]['source-ver']:
  227. packages[pkgname]['source-ver'] = packages[pkgname]['version']
  228. if packages[pkgname]['source'] in self.sources[distribution]:
  229. self.sources[distribution][packages[pkgname]['source']]['binaries'].append(pkgname + "/" + arch)
  230. if not packages[pkgname]['provides']:
  231. continue
  232. parts = map(string.strip, packages[pkgname]["provides"].split(","))
  233. del packages[pkgname]["provides"]
  234. for p in parts:
  235. if p in provides:
  236. provides[p].append(pkgname)
  237. else:
  238. provides[p] = [pkgname]
  239. for pkgname in packages:
  240. dependencies = []
  241. if packages[pkgname]['depends']:
  242. packages[pkgname]['depends-txt'] = packages[pkgname]['depends']
  243. packages[pkgname]['depends'] = apt_pkg.ParseDepends(packages[pkgname]['depends'])
  244. dependencies.extend(packages[pkgname]['depends'])
  245. if packages[pkgname]['pre-depends']:
  246. packages[pkgname]['pre-depends-txt'] = packages[pkgname]['pre-depends']
  247. packages[pkgname]['pre-depends'] = apt_pkg.ParseDepends(packages[pkgname]['pre-depends'])
  248. dependencies.extend(packages[pkgname]['pre-depends'])
  249. for p in dependencies:
  250. for a in p:
  251. if a[0] not in packages: continue
  252. packages[a[0]]['rdepends'].append((pkgname, a[1], a[2]))
  253. return (packages, provides)
  254. # Package analisys
  255. def should_remove_source(self, pkg):
  256. """Check if a source package should be removed from testing"""
  257. if self.sources['unstable'].has_key(pkg):
  258. return False
  259. src = self.sources['testing'][pkg]
  260. excuse = Excuse("-" + pkg)
  261. excuse.set_vers(src['version'], None)
  262. src['maintainer'] and excuse.set_maint(src['maintainer'].strip())
  263. src['section'] and excuse.set_section(src['section'].strip())
  264. excuse.addhtml("Valid candidate")
  265. self.excuses.append(excuse)
  266. return True
  267. def same_source(self, sv1, sv2):
  268. if sv1 == sv2:
  269. return 1
  270. m = re.match(r'^(.*)\+b\d+$', sv1)
  271. if m: sv1 = m.group(1)
  272. m = re.match(r'^(.*)\+b\d+$', sv2)
  273. if m: sv2 = m.group(1)
  274. if sv1 == sv2:
  275. return 1
  276. if re.search("-", sv1) or re.search("-", sv2):
  277. m = re.match(r'^(.*-[^.]+)\.0\.\d+$', sv1)
  278. if m: sv1 = m.group(1)
  279. m = re.match(r'^(.*-[^.]+\.[^.]+)\.\d+$', sv1)
  280. if m: sv1 = m.group(1)
  281. m = re.match(r'^(.*-[^.]+)\.0\.\d+$', sv2)
  282. if m: sv2 = m.group(1)
  283. m = re.match(r'^(.*-[^.]+\.[^.]+)\.\d+$', sv2)
  284. if m: sv2 = m.group(1)
  285. return (sv1 == sv2)
  286. else:
  287. m = re.match(r'^([^-]+)\.0\.\d+$', sv1)
  288. if m and sv2 == m.group(1): return 1
  289. m = re.match(r'^([^-]+)\.0\.\d+$', sv2)
  290. if m and sv1 == m.group(1): return 1
  291. return 0
  292. def get_dependency_solvers(self, block, arch, distribution):
  293. packages = []
  294. missing = []
  295. for name, version, op in block:
  296. real_package = False
  297. if name in self.binaries[distribution][arch][0]:
  298. real_package = True
  299. package = self.binaries[distribution][arch][0][name]
  300. if op == '' and version == '' or apt_pkg.CheckDep(package['version'], op, version):
  301. packages.append(name)
  302. return (True, packages)
  303. # TODO: this would be enough according to policy, but not according to britney v.1
  304. #if op == '' and version == '' and name in self.binaries['unstable'][arch][1]:
  305. # # packages.extend(self.binaries['unstable'][arch][1][name])
  306. # return (True, packages)
  307. if name in self.binaries['unstable'][arch][1]:
  308. for prov in self.binaries['unstable'][arch][1][name]:
  309. package = self.binaries['unstable'][arch][0][prov]
  310. if op == '' and version == '' or apt_pkg.CheckDep(package['version'], op, version):
  311. packages.append(name)
  312. break
  313. if len(packages) > 0:
  314. return (True, packages)
  315. if real_package:
  316. missing.append(name)
  317. return (False, missing)
  318. def excuse_unsat_deps(self, pkg, src, arch, suite, excuse, ignore_break=0):
  319. binary_u = self.binaries[suite][arch][0][pkg]
  320. for type in ('Pre-Depends', 'Depends'):
  321. type_key = type.lower()
  322. if not binary_u[type_key]:
  323. continue
  324. packages = []
  325. for block, block_txt in map(None, binary_u[type_key], binary_u[type_key + '-txt'].split(',')):
  326. solved, packages = self.get_dependency_solvers(block, arch, 'testing')
  327. if solved: continue
  328. solved, packages = self.get_dependency_solvers(block, arch, suite)
  329. packages = [self.binaries[suite][arch][0][p]['source'] for p in packages]
  330. if src in packages: continue
  331. if len(packages) == 0:
  332. excuse.addhtml("%s/%s unsatisfiable %s: %s" % (pkg, arch, type, block_txt.strip()))
  333. for p in packages:
  334. if ignore_break or arch not in self.options.break_arches.split():
  335. excuse.add_dep(p)
  336. else:
  337. excuse.add_break_dep(p, arch)
  338. def should_upgrade_srcarch(self, src, arch, suite):
  339. # binnmu this arch?
  340. source_t = self.sources['testing'][src]
  341. source_u = self.sources[suite][src]
  342. ref = "%s/%s%s" % (src, arch, suite != 'unstable' and "_" + suite or "")
  343. excuse = Excuse(ref)
  344. excuse.set_vers(source_t['version'], source_t['version'])
  345. source_u['maintainer'] and excuse.set_maint(source_u['maintainer'].strip())
  346. source_u['section'] and excuse.set_section(source_u['section'].strip())
  347. anywrongver = False
  348. anyworthdoing = False
  349. if self.hints["remove"].has_key(src) and \
  350. self.same_source(source_t['version'], self.hints["remove"][src][0]):
  351. excuse.addhtml("Removal request by %s" % (self.hints["remove"][src][1]))
  352. excuse.addhtml("Trying to remove package, not update it")
  353. excuse.addhtml("Not considered")
  354. self.excuses.append(excuse)
  355. return False
  356. for pkg in source_u['binaries']:
  357. if not pkg.endswith("/" + arch): continue
  358. pkg_name = pkg.split("/")[0]
  359. binary_t = pkg in source_t['binaries'] and self.binaries['testing'][arch][0][pkg_name] or None
  360. binary_u = self.binaries[suite][arch][0][pkg_name]
  361. pkgsv = self.binaries[suite][arch][0][pkg_name]['source-ver']
  362. if binary_u['architecture'] == 'all':
  363. excuse.addhtml("Ignoring %s %s (from %s) as it is arch: all" % (pkg, binary_u['version'], pkgsv))
  364. continue
  365. if not self.same_source(source_t['version'], pkgsv):
  366. anywrongver = True
  367. excuse.addhtml("From wrong source: %s %s (%s not %s)" % (pkg, binary_u['version'], pkgsv, source_t['version']))
  368. break
  369. self.excuse_unsat_deps(pkg_name, src, arch, suite, excuse)
  370. if not binary_t:
  371. excuse.addhtml("New binary: %s (%s)" % (pkg, binary_u['version']))
  372. anyworthdoing = True
  373. continue
  374. vcompare = apt_pkg.VersionCompare(binary_t['version'], binary_u['version'])
  375. if vcompare > 0:
  376. anywrongver = True
  377. excuse.addhtml("Not downgrading: %s (%s to %s)" % (pkg, binary_t['version'], binary_u['version']))
  378. break
  379. elif vcompare < 0:
  380. excuse.addhtml("Updated binary: %s (%s to %s)" % (pkg, binary_t['version'], binary_u['version']))
  381. anyworthdoing = True
  382. if not anywrongver and (anyworthdoing or src in self.sources[suite]):
  383. srcv = self.sources[suite][src]['version']
  384. ssrc = self.same_source(source_t['version'], srcv)
  385. for pkg in set(k.split("/")[0] for k in self.sources['testing'][src]['binaries']):
  386. if self.binaries['testing'][arch][0][pkg]['architecture'] == 'all':
  387. excuse.addhtml("Ignoring removal of %s as it is arch: all" % (pkg))
  388. continue
  389. if not self.binaries[suite][arch][0].has_key(pkg):
  390. tpkgv = self.binaries['testing'][arch][0][pkg]['version']
  391. excuse.addhtml("Removed binary: %s %s" % (pkg, tpkgv))
  392. if ssrc: anyworthdoing = True
  393. if not anywrongver and anyworthdoing:
  394. excuse.addhtml("Valid candidate")
  395. self.excuses.append(excuse)
  396. elif anyworthdoing:
  397. excuse.addhtml("Not considered")
  398. self.excuses.append(excuse)
  399. return False
  400. return True
  401. def should_upgrade_src(self, src, suite):
  402. source_u = self.sources[suite][src]
  403. if src in self.sources['testing']:
  404. source_t = self.sources['testing'][src]
  405. if apt_pkg.VersionCompare(source_t['version'], source_u['version']) == 0:
  406. # Candidate for binnmus only
  407. return False
  408. else:
  409. source_t = None
  410. ref = "%s%s" % (src, suite != 'unstable' and "_" + suite or "")
  411. update_candidate = True
  412. excuse = Excuse(ref)
  413. excuse.set_vers(source_t and source_t['version'] or None, source_u['version'])
  414. source_u['maintainer'] and excuse.set_maint(source_u['maintainer'].strip())
  415. source_u['section'] and excuse.set_section(source_u['section'].strip())
  416. if source_t and apt_pkg.VersionCompare(source_u['version'], source_t['version']) < 0:
  417. # Version in unstable is older!
  418. excuse.addhtml("ALERT: %s is newer in testing (%s %s)" % (src, source_t['version'], source_u['version']))
  419. self.excuses.append(excuse)
  420. return False
  421. urgency = self.urgencies.get(src, self.options.default_urgency)
  422. if not source_t and urgency != self.options.default_urgency:
  423. excuse.addhtml("Ignoring %s urgency setting for NEW package" % (urgency))
  424. urgency = self.options.default_urgency
  425. if self.hints["remove"].has_key(src):
  426. if source_t and self.same_source(source_t['version'], self.hints['remove'][src][0]) or \
  427. self.same_source(source_u['version'], self.hints['remove'][src][0]):
  428. excuse.addhtml("Removal request by %s" % (self.hints["remove"][src][1]))
  429. excuse.addhtml("Trying to remove package, not update it")
  430. update_candidate = False
  431. blocked = None
  432. if self.hints["block"].has_key(src):
  433. blocked = self.hints["block"][src]
  434. elif self.hints["block-all"].has_key("source"):
  435. blocked = self.hints["block-all"]["source"]
  436. if blocked:
  437. unblock = self.hints["unblock"].get(src,(None,None))
  438. if unblock[0] != None and self.same_source(unblock[0], source_u['version']):
  439. excuse.addhtml("Ignoring request to block package by %s, due to unblock request by %s" % (blocked, unblock[1]))
  440. else:
  441. if unblock[0] != None:
  442. excuse.addhtml("Unblock request by %s ignored due to version mismatch: %s" % (unblock[1], unblock[0]))
  443. excuse.addhtml("Not touching package, as requested by %s (contact debian-release if update is needed)" % (blocked))
  444. update_candidate = False
  445. if suite == 'unstable':
  446. if not self.dates.has_key(src):
  447. self.dates[src] = (source_u['version'], self.date_now)
  448. elif not self.same_source(self.dates[src][0], source_u['version']):
  449. self.dates[src] = (source_u['version'], self.date_now)
  450. days_old = self.date_now - self.dates[src][1]
  451. min_days = self.MINDAYS[urgency]
  452. excuse.setdaysold(days_old, min_days)
  453. if days_old < min_days:
  454. if self.hints["urgent"].has_key(src) and self.same_source(source_u['version'], self.hints["urgent"][src][0]):
  455. excuse.addhtml("Too young, but urgency pushed by %s" % (self.hints["urgent"][src][1]))
  456. else:
  457. update_candidate = False
  458. pkgs = {src: ["source"]}
  459. for arch in self.options.architectures.split():
  460. oodbins = {}
  461. for pkg in set(k.split("/")[0] for k in self.sources[suite][src]['binaries']):
  462. if not pkgs.has_key(pkg): pkgs[pkg] = []
  463. pkgs[pkg].append(arch)
  464. binary_u = self.binaries[suite][arch][0][pkg]
  465. pkgsv = binary_u['source-ver']
  466. if not self.same_source(source_u['version'], pkgsv):
  467. if not oodbins.has_key(pkgsv):
  468. oodbins[pkgsv] = []
  469. oodbins[pkgsv].append(pkg)
  470. continue
  471. if binary_u['architecture'] != 'all' or arch in self.options.nobreakall_arches:
  472. self.excuse_unsat_deps(pkg, src, arch, suite, excuse)
  473. if oodbins:
  474. oodtxt = ""
  475. for v in oodbins.keys():
  476. if oodtxt: oodtxt = oodtxt + "; "
  477. oodtxt = oodtxt + "%s (from <a href=\"http://buildd.debian.org/build.php?arch=%s&pkg=%s&ver=%s\" target=\"_blank\">%s</a>)" % (", ".join(sorted(oodbins[v])), arch, src, v, v)
  478. text = "out of date on <a href=\"http://buildd.debian.org/build.php?arch=%s&pkg=%s&ver=%s\" target=\"_blank\">%s</a>: %s" % (arch, src, source_u['version'], arch, oodtxt)
  479. if arch in self.options.fucked_arches:
  480. text = text + " (but %s isn't keeping up, so nevermind)" % (arch)
  481. else:
  482. update_candidate = False
  483. if self.date_now != self.dates[src][1]:
  484. excuse.addhtml(text)
  485. if len(self.sources[suite][src]['binaries']) == 0:
  486. excuse.addhtml("%s has no binaries on any arch" % src)
  487. update_candidate = False
  488. if suite == 'unstable':
  489. for pkg in pkgs.keys():
  490. if not self.bugs['testing'].has_key(pkg):
  491. self.bugs['testing'][pkg] = 0
  492. if not self.bugs['unstable'].has_key(pkg):
  493. self.bugs['unstable'][pkg] = 0
  494. if self.bugs['unstable'][pkg] > self.bugs['testing'][pkg]:
  495. excuse.addhtml("%s (%s) is <a href=\"http://bugs.debian.org/cgi-bin/pkgreport.cgi?which=pkg&data=%s&sev-inc=critical&sev-inc=grave&sev-inc=serious\" target=\"_blank\">buggy</a>! (%d > %d)" % (pkg, ", ".join(pkgs[pkg]), pkg, self.bugs['unstable'][pkg], self.bugs['testing'][pkg]))
  496. update_candidate = False
  497. elif self.bugs['unstable'][pkg] > 0:
  498. excuse.addhtml("%s (%s) is (less) <a href=\"http://bugs.debian.org/cgi-bin/pkgreport.cgi?which=pkg&data=%s&sev-inc=critical&sev-inc=grave&sev-inc=serious\" target=\"_blank\">buggy</a>! (%d <= %d)" % (pkg, ", ".join(pkgs[pkg]), pkg, self.bugs['unstable'][pkg], self.bugs['testing'][pkg]))
  499. if not update_candidate and self.hints["force"].has_key(src) and self.same_source(source_u['version'], self.hints["force"][src][0]) :
  500. excuse.dontinvalidate = 1
  501. excuse.addhtml("Should ignore, but forced by %s" % (self.hints["force"][src][1]))
  502. update_candidate = True
  503. if suite == "tpu":
  504. if self.approvals.has_key("%s_%s" % (src, source_u['version'])):
  505. excuse.addhtml("Approved by %s" % approvals["%s_%s" % (src, source_u['version'])])
  506. else:
  507. excuse.addhtml("NEEDS APPROVAL BY RM")
  508. update_candidate = False
  509. if update_candidate:
  510. excuse.addhtml("Valid candidate")
  511. else:
  512. excuse.addhtml("Not considered")
  513. self.excuses.append(excuse)
  514. return update_candidate
  515. def reversed_exc_deps(self):
  516. res = {}
  517. for exc in self.excuses:
  518. for d in exc.deps:
  519. if not res.has_key(d): res[d] = []
  520. res[d].append(exc.name)
  521. return res
  522. def invalidate_excuses(self, valid, invalid):
  523. i = 0
  524. exclookup = {}
  525. for e in self.excuses:
  526. exclookup[e.name] = e
  527. revdeps = self.reversed_exc_deps()
  528. while i < len(invalid):
  529. if not revdeps.has_key(invalid[i]):
  530. i += 1
  531. continue
  532. if (invalid[i] + "_tpu") in valid:
  533. i += 1
  534. continue
  535. for x in revdeps[invalid[i]]:
  536. if x in valid and exclookup[x].dontinvalidate:
  537. continue
  538. exclookup[x].invalidate_dep(invalid[i])
  539. if x in valid:
  540. p = valid.index(x)
  541. invalid.append(valid.pop(p))
  542. exclookup[x].addhtml("Invalidated by dependency")
  543. exclookup[x].addhtml("Not considered")
  544. i = i + 1
  545. def main(self):
  546. """Main method, entry point for the analisys"""
  547. upgrade_me = []
  548. # Packages to be removed
  549. for pkg in self.sources['testing']:
  550. if self.should_remove_source(pkg):
  551. upgrade_me.append("-" + pkg)
  552. # Packages to be upgraded from unstable
  553. for pkg in self.sources['unstable']:
  554. if self.sources['testing'].has_key(pkg):
  555. for arch in self.options.architectures.split():
  556. if self.should_upgrade_srcarch(pkg, arch, 'unstable'):
  557. upgrade_me.append("%s/%s" % (pkg, arch))
  558. if self.should_upgrade_src(pkg, 'unstable'):
  559. upgrade_me.append(pkg)
  560. # Packages to be upgraded from testing-proposed-updates
  561. for pkg in self.sources['tpu']:
  562. if self.sources['testing'].has_key(pkg):
  563. for arch in self.options.architectures.split():
  564. if self.should_upgrade_srcarch(pkg, arch, 'tpu'):
  565. upgrade_me.append("%s/%s_tpu" % (pkg, arch))
  566. if self.should_upgrade_src(pkg, 'tpu'):
  567. upgrade_me.append("%s_tpu" % pkg)
  568. # Process 'remove' hints
  569. for src in self.hints["remove"].keys():
  570. if src in upgrade_me: continue
  571. if ("-"+src) in upgrade_me: continue
  572. if not self.sources['testing'].has_key(src): continue
  573. tsrcv = self.sources['testing'][src]['version']
  574. if not self.same_source(tsrcv, self.hints["remove"][src][0]): continue
  575. upgrade_me.append("-%s" % (src))
  576. excuse = Excuse("-%s" % (src))
  577. excuse.set_vers(tsrcv, None)
  578. excuse.addhtml("Removal request by %s" % (self.hints["remove"][src][1]))
  579. excuse.addhtml("Package is broken, will try to remove")
  580. self.excuses.append(excuse)
  581. # Sort excuses by daysold and name
  582. self.excuses.sort(lambda x, y: cmp(x.daysold, y.daysold) or cmp(x.name, y.name))
  583. # Extract unconsidered packages
  584. unconsidered = [e.name for e in self.excuses if e.name not in upgrade_me]
  585. # Invalidate impossible excuses
  586. for e in self.excuses:
  587. for d in e.deps:
  588. if d not in upgrade_me and d not in unconsidered:
  589. e.addhtml("Unpossible dep: %s -> %s" % (e.name, d))
  590. self.invalidate_excuses(upgrade_me, unconsidered)
  591. # Write excuses
  592. f = open(self.options.excuses_output, 'w')
  593. f.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n")
  594. f.write("<html><head><title>excuses...</title>")
  595. f.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"></head><body>\n")
  596. f.write("<p>Generated: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "</p>\n")
  597. f.write("<ul>\n")
  598. for e in self.excuses:
  599. f.write("<li>%s" % e.html())
  600. f.write("</ul></body></html>\n")
  601. f.close()
  602. del self.excuses
  603. # Some examples ...
  604. # print self.sources['testing']['zsh-beta']['version']
  605. # print self.sources['unstable']['zsh-beta']['version']
  606. # print self.urgencies['zsh-beta']
  607. # Which packages depend on passwd?
  608. # for i in self.binaries['testing']['i386'][0]['passwd']['rdepends']:
  609. # print i
  610. # Which packages provide mysql-server?
  611. # for i in self.binaries['testing']['i386'][1]['mysql-server']:
  612. # print i
  613. # Which binary packages are build from php4 testing source package?
  614. # print self.sources['testing']['php4']['binaries']
  615. if __name__ == '__main__':
  616. Britney().main()