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.
 
 
 

366 lines
14 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright (C) 2001-2004 Anthony Towns <ajt@debian.org>
  3. # Andreas Barth <aba@debian.org>
  4. # Fabio Tranchitella <kobold@debian.org>
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. from collections import defaultdict
  14. import re
  15. from britney2 import DependencyType
  16. from britney2.policies.policy import PolicyVerdict
  17. VERDICT2DESC = {
  18. PolicyVerdict.PASS:
  19. 'Will attempt migration (Any information below is purely informational)',
  20. PolicyVerdict.PASS_HINTED:
  21. 'Will attempt migration due to a hint (Any information below is purely informational)',
  22. PolicyVerdict.REJECTED_TEMPORARILY:
  23. 'Waiting for test results, another package or too young (no action required now - check later)',
  24. PolicyVerdict.REJECTED_WAITING_FOR_ANOTHER_ITEM:
  25. 'Waiting for another item to be ready to migrate (no action required now - check later)',
  26. PolicyVerdict.REJECTED_BLOCKED_BY_ANOTHER_ITEM:
  27. 'BLOCKED: Cannot migrate due to another item, which is blocked (please check which dependencies are stuck)',
  28. PolicyVerdict.REJECTED_NEEDS_APPROVAL:
  29. 'BLOCKED: Needs an approval (either due to a freeze, the source suite or a manual hint)',
  30. PolicyVerdict.REJECTED_CANNOT_DETERMINE_IF_PERMANENT:
  31. 'BLOCKED: Maybe temporary, maybe blocked but Britney is missing information (check below)',
  32. PolicyVerdict.REJECTED_PERMANENTLY:
  33. 'BLOCKED: Rejected/violates migration policy/introduces a regression',
  34. }
  35. class Excuse(object):
  36. """Excuse class
  37. This class represents an update excuse, which is a detailed explanation
  38. of why a package can or cannot be updated in the testing distribution from
  39. a newer package in another distribution (like for example unstable).
  40. The main purpose of the excuses is to be written in an HTML file which
  41. will be published over HTTP. The maintainers will be able to parse it
  42. manually or automatically to find the explanation of why their packages
  43. have been updated or not.
  44. """
  45. # @var reemail
  46. # Regular expression for removing the email address
  47. reemail = re.compile(r" *<.*?>")
  48. def __init__(self, name):
  49. """Class constructor
  50. This method initializes the excuse with the specified name and
  51. the default values.
  52. """
  53. self.name = name
  54. self.ver = ("-", "-")
  55. self.maint = None
  56. self.daysold = None
  57. self.mindays = None
  58. self.section = None
  59. self._is_valid = False
  60. self.needs_approval = False
  61. self.hints = []
  62. self.forced = False
  63. self._policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
  64. self.all_invalid_deps = {}
  65. self.all_deps = {}
  66. self.sane_deps = []
  67. self.break_deps = []
  68. self.unsatisfiable_on_archs = []
  69. self.unsat_deps = defaultdict(set)
  70. self.newbugs = set()
  71. self.oldbugs = set()
  72. self.reason = {}
  73. self.htmlline = []
  74. self.missing_builds = set()
  75. self.missing_builds_ood_arch = set()
  76. self.old_binaries = defaultdict(set)
  77. self.policy_info = {}
  78. self.verdict_info = defaultdict(list)
  79. self.infoline = []
  80. self.detailed_info = []
  81. self.bounty = {}
  82. self.penalty = {}
  83. def sortkey(self):
  84. if self.daysold is None:
  85. return (-1, self.name)
  86. return (self.daysold, self.name)
  87. @property
  88. def is_valid(self):
  89. return False if self._policy_verdict.is_rejected else True
  90. @property
  91. def policy_verdict(self):
  92. return self._policy_verdict
  93. @policy_verdict.setter
  94. def policy_verdict(self, value):
  95. if value.is_rejected and self.forced:
  96. # By virtue of being forced, the item was hinted to
  97. # undo the rejection
  98. value = PolicyVerdict.PASS_HINTED
  99. self._policy_verdict = value
  100. def set_vers(self, tver, uver):
  101. """Set the versions of the item from target and source suite"""
  102. if tver and uver:
  103. self.ver = (tver, uver)
  104. elif tver:
  105. self.ver = (tver, self.ver[1])
  106. elif uver:
  107. self.ver = (self.ver[0], uver)
  108. def set_maint(self, maint):
  109. """Set the package maintainer's name"""
  110. self.maint = self.reemail.sub("", maint)
  111. def set_section(self, section):
  112. """Set the section of the package"""
  113. self.section = section
  114. def add_dependency(self, deptype, name, arch):
  115. """Add a dependency of type deptype """
  116. if name not in self.all_deps:
  117. self.all_deps[name] = {}
  118. if deptype not in self.all_deps[name]:
  119. self.all_deps[name][deptype] = []
  120. self.all_deps[name][deptype].append(arch)
  121. def get_deps(self):
  122. # the autohinter uses the excuses data to query dependencies between
  123. # excuses. For now, we keep the current behaviour by just returning
  124. # the data that was in the old deps set
  125. """ Get the dependencies of type DEPENDS """
  126. deps = set()
  127. for dep in self.all_deps:
  128. if DependencyType.DEPENDS in self.all_deps[dep]:
  129. deps.add(dep)
  130. return deps
  131. def add_sane_dep(self, name):
  132. """Add a sane dependency"""
  133. if name not in self.sane_deps:
  134. self.sane_deps.append(name)
  135. def add_break_dep(self, name, arch):
  136. """Add a break dependency"""
  137. if (name, arch) not in self.break_deps:
  138. self.break_deps.append((name, arch))
  139. def add_unsatisfiable_on_arch(self, arch):
  140. """Add an arch that has unsatisfiable dependencies"""
  141. if arch not in self.unsatisfiable_on_archs:
  142. self.unsatisfiable_on_archs.append(arch)
  143. def add_unsatisfiable_dep(self, signature, arch):
  144. """Add an unsatisfiable dependency"""
  145. self.unsat_deps[arch].add(signature)
  146. def invalidate_dependency(self, name, verdict):
  147. """Invalidate dependency"""
  148. self.all_invalid_deps[name] = verdict
  149. def setdaysold(self, daysold, mindays):
  150. """Set the number of days from the upload and the minimum number of days for the update"""
  151. self.daysold = daysold
  152. self.mindays = mindays
  153. def force(self):
  154. """Add force hint"""
  155. self.forced = True
  156. if self._policy_verdict.is_rejected:
  157. self._policy_verdict = PolicyVerdict.PASS_HINTED
  158. return True
  159. return False
  160. def addinfo(self, note):
  161. """Add a note in HTML"""
  162. self.infoline.append(note)
  163. def add_verdict_info(self, verdict, note):
  164. """Add a note to info about this verdict level"""
  165. self.verdict_info[verdict].append(note)
  166. def add_detailed_info(self, note):
  167. """Add a note to detailed info"""
  168. self.detailed_info.append(note)
  169. def missing_build_on_arch(self, arch):
  170. """Note that the item is missing a build on a given architecture"""
  171. self.missing_builds.add(arch)
  172. def missing_build_on_ood_arch(self, arch):
  173. """Note that the item is missing a build on a given "out of date" architecture"""
  174. self.missing_builds.add(arch)
  175. def add_old_binary(self, binary, from_source_version):
  176. """Denote than an old binary ("cruft") is available from a previous source version"""
  177. self.old_binaries[from_source_version].add(binary)
  178. def add_hint(self, hint):
  179. self.hints.append(hint)
  180. def _format_verdict_summary(self):
  181. verdict = self._policy_verdict
  182. if verdict in VERDICT2DESC:
  183. return VERDICT2DESC[verdict]
  184. return "UNKNOWN: Missing description for {0} - Please file a bug against Britney".format(verdict.name)
  185. def _render_dep_issues(self, dep_issues, invalid_deps):
  186. lastdep = ""
  187. res = []
  188. for x in sorted(dep_issues, key=lambda x: x.split('/')[0]):
  189. dep = x.split('/')[0]
  190. if dep != lastdep:
  191. seen = {}
  192. lastdep = dep
  193. for deptype in sorted(dep_issues[x], key=lambda y: str(y)):
  194. field = deptype
  195. if deptype in seen:
  196. continue
  197. seen[deptype] = True
  198. if x in invalid_deps:
  199. res.append("%s: %s <a href=\"#%s\">%s</a> (not considered)" % (field, self.name, dep, dep))
  200. else:
  201. res.append("%s: %s <a href=\"#%s\">%s</a>" % (field, self.name, dep, dep))
  202. return res
  203. def html(self):
  204. """Render the excuse in HTML"""
  205. res = "<a id=\"%s\" name=\"%s\">%s</a> (%s to %s)\n<ul>\n" % \
  206. (self.name, self.name, self.name, self.ver[0], self.ver[1])
  207. info = self._text()
  208. for l in info:
  209. res += "<li>%s\n" % l
  210. res = res + "</ul>\n"
  211. return res
  212. def setbugs(self, oldbugs, newbugs):
  213. """"Set the list of old and new bugs"""
  214. self.newbugs.update(newbugs)
  215. self.oldbugs.update(oldbugs)
  216. def addreason(self, reason):
  217. """"adding reason"""
  218. self.reason[reason] = 1
  219. def _text(self):
  220. """Render the excuse in text"""
  221. res = []
  222. res.append(
  223. "Migration status for %s (%s to %s): %s" %
  224. (self.name, self.ver[0], self.ver[1], self._format_verdict_summary()))
  225. if not self.is_valid:
  226. res.append("Issues preventing migration:")
  227. for v in sorted(self.verdict_info.keys(), reverse=True):
  228. for x in self.verdict_info[v]:
  229. res.append("" + x + "")
  230. di = [x for x in self.all_invalid_deps.keys() if self.all_invalid_deps[x] == v]
  231. ad = {x: self.all_deps[x] for x in di}
  232. for x in self._render_dep_issues(ad, di):
  233. res.append("" + x + "")
  234. if self.infoline:
  235. res.append("Additional info:")
  236. for x in self.infoline:
  237. res.append("" + x + "")
  238. if self.htmlline:
  239. res.append("Legacy info:")
  240. for x in self.htmlline:
  241. res.append("" + x + "")
  242. return res
  243. def excusedata(self):
  244. """Render the excuse in as key-value data"""
  245. source = self.name
  246. if '_' in source:
  247. source = source.split("_")[0]
  248. if '/' in source:
  249. source = source.split("/")[0]
  250. if source[0] == '-':
  251. source = source[1:]
  252. excusedata = {}
  253. excusedata["excuses"] = self._text()
  254. excusedata["item-name"] = self.name
  255. excusedata["source"] = source
  256. excusedata["migration-policy-verdict"] = self._policy_verdict.name
  257. excusedata["old-version"] = self.ver[0]
  258. excusedata["new-version"] = self.ver[1]
  259. if self.maint:
  260. excusedata['maintainer'] = self.maint
  261. if self.section and self.section.find("/") > -1:
  262. excusedata['component'] = self.section.split('/')[0]
  263. if self.policy_info:
  264. excusedata['policy_info'] = self.policy_info
  265. if self.missing_builds or self.missing_builds_ood_arch:
  266. excusedata['missing-builds'] = {
  267. 'on-architectures': sorted(self.missing_builds),
  268. 'on-unimportant-architectures': sorted(self.missing_builds_ood_arch),
  269. }
  270. if self.all_invalid_deps:
  271. excusedata['invalidated-by-other-package'] = True
  272. if self.all_deps or self.all_invalid_deps.keys() \
  273. or self.break_deps or self.unsat_deps:
  274. excusedata['dependencies'] = dep_data = {}
  275. migrate_after = sorted(self.all_deps.keys() - self.all_invalid_deps.keys())
  276. break_deps = [x for x, _ in self.break_deps if x not in self.all_deps]
  277. if self.all_invalid_deps.keys():
  278. dep_data['blocked-by'] = sorted(self.all_invalid_deps.keys())
  279. if migrate_after:
  280. dep_data['migrate-after'] = migrate_after
  281. if break_deps:
  282. dep_data['unimportant-dependencies'] = sorted(break_deps)
  283. if self.unsat_deps:
  284. dep_data['unsatisfiable-dependencies'] = {x: sorted(self.unsat_deps[x]) for x in self.unsat_deps}
  285. if self.needs_approval:
  286. status = 'not-approved'
  287. if any(h.type == 'unblock' for h in self.hints):
  288. status = 'approved'
  289. excusedata['manual-approval-status'] = status
  290. if self.hints:
  291. hint_info = [{
  292. 'hint-type': h.type,
  293. 'hint-from': h.user,
  294. } for h in self.hints]
  295. excusedata['hints'] = hint_info
  296. if self.old_binaries:
  297. excusedata['old-binaries'] = {x: sorted(self.old_binaries[x]) for x in self.old_binaries}
  298. if self.forced:
  299. excusedata["forced-reason"] = sorted(list(self.reason.keys()))
  300. excusedata["reason"] = []
  301. else:
  302. excusedata["reason"] = sorted(list(self.reason.keys()))
  303. excusedata["is-candidate"] = self.is_valid
  304. if self.detailed_info:
  305. di = []
  306. for x in self.detailed_info:
  307. di.append("" + x + "")
  308. excusedata["detailed-info"] = di
  309. return excusedata
  310. def add_bounty(self, policy, bounty):
  311. """"adding bounty"""
  312. self.bounty[policy] = bounty
  313. def add_penalty(self, policy, penalty):
  314. """"adding penalty"""
  315. self.penalty[policy] = penalty