diff --git a/britney.py b/britney.py index c7f65e6..c454d9f 100755 --- a/britney.py +++ b/britney.py @@ -193,7 +193,7 @@ from urllib.parse import quote import apt_pkg -from britney2 import SourcePackage, BinaryPackageId, BinaryPackage +from britney2 import SourcePackage, BinaryPackageId, BinaryPackage, DependencyType from britney2.excuse import Excuse from britney2.hints import HintParser from britney2.inputs.suiteloader import DebMirrorLikeSuiteContentLoader, MissingRequiredConfigurationError @@ -806,9 +806,9 @@ class Britney(object): sources_s = source_suite.sources for p in packages: if p in sources_t and sources_t[p].version == sources_s[p].version: - excuse.add_dep("%s/%s" % (p, arch), arch) + excuse.add_dependency(DependencyType.DEPENDS, "%s/%s" % (p, arch), arch) else: - excuse.add_dep(p, arch) + excuse.add_dependency(DependencyType.DEPENDS, p, arch) else: for p in packages: excuse.add_break_dep(p, arch) @@ -1410,34 +1410,35 @@ class Britney(object): # parts[0] == package name # parts[1] == optional architecture parts = e.name.split('/') - for d in e.deps: - ok = False - # source -> source dependency; both packages must have - # valid excuses - if d in upgrade_me or d in unconsidered: - ok = True - # if the excuse is for a binNMU, also consider d/$arch as a - # valid excuse - elif len(parts) == 2: - bd = '%s/%s' % (d, parts[1]) - if bd in upgrade_me or bd in unconsidered: + for d in e.all_deps: + for deptype in e.all_deps[d]: + ok = False + # source -> source dependency; both packages must have + # valid excuses + if d in upgrade_me or d in unconsidered: ok = True - # if the excuse is for a source package, check each of the - # architectures on which the excuse lists a dependency on d, - # and consider the excuse valid if it is possible on each - # architecture - else: - arch_ok = True - for arch in e.deps[d]: - bd = '%s/%s' % (d, arch) - if bd not in upgrade_me and bd not in unconsidered: - arch_ok = False - break - if arch_ok: - ok = True - if not ok: - e.addhtml("Impossible dependency: %s -> %s" % (e.name, d)) - e.addreason("depends") + # if the excuse is for a binNMU, also consider d/$arch as a + # valid excuse + elif len(parts) == 2: + bd = '%s/%s' % (d, parts[1]) + if bd in upgrade_me or bd in unconsidered: + ok = True + # if the excuse is for a source package, check each of the + # architectures on which the excuse lists a dependency on d, + # and consider the excuse valid if it is possible on each + # architecture + else: + arch_ok = True + for arch in e.all_deps[d][deptype]: + bd = '%s/%s' % (d, arch) + if bd not in upgrade_me and bd not in unconsidered: + arch_ok = False + break + if arch_ok: + ok = True + if not ok: + e.addhtml("Impossible %s: %s -> %s" % (deptype, e.name, d)) + e.addreason(deptype.get_reason()) invalidate_excuses(excuses, upgrade_me, unconsidered) # sort the list of candidates @@ -2048,7 +2049,7 @@ class Britney(object): # consider only excuses which are valid candidates and still relevant. valid_excuses = frozenset(y.uvname for y in upgrade_me if y not in sources_t or sources_t[y].version != excuses[y].ver[1]) - excuses_deps = {name: valid_excuses.intersection(excuse.deps) + excuses_deps = {name: valid_excuses.intersection(excuse.get_deps()) for name, excuse in excuses.items() if name in valid_excuses} excuses_rdeps = defaultdict(set) for name, deps in excuses_deps.items(): @@ -2059,7 +2060,7 @@ class Britney(object): excuse = excuses[e] if not circular_first: hint[e] = excuse.ver[1] - if not excuse.deps: + if not excuse.get_deps(): return hint for p in excuses_deps[e]: if p in hint or p not in valid_excuses: @@ -2074,7 +2075,7 @@ class Britney(object): seen_hints = set() for e in valid_excuses: excuse = excuses[e] - if excuse.deps: + if excuse.get_deps(): hint = find_related(e, {}, True) if isinstance(hint, dict) and e in hint: h = frozenset(hint.items()) diff --git a/britney2/__init__.py b/britney2/__init__.py index 5e68ce0..46fbef1 100644 --- a/britney2/__init__.py +++ b/britney2/__init__.py @@ -1,6 +1,21 @@ from collections import namedtuple from enum import Enum, unique +class DependencyType(Enum): + DEPENDS = ('Depends', 'depends', 'dependency') + # BUILD_DEPENDS includes BUILD_DEPENDS_ARCH + BUILD_DEPENDS = ('Build-Depends(-Arch)', 'build-depends', 'build-dependency') + BUILD_DEPENDS_INDEP = ('Build-Depends-Indep', 'build-depends-indep', 'build-dependency (indep)') + + def __str__(self): + return self.value[0] + + def get_reason(self): + return self.value[1] + + def get_description(self): + return self.value[2] + @unique class SuiteClass(Enum): diff --git a/britney2/excuse.py b/britney2/excuse.py index 5487870..1617e3e 100644 --- a/britney2/excuse.py +++ b/britney2/excuse.py @@ -17,6 +17,7 @@ from collections import defaultdict import re +from britney2 import DependencyType from britney2.policies.policy import PolicyVerdict VERDICT2DESC = { @@ -74,11 +75,8 @@ class Excuse(object): self.forced = False self._policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY - self.invalid_deps = set() - self.invalid_build_deps = set() - self.deps = {} - self.arch_build_deps = {} - self.indep_build_deps = {} + self.all_invalid_deps = set() + self.all_deps = {} self.sane_deps = [] self.break_deps = [] self.unsatisfiable_on_archs = [] @@ -133,11 +131,24 @@ class Excuse(object): """Set the section of the package""" self.section = section - def add_dep(self, name, arch): - """Add a dependency""" - if name not in self.deps: - self.deps[name]=[] - self.deps[name].append(arch) + def add_dependency(self, deptype, name, arch): + """Add a dependency of type deptype """ + if name not in self.all_deps: + self.all_deps[name]={} + if deptype not in self.all_deps[name]: + self.all_deps[name][deptype]=[] + self.all_deps[name][deptype].append(arch) + + def get_deps(self): + # the autohinter uses the excuses data to query dependencies between + # excuses. For now, we keep the current behaviour by just returning + # the data that was in the old deps set + """ Get the dependencies of type DEPENDS """ + deps = set() + for dep in self.all_deps: + if DependencyType.DEPENDS in self.all_deps[dep]: + deps.add(dep) + return deps def add_sane_dep(self, name): """Add a sane dependency""" @@ -153,27 +164,13 @@ class Excuse(object): if arch not in self.unsatisfiable_on_archs: self.unsatisfiable_on_archs.append(arch) - def add_arch_build_dep(self, name, arch): - if name not in self.arch_build_deps: - self.arch_build_deps[name] = [] - self.arch_build_deps[name].append(arch) - - def add_indep_build_dep(self, name, arch): - if name not in self.indep_build_deps: - self.indep_build_deps[name] = [] - self.indep_build_deps[name].append(arch) - def add_unsatisfiable_dep(self, signature, arch): """Add an unsatisfiable dependency""" self.unsat_deps[arch].add(signature) - def invalidate_dep(self, name): + def invalidate_dependency(self, name): """Invalidate dependency""" - self.invalid_deps.add(name) - - def invalidate_build_dep(self, name): - """Invalidate build-dependency""" - self.invalid_build_deps.add(name) + self.all_invalid_deps.add(name) def setdaysold(self, daysold, mindays): """Set the number of days from the upload and the minimum number of days for the update""" @@ -213,18 +210,23 @@ class Excuse(object): return VERDICT2DESC[verdict] return "UNKNOWN: Missing description for {0} - Please file a bug against Britney".format(verdict.name) - def _render_dep_issue(self, dep_issues, invalid_deps, field): + def _render_dep_issues(self, dep_issues, invalid_deps): lastdep = "" res = [] for x in sorted(dep_issues, key=lambda x: x.split('/')[0]): dep = x.split('/')[0] - if dep == lastdep: - continue + if dep != lastdep: + seen = {} lastdep = dep - if x in invalid_deps: - res.append("
  • %s: %s %s (not considered)\n" % (field, self.name, dep, dep)) - else: - res.append("
  • %s: %s %s\n" % (field, self.name, dep, dep)) + for deptype in sorted(dep_issues[x], key=lambda y: str(y)): + field = deptype + if deptype in seen: + continue + seen[deptype] = True + if x in invalid_deps: + res.append("
  • %s: %s %s (not considered)\n" % (field, self.name, dep, dep)) + else: + res.append("
  • %s: %s %s\n" % (field, self.name, dep, dep)) return "".join(res) @@ -248,15 +250,12 @@ class Excuse(object): (self.daysold, self.mindays)) for x in self.htmlline: res = res + "
  • " + x + "\n" - res += self._render_dep_issue(self.deps, self.invalid_deps, 'Depends') + res += self._render_dep_issues(self.all_deps, self.all_invalid_deps) for (n, a) in self.break_deps: - if n not in self.deps: + if n not in self.all_deps: res += "
  • Ignoring %s depends: %s\n" % (a, n, n) - res += self._render_dep_issue(self.arch_build_deps, self.invalid_build_deps, 'Build-Depends(-Arch)') - res += self._render_dep_issue(self.indep_build_deps, self.invalid_build_deps, 'Build-Depends-Indep') - res = res + "\n" return res @@ -304,17 +303,16 @@ class Excuse(object): 'on-architectures': sorted(self.missing_builds), 'on-unimportant-architectures': sorted(self.missing_builds_ood_arch), } - if self.invalid_deps or self.invalid_build_deps: + if self.all_invalid_deps: excusedata['invalidated-by-other-package'] = True - if self.deps or self.invalid_deps or self.arch_build_deps or self.indep_build_deps \ - or self.invalid_build_deps or self.break_deps or self.unsat_deps: + if self.all_deps or self.all_invalid_deps \ + or self.break_deps or self.unsat_deps: excusedata['dependencies'] = dep_data = {} - migrate_after_bd = (self.arch_build_deps.keys() | self.indep_build_deps.keys()) - self.invalid_build_deps - migrate_after = sorted((self.deps.keys() - self.invalid_deps) | migrate_after_bd) - break_deps = [x for x, _ in self.break_deps if x not in self.deps] + migrate_after = sorted(self.all_deps.keys() - self.all_invalid_deps) + break_deps = [x for x, _ in self.break_deps if x not in self.all_deps] - if self.invalid_deps or self.invalid_build_deps: - dep_data['blocked-by'] = sorted(self.invalid_deps | self.invalid_build_deps) + if self.all_invalid_deps: + dep_data['blocked-by'] = sorted(self.all_invalid_deps) if migrate_after: dep_data['migrate-after'] = migrate_after if break_deps: diff --git a/britney2/policies/policy.py b/britney2/policies/policy.py index 98258c5..d27e627 100644 --- a/britney2/policies/policy.py +++ b/britney2/policies/policy.py @@ -11,6 +11,7 @@ from britney2 import SuiteClass from britney2.hints import Hint, split_into_one_hint_per_package from britney2.policies import PolicyVerdict from britney2.utils import get_dependency_solvers +from britney2 import DependencyType class BasePolicy(object): @@ -746,9 +747,9 @@ class BuildDependsPolicy(BasePolicy): for p in packages: if arch not in self.options.break_arches: if p in sources_t and sources_t[p].version == sources_s[p].version: - excuse.add_arch_build_dep("%s/%s" % (p, arch), arch) + excuse.add_dependency(DependencyType.BUILD_DEPENDS,"%s/%s" % (p, arch), arch) else: - excuse.add_arch_build_dep(p, arch) + excuse.add_dependency(DependencyType.BUILD_DEPENDS, p, arch) if unsat_bd: build_deps_info['unsatisfiable-arch-build-depends'] = unsat_bd diff --git a/britney2/utils.py b/britney2/utils.py index aad7d13..b87c0c7 100644 --- a/britney2/utils.py +++ b/britney2/utils.py @@ -776,21 +776,18 @@ def invalidate_excuses(excuses, valid, invalid): """ # build the reverse dependencies - revdeps = defaultdict(list) - revbuilddeps = defaultdict(list) - revindepbuilddeps = defaultdict(list) + allrevdeps = defaultdict(dict) for exc in excuses.values(): - for d in exc.deps: - revdeps[d].append(exc.name) - for d in exc.arch_build_deps: - revbuilddeps[d].append(exc.name) - for d in exc.indep_build_deps: - revindepbuilddeps[d].append(exc.name) + for d in exc.all_deps: + if exc.name not in allrevdeps[d]: + allrevdeps[d][exc.name] = set() + for deptype in exc.all_deps[d]: + allrevdeps[d][exc.name].add(deptype) # loop on the invalid excuses for ename in iter_except(invalid.pop, KeyError): # if there is no reverse dependency, skip the item - if ename not in revdeps and ename not in revbuilddeps: + if ename not in allrevdeps: continue # if the dependency can be satisfied by a testing-proposed-updates excuse, skip the item if (ename + "_tpu") in valid: @@ -801,46 +798,19 @@ def invalidate_excuses(excuses, valid, invalid): rdep_verdict = PolicyVerdict.REJECTED_BLOCKED_BY_ANOTHER_ITEM # loop on the reverse dependencies - if ename in revdeps: - for x in revdeps[ename]: + if ename in allrevdeps: + for x in allrevdeps[ename]: # if the item is valid and it is not marked as `forced', then we invalidate it if x in valid and not excuses[x].forced: # otherwise, invalidate the dependency and mark as invalidated and # remove the depending excuses - excuses[x].invalidate_dep(ename) + excuses[x].invalidate_dependency(ename) valid.discard(x) invalid.add(x) - excuses[x].addhtml("Invalidated by dependency") - excuses[x].addreason("depends") - if excuses[x].policy_verdict.value < rdep_verdict.value: - excuses[x].policy_verdict = rdep_verdict - - if ename in revbuilddeps: - for x in revbuilddeps[ename]: - # if the item is valid and it is not marked as `forced', then we invalidate it - if x in valid and not excuses[x].forced: - - # otherwise, invalidate the dependency and mark as invalidated and - # remove the depending excuses - excuses[x].invalidate_build_dep(ename) - valid.discard(x) - invalid.add(x) - excuses[x].addhtml("Invalidated by build-dependency") - if excuses[x].policy_verdict.value < rdep_verdict.value: - excuses[x].policy_verdict = rdep_verdict - - if ename in revindepbuilddeps: - for x in revindepbuilddeps[ename]: - # if the item is valid and it is not marked as `forced', then we invalidate it - if x in valid and not excuses[x].forced: - - # otherwise, invalidate the dependency and mark as invalidated and - # remove the depending excuses - excuses[x].invalidate_build_dep(ename) - valid.discard(x) - invalid.add(x) - excuses[x].addhtml("Invalidated by build-dependency (indep)") + for deptype in allrevdeps[ename][x]: + excuses[x].addhtml("Invalidated by %s" % deptype.get_description()) + excuses[x].addreason(deptype.get_reason()) if excuses[x].policy_verdict.value < rdep_verdict.value: excuses[x].policy_verdict = rdep_verdict