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.
 
 
 

657 lines
33 KiB

  1. from itertools import chain
  2. from urllib.parse import quote
  3. import apt_pkg
  4. from britney2 import DependencyType
  5. from britney2.excuse import Excuse
  6. from britney2.policies import PolicyVerdict
  7. from britney2.utils import (invalidate_excuses, find_smooth_updateable_binaries, compute_item_name,
  8. get_dependency_solvers,
  9. )
  10. class ExcuseFinder(object):
  11. def __init__(self, options, suite_info, all_binaries, pkg_universe, policy_engine, mi_factory, hints):
  12. self.options = options
  13. self.suite_info = suite_info
  14. self.all_binaries = all_binaries
  15. self.pkg_universe = pkg_universe
  16. self._policy_engine = policy_engine
  17. self._migration_item_factory = mi_factory
  18. self.hints = hints
  19. self.excuses = {}
  20. def _excuse_unsat_deps(self, pkg, src, arch, source_suite, excuse, get_dependency_solvers=get_dependency_solvers):
  21. """Find unsatisfied dependencies for a binary package
  22. This method analyzes the dependencies of the binary package specified
  23. by the parameter `pkg', built from the source package `src', for the
  24. architecture `arch' within the suite `suite'. If the dependency can't
  25. be satisfied in testing and/or unstable, it updates the excuse passed
  26. as parameter.
  27. """
  28. # retrieve the binary package from the specified suite and arch
  29. target_suite = self.suite_info.target_suite
  30. binaries_s_a = source_suite.binaries[arch]
  31. provides_s_a = source_suite.provides_table[arch]
  32. binaries_t_a = target_suite.binaries[arch]
  33. provides_t_a = target_suite.provides_table[arch]
  34. binary_u = binaries_s_a[pkg]
  35. source_s = source_suite.sources[binary_u.source]
  36. if (binary_u.source_version != source_s.version):
  37. # we don't want cruft to block packages, so if this is cruft, we
  38. # can simply ignore it; if the cruft would migrate to testing, the
  39. # installability check will catch missing deps
  40. return True
  41. # local copies for better performance
  42. parse_depends = apt_pkg.parse_depends
  43. # analyze the dependency fields (if present)
  44. deps = binary_u.depends
  45. if not deps:
  46. return True
  47. is_all_ok = True
  48. # for every dependency block (formed as conjunction of disjunction)
  49. for block, block_txt in zip(parse_depends(deps, False), deps.split(',')):
  50. # if the block is satisfied in testing, then skip the block
  51. packages = get_dependency_solvers(block, binaries_t_a, provides_t_a)
  52. if packages:
  53. for p in packages:
  54. if p.pkg_id.package_name not in binaries_s_a:
  55. continue
  56. excuse.add_sane_dep(p.source)
  57. continue
  58. # check if the block can be satisfied in the source suite, and list the solving packages
  59. packages = get_dependency_solvers(block, binaries_s_a, provides_s_a)
  60. packages = sorted(p.source for p in packages)
  61. # if the dependency can be satisfied by the same source package, skip the block:
  62. # obviously both binary packages will enter testing together
  63. if src in packages:
  64. continue
  65. # if no package can satisfy the dependency, add this information to the excuse
  66. if not packages:
  67. excuse.addhtml("%s/%s unsatisfiable Depends: %s" % (pkg, arch, block_txt.strip()))
  68. excuse.add_unsatisfiable_dep(block_txt.strip(), arch)
  69. excuse.addreason("depends")
  70. excuse.add_unsatisfiable_on_arch(arch)
  71. if arch not in self.options.break_arches:
  72. is_all_ok = False
  73. continue
  74. # for the solving packages, update the excuse to add the dependencies
  75. if arch not in self.options.break_arches:
  76. sources_t = target_suite.sources
  77. sources_s = source_suite.sources
  78. for p in packages:
  79. item_name = compute_item_name(sources_t, sources_s, p, arch)
  80. excuse.add_dependency(DependencyType.DEPENDS, item_name, arch)
  81. else:
  82. for p in packages:
  83. excuse.add_break_dep(p, arch)
  84. return is_all_ok
  85. def _should_remove_source(self, item):
  86. """Check if a source package should be removed from testing
  87. This method checks if a source package should be removed from the
  88. target suite; this happens if the source package is not
  89. present in the primary source suite anymore.
  90. It returns True if the package can be removed, False otherwise.
  91. In the former case, a new excuse is appended to the object
  92. attribute excuses.
  93. """
  94. if hasattr(self.options, 'partial_source'):
  95. return False
  96. # if the source package is available in unstable, then do nothing
  97. source_suite = self.suite_info.primary_source_suite
  98. pkg = item.package
  99. if pkg in source_suite.sources:
  100. return False
  101. # otherwise, add a new excuse for its removal
  102. src = item.suite.sources[pkg]
  103. excuse = Excuse(item.name)
  104. excuse.addhtml("Package not in %s, will try to remove" % source_suite.name)
  105. excuse.set_vers(src.version, None)
  106. src.maintainer and excuse.set_maint(src.maintainer)
  107. src.section and excuse.set_section(src.section)
  108. # if the package is blocked, skip it
  109. for hint in self.hints.search('block', package=pkg, removal=True):
  110. excuse.addhtml("Not touching package, as requested by %s "
  111. "(contact debian-release if update is needed)" % hint.user)
  112. excuse.addreason("block")
  113. self.excuses[excuse.name] = excuse
  114. return False
  115. excuse.policy_verdict = PolicyVerdict.PASS
  116. self.excuses[excuse.name] = excuse
  117. return True
  118. def _should_upgrade_srcarch(self, item):
  119. """Check if a set of binary packages should be upgraded
  120. This method checks if the binary packages produced by the source
  121. package on the given architecture should be upgraded; this can
  122. happen also if the migration is a binary-NMU for the given arch.
  123. It returns False if the given packages don't need to be upgraded,
  124. True otherwise. In the former case, a new excuse is appended to
  125. the object attribute excuses.
  126. """
  127. # retrieve the source packages for testing and suite
  128. target_suite = self.suite_info.target_suite
  129. source_suite = item.suite
  130. src = item.package
  131. arch = item.architecture
  132. source_t = target_suite.sources[src]
  133. source_u = source_suite.sources[src]
  134. excuse = Excuse(item.name)
  135. excuse.set_vers(source_t.version, source_t.version)
  136. source_u.maintainer and excuse.set_maint(source_u.maintainer)
  137. source_u.section and excuse.set_section(source_u.section)
  138. # if there is a `remove' hint and the requested version is the same as the
  139. # version in testing, then stop here and return False
  140. # (as a side effect, a removal may generate such excuses for both the source
  141. # package and its binary packages on each architecture)
  142. for hint in self.hints.search('remove', package=src, version=source_t.version):
  143. excuse.add_hint(hint)
  144. excuse.addhtml("Removal request by %s" % (hint.user))
  145. excuse.addhtml("Trying to remove package, not update it")
  146. self.excuses[excuse.name] = excuse
  147. return False
  148. # the starting point is that there is nothing wrong and nothing worth doing
  149. anywrongver = False
  150. anyworthdoing = False
  151. packages_t_a = target_suite.binaries[arch]
  152. packages_s_a = source_suite.binaries[arch]
  153. # for every binary package produced by this source in unstable for this architecture
  154. for pkg_id in sorted(x for x in source_u.binaries if x.architecture == arch):
  155. pkg_name = pkg_id.package_name
  156. # retrieve the testing (if present) and unstable corresponding binary packages
  157. binary_t = packages_t_a[pkg_name] if pkg_name in packages_t_a else None
  158. binary_u = packages_s_a[pkg_name]
  159. # this is the source version for the new binary package
  160. pkgsv = binary_u.source_version
  161. # if the new binary package is architecture-independent, then skip it
  162. if binary_u.architecture == 'all':
  163. if pkg_id not in source_t.binaries:
  164. # only add a note if the arch:all does not match the expected version
  165. excuse.addhtml("Ignoring %s %s (from %s) as it is arch: all" % (pkg_name, binary_u.version, pkgsv))
  166. continue
  167. # if the new binary package is not from the same source as the testing one, then skip it
  168. # this implies that this binary migration is part of a source migration
  169. if source_u.version == pkgsv and source_t.version != pkgsv:
  170. anywrongver = True
  171. excuse.addhtml("From wrong source: %s %s (%s not %s)" % (pkg_name, binary_u.version, pkgsv,
  172. source_t.version))
  173. continue
  174. # cruft in unstable
  175. if source_u.version != pkgsv and source_t.version != pkgsv:
  176. if self.options.ignore_cruft:
  177. excuse.addhtml("Old cruft: %s %s (but ignoring cruft, so nevermind)" % (pkg_name, pkgsv))
  178. else:
  179. anywrongver = True
  180. excuse.addhtml("Old cruft: %s %s" % (pkg_name, pkgsv))
  181. continue
  182. # if the source package has been updated in unstable and this is a binary migration, skip it
  183. # (the binaries are now out-of-date)
  184. if source_t.version == pkgsv and source_t.version != source_u.version:
  185. anywrongver = True
  186. excuse.addhtml("From wrong source: %s %s (%s not %s)" % (pkg_name, binary_u.version, pkgsv,
  187. source_u.version))
  188. continue
  189. # find unsatisfied dependencies for the new binary package
  190. self._excuse_unsat_deps(pkg_name, src, arch, source_suite, excuse)
  191. # if the binary is not present in testing, then it is a new binary;
  192. # in this case, there is something worth doing
  193. if not binary_t:
  194. excuse.addhtml("New binary: %s (%s)" % (pkg_name, binary_u.version))
  195. anyworthdoing = True
  196. continue
  197. # at this point, the binary package is present in testing, so we can compare
  198. # the versions of the packages ...
  199. vcompare = apt_pkg.version_compare(binary_t.version, binary_u.version)
  200. # ... if updating would mean downgrading, then stop here: there is something wrong
  201. if vcompare > 0:
  202. anywrongver = True
  203. excuse.addhtml("Not downgrading: %s (%s to %s)" % (pkg_name, binary_t.version, binary_u.version))
  204. break
  205. # ... if updating would mean upgrading, then there is something worth doing
  206. elif vcompare < 0:
  207. excuse.addhtml("Updated binary: %s (%s to %s)" % (pkg_name, binary_t.version, binary_u.version))
  208. anyworthdoing = True
  209. srcv = source_u.version
  210. same_source = source_t.version == srcv
  211. primary_source_suite = self.suite_info.primary_source_suite
  212. is_primary_source = source_suite == primary_source_suite
  213. # if there is nothing wrong and there is something worth doing or the source
  214. # package is not fake, then check what packages should be removed
  215. if not anywrongver and (anyworthdoing or not source_u.is_fakesrc):
  216. # we want to remove binaries that are no longer produced by the
  217. # new source, but there are some special cases:
  218. # - if this is binary-only (same_source) and not from the primary
  219. # source, we don't do any removals:
  220. # binNMUs in *pu on some architectures would otherwise result in
  221. # the removal of binaries on other architectures
  222. # - for the primary source, smooth binaries in the target suite
  223. # are not considered for removal
  224. if not same_source or is_primary_source:
  225. smoothbins = set()
  226. if is_primary_source:
  227. binaries_t = target_suite.binaries
  228. possible_smooth_updates = [p for p in source_t.binaries if p.architecture == arch]
  229. smoothbins = find_smooth_updateable_binaries(possible_smooth_updates,
  230. source_u,
  231. self.pkg_universe,
  232. target_suite,
  233. binaries_t,
  234. source_suite.binaries,
  235. frozenset(),
  236. self.options.smooth_updates)
  237. # for every binary package produced by this source in testing for this architecture
  238. for pkg_id in sorted(x for x in source_t.binaries if x.architecture == arch):
  239. pkg = pkg_id.package_name
  240. # if the package is architecture-independent, then ignore it
  241. tpkg_data = packages_t_a[pkg]
  242. if tpkg_data.architecture == 'all':
  243. if pkg_id not in source_u.binaries:
  244. # only add a note if the arch:all does not match the expected version
  245. excuse.addhtml("Ignoring removal of %s as it is arch: all" % (pkg))
  246. continue
  247. # if the package is not produced by the new source package, then remove it from testing
  248. if pkg not in packages_s_a:
  249. excuse.addhtml("Removed binary: %s %s" % (pkg, tpkg_data.version))
  250. # the removed binary is only interesting if this is a binary-only migration,
  251. # as otherwise the updated source will already cause the binary packages
  252. # to be updated
  253. if same_source and pkg_id not in smoothbins:
  254. # Special-case, if the binary is a candidate for a smooth update, we do not consider
  255. # it "interesting" on its own. This case happens quite often with smooth updatable
  256. # packages, where the old binary "survives" a full run because it still has
  257. # reverse dependencies.
  258. anyworthdoing = True
  259. if not anyworthdoing:
  260. # nothing worth doing, we don't add an excuse to the list, we just return false
  261. return False
  262. # there is something worth doing
  263. # we assume that this package will be ok, if not invalidated below
  264. excuse.policy_verdict = PolicyVerdict.PASS
  265. # if there is something something wrong, reject this package
  266. if anywrongver:
  267. excuse.policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
  268. self._policy_engine.apply_srcarch_policies(item, arch, source_t, source_u, excuse)
  269. self.excuses[excuse.name] = excuse
  270. return excuse.is_valid
  271. def _should_upgrade_src(self, item):
  272. """Check if source package should be upgraded
  273. This method checks if a source package should be upgraded. The analysis
  274. is performed for the source package specified by the `src' parameter,
  275. for the distribution `source_suite'.
  276. It returns False if the given package doesn't need to be upgraded,
  277. True otherwise. In the former case, a new excuse is appended to
  278. the object attribute excuses.
  279. """
  280. src = item.package
  281. source_suite = item.suite
  282. suite_name = source_suite.name
  283. source_u = source_suite.sources[src]
  284. if source_u.is_fakesrc:
  285. # it is a fake package created to satisfy Britney implementation details; silently ignore it
  286. return False
  287. target_suite = self.suite_info.target_suite
  288. # retrieve the source packages for testing (if available) and suite
  289. if src in target_suite.sources:
  290. source_t = target_suite.sources[src]
  291. # if testing and unstable have the same version, then this is a candidate for binary-NMUs only
  292. if apt_pkg.version_compare(source_t.version, source_u.version) == 0:
  293. return False
  294. else:
  295. source_t = None
  296. excuse = Excuse(item.name)
  297. excuse.set_vers(source_t and source_t.version or None, source_u.version)
  298. source_u.maintainer and excuse.set_maint(source_u.maintainer)
  299. source_u.section and excuse.set_section(source_u.section)
  300. # if the version in unstable is older, then stop here with a warning in the excuse and return False
  301. if source_t and apt_pkg.version_compare(source_u.version, source_t.version) < 0:
  302. excuse.addhtml("ALERT: %s is newer in the target suite (%s %s)" % (src, source_t.version, source_u.version))
  303. self.excuses[excuse.name] = excuse
  304. excuse.addreason("newerintesting")
  305. return False
  306. # the starting point is that we will update the candidate
  307. excuse.policy_verdict = PolicyVerdict.PASS
  308. # if there is a `remove' hint and the requested version is the same as the
  309. # version in testing, then stop here and return False
  310. for hint in self.hints.search('remove', package=src):
  311. if source_t and source_t.version == hint.version or \
  312. source_u.version == hint.version:
  313. excuse.add_hint(hint)
  314. excuse.addhtml("Removal request by %s" % (hint.user))
  315. excuse.addhtml("Trying to remove package, not update it")
  316. excuse.policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
  317. break
  318. all_binaries = self.all_binaries
  319. for pkg_id in sorted(source_u.binaries):
  320. is_valid = self._excuse_unsat_deps(pkg_id.package_name, src, pkg_id.architecture, source_suite, excuse)
  321. if is_valid:
  322. continue
  323. binary_u = all_binaries[pkg_id]
  324. # There is an issue with the package. If it is arch:any, then _excuse_unsat_deps will have
  325. # handled everything for us correctly. However, arch:all have some special-casing IRT
  326. # nobreakall that we deal with ourselves here.
  327. if binary_u.architecture == 'all' and pkg_id.architecture in self.options.nobreakall_arches:
  328. # We sometimes forgive uninstallable arch:all packages on nobreakall architectures
  329. # (e.g. we sometimes force-hint in arch:all packages that are only installable on
  330. # on a subset of all nobreak architectures).
  331. # This forgivness is only done if the package is already in testing AND it is broken
  332. # in testing on this architecture already. Anything else would be a regression
  333. if target_suite.is_pkg_in_the_suite(pkg_id) and not target_suite.is_installable(pkg_id):
  334. # It is a regression.
  335. excuse.policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
  336. # at this point, we check the status of the builds on all the supported architectures
  337. # to catch the out-of-date ones
  338. archs_to_consider = list(self.options.architectures)
  339. archs_to_consider.append('all')
  340. for arch in archs_to_consider:
  341. oodbins = {}
  342. uptodatebins = False
  343. # for every binary package produced by this source in the suite for this architecture
  344. if arch == 'all':
  345. consider_binaries = source_u.binaries
  346. else:
  347. # Will also include arch:all for the given architecture (they are filtered out
  348. # below)
  349. consider_binaries = sorted(x for x in source_u.binaries if x.architecture == arch)
  350. for pkg_id in consider_binaries:
  351. pkg = pkg_id.package_name
  352. # retrieve the binary package and its source version
  353. binary_u = all_binaries[pkg_id]
  354. pkgsv = binary_u.source_version
  355. # arch:all packages are treated separately from arch:arch
  356. if binary_u.architecture != arch:
  357. continue
  358. # if it wasn't built by the same source, it is out-of-date
  359. # if there is at least one binary on this arch which is
  360. # up-to-date, there is a build on this arch
  361. if source_u.version != pkgsv:
  362. if pkgsv not in oodbins:
  363. oodbins[pkgsv] = set()
  364. oodbins[pkgsv].add(pkg)
  365. excuse.add_old_binary(pkg, pkgsv)
  366. continue
  367. else:
  368. uptodatebins = True
  369. # if there are out-of-date packages, warn about them in the excuse and set excuse.is_valid
  370. # to False to block the update; if the architecture where the package is out-of-date is
  371. # in the `outofsync_arches' list, then do not block the update
  372. if oodbins:
  373. oodtxt = ""
  374. for v in sorted(oodbins):
  375. if oodtxt:
  376. oodtxt = oodtxt + "; "
  377. oodtxt = oodtxt + "%s (from <a href=\"https://buildd.debian.org/status/logs.php?" \
  378. "arch=%s&pkg=%s&ver=%s\" target=\"_blank\">%s</a>)" % \
  379. (", ".join(sorted(oodbins[v])), quote(arch), quote(src), quote(v), v)
  380. if uptodatebins:
  381. text = "old binaries left on <a href=\"https://buildd.debian.org/status/logs.php?" \
  382. "arch=%s&pkg=%s&ver=%s\" target=\"_blank\">%s</a>: %s" % \
  383. (quote(arch), quote(src), quote(source_u.version), arch, oodtxt)
  384. else:
  385. text = "missing build on <a href=\"https://buildd.debian.org/status/logs.php?" \
  386. "arch=%s&pkg=%s&ver=%s\" target=\"_blank\">%s</a>: %s" % \
  387. (quote(arch), quote(src), quote(source_u.version), arch, oodtxt)
  388. if arch in self.options.outofsync_arches:
  389. text = text + " (but %s isn't keeping up, so nevermind)" % (arch)
  390. if not uptodatebins:
  391. excuse.missing_build_on_ood_arch(arch)
  392. else:
  393. if uptodatebins:
  394. if self.options.ignore_cruft:
  395. text = text + " (but ignoring cruft, so nevermind)"
  396. else:
  397. excuse.policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
  398. else:
  399. excuse.policy_verdict = PolicyVerdict.REJECTED_CANNOT_DETERMINE_IF_PERMANENT
  400. excuse.missing_build_on_arch(arch)
  401. excuse.addhtml(text)
  402. # if the source package has no binaries, set is_valid to False to block the update
  403. if not source_u.binaries:
  404. excuse.addhtml("%s has no binaries on any arch" % src)
  405. excuse.addreason("no-binaries")
  406. excuse.policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
  407. self._policy_engine.apply_src_policies(item, source_t, source_u, excuse)
  408. if source_suite.suite_class.is_additional_source and source_t:
  409. # o-o-d(ish) checks for (t-)p-u
  410. # This only makes sense if the package is actually in testing.
  411. for arch in self.options.architectures:
  412. # if the package in testing has no binaries on this
  413. # architecture, it can't be out-of-date
  414. if not any(x for x in source_t.binaries
  415. if x.architecture == arch and all_binaries[x].architecture != 'all'):
  416. continue
  417. # if the (t-)p-u package has produced any binaries on
  418. # this architecture then we assume it's ok. this allows for
  419. # uploads to (t-)p-u which intentionally drop binary
  420. # packages
  421. if any(x for x in source_suite.binaries[arch].values()
  422. if x.source == src and x.source_version == source_u.version and x.architecture != 'all'):
  423. continue
  424. # TODO: Find a way to avoid hardcoding pu/stable relation.
  425. if suite_name == 'pu':
  426. base = 'stable'
  427. else:
  428. base = target_suite.name
  429. text = "Not yet built on <a href=\"https://buildd.debian.org/status/logs.php?arch=%s&pkg=%s&ver=%s&suite=%s\" target=\"_blank\">%s</a> (relative to target suite)" % (quote(arch), quote(src), quote(source_u.version), base, arch)
  430. if arch in self.options.outofsync_arches:
  431. text = text + " (but %s isn't keeping up, so never mind)" % (arch)
  432. excuse.missing_build_on_ood_arch(arch)
  433. else:
  434. excuse.policy_verdict = PolicyVerdict.REJECTED_CANNOT_DETERMINE_IF_PERMANENT
  435. excuse.missing_build_on_arch(arch)
  436. excuse.addhtml(text)
  437. # check if there is a `force' hint for this package, which allows it to go in even if it is not updateable
  438. forces = self.hints.search('force', package=src, version=source_u.version)
  439. if forces:
  440. # force() updates the final verdict for us
  441. changed_state = excuse.force()
  442. if changed_state:
  443. excuse.addhtml("Should ignore, but forced by %s" % (forces[0].user))
  444. self.excuses[excuse.name] = excuse
  445. return excuse.is_valid
  446. def _compute_excuses_and_initial_actionable_items(self):
  447. # list of local methods and variables (for better performance)
  448. excuses = self.excuses
  449. suite_info = self.suite_info
  450. pri_source_suite = suite_info.primary_source_suite
  451. architectures = self.options.architectures
  452. should_remove_source = self._should_remove_source
  453. should_upgrade_srcarch = self._should_upgrade_srcarch
  454. should_upgrade_src = self._should_upgrade_src
  455. mi_factory = self._migration_item_factory
  456. sources_ps = pri_source_suite.sources
  457. sources_t = suite_info.target_suite.sources
  458. # this set will contain the packages which are valid candidates;
  459. # if a package is going to be removed, it will have a "-" prefix
  460. actionable_items = set()
  461. actionable_items_add = actionable_items.add # Every . in a loop slows it down
  462. # for every source package in testing, check if it should be removed
  463. for pkg in sources_t:
  464. if pkg not in sources_ps:
  465. item = mi_factory.parse_item("-" + pkg, versioned=False, auto_correct=False)
  466. if should_remove_source(item):
  467. actionable_items_add(item.name)
  468. # for every source package in the source suites, check if it should be upgraded
  469. for suite in chain((pri_source_suite, *suite_info.additional_source_suites)):
  470. sources_s = suite.sources
  471. item_suffix = "_%s" % suite.excuses_suffix if suite.excuses_suffix else ''
  472. for pkg in sources_s:
  473. src_s_data = sources_s[pkg]
  474. if src_s_data.is_fakesrc:
  475. continue
  476. src_t_data = sources_t.get(pkg)
  477. if src_t_data is None or apt_pkg.version_compare(src_s_data.version, src_t_data.version) != 0:
  478. item = mi_factory.parse_item("%s%s" % (pkg, item_suffix), versioned=False, auto_correct=False)
  479. # check if the source package should be upgraded
  480. if should_upgrade_src(item):
  481. actionable_items_add(item.name)
  482. else:
  483. # package has same version in source and target suite; check if any of the
  484. # binaries have changed on the various architectures
  485. for arch in architectures:
  486. item = mi_factory.parse_item("%s/%s%s" % (pkg, arch, item_suffix),
  487. versioned=False, auto_correct=False)
  488. if should_upgrade_srcarch(item):
  489. actionable_items_add(item.name)
  490. # process the `remove' hints, if the given package is not yet in actionable_items
  491. for hint in self.hints['remove']:
  492. src = hint.package
  493. if src not in sources_t or src in actionable_items or ("-" + src in actionable_items):
  494. continue
  495. # check if the version specified in the hint is the same as the considered package
  496. tsrcv = sources_t[src].version
  497. if tsrcv != hint.version:
  498. continue
  499. # add the removal of the package to actionable_items and build a new excuse
  500. excuse = Excuse("-%s" % (src))
  501. excuse.set_vers(tsrcv, None)
  502. excuse.addhtml("Removal request by %s" % (hint.user))
  503. # if the removal of the package is blocked, skip it
  504. blocked = False
  505. for blockhint in self.hints.search('block', package=src, removal=True):
  506. excuse.addhtml("Not removing package, due to block hint by %s "
  507. "(contact debian-release if update is needed)" % blockhint.user)
  508. excuse.addreason("block")
  509. blocked = True
  510. if blocked:
  511. excuses[excuse.name] = excuse
  512. continue
  513. actionable_items_add("-%s" % (src))
  514. excuse.addhtml("Package is broken, will try to remove")
  515. excuse.add_hint(hint)
  516. # Using "PASS" here as "Created by a hint" != "accepted due to hint". In a future
  517. # where there might be policy checks on removals, it would make sense to distinguish
  518. # those two states. Not sure that future will ever be.
  519. excuse.policy_verdict = PolicyVerdict.PASS
  520. excuses[excuse.name] = excuse
  521. return actionable_items
  522. def find_actionable_excuses(self):
  523. excuses = self.excuses
  524. actionable_items = self._compute_excuses_and_initial_actionable_items()
  525. # extract the not considered packages, which are in the excuses but not in upgrade_me
  526. unconsidered = {ename for ename in excuses if ename not in actionable_items}
  527. # invalidate impossible excuses
  528. for e in excuses.values():
  529. # parts[0] == package name
  530. # parts[1] == optional architecture
  531. parts = e.name.split('/')
  532. for d in sorted(e.all_deps):
  533. for deptype in e.all_deps[d]:
  534. ok = False
  535. # source -> source dependency; both packages must have
  536. # valid excuses
  537. if d in actionable_items or d in unconsidered:
  538. ok = True
  539. # if the excuse is for a binNMU, also consider d/$arch as a
  540. # valid excuse
  541. elif len(parts) == 2:
  542. bd = '%s/%s' % (d, parts[1])
  543. if bd in actionable_items or bd in unconsidered:
  544. ok = True
  545. # if the excuse is for a source package, check each of the
  546. # architectures on which the excuse lists a dependency on d,
  547. # and consider the excuse valid if it is possible on each
  548. # architecture
  549. else:
  550. arch_ok = True
  551. for arch in e.all_deps[d][deptype]:
  552. bd = '%s/%s' % (d, arch)
  553. if bd not in actionable_items and bd not in unconsidered:
  554. arch_ok = False
  555. break
  556. if arch_ok:
  557. ok = True
  558. if not ok:
  559. e.addhtml("Impossible %s: %s -> %s" % (deptype, e.name, d))
  560. e.addreason(deptype.get_reason())
  561. invalidate_excuses(excuses, actionable_items, unconsidered)
  562. mi_factory = self._migration_item_factory
  563. actionable_items = {mi_factory.parse_item(x, versioned=False, auto_correct=False) for x in actionable_items}
  564. return excuses, actionable_items