Add BuildDependsPolicy to check Build-Depends(-Arch) availability

Add a new "BuildDependsPolicy" that will check the satisfiability of
the build-dependencies listed in the Build-Depends and
Build-Depends-Arch fields.  This enables gating of packages based on
missing / broken build-dependencies.

There are some limitations:

 * Build-Depends-Indep is ignored for now.  Missing or broken packages
   listed in Build-Depends-Indep will be continue to be silently
   ignored.

 * Being a policy check, it does not enforce "self-containedness" as
   a package can still migrate before a build-dependency.  However,
   this can only happen if the build-dependency is ready to migrate
   itself.  If the build-dependency is not ready (e.g. new RC bugs),
   then packages build-depending on it cannot migrate either (unless
   the version in testing satisfies there requirements).

Signed-off-by: Niels Thykier <niels@thykier.net>
ubuntu/rebased
Niels Thykier 7 years ago
parent c537f0554f
commit ee27d7a67c

@ -198,7 +198,7 @@ from britney2.hints import HintParser
from britney2.installability.builder import build_installability_tester from britney2.installability.builder import build_installability_tester
from britney2.migrationitem import MigrationItem from britney2.migrationitem import MigrationItem
from britney2.policies import PolicyVerdict from britney2.policies import PolicyVerdict
from britney2.policies.policy import AgePolicy, RCBugPolicy, PiupartsPolicy from britney2.policies.policy import AgePolicy, RCBugPolicy, PiupartsPolicy, BuildDependsPolicy
from britney2.utils import (old_libraries_format, undo_changes, from britney2.utils import (old_libraries_format, undo_changes,
compute_reverse_tree, possibly_compressed, compute_reverse_tree, possibly_compressed,
read_nuninst, write_nuninst, write_heidi, read_nuninst, write_nuninst, write_heidi,
@ -511,6 +511,7 @@ class Britney(object):
self.policies.append(AgePolicy(self.options, self.suite_info, MINDAYS)) self.policies.append(AgePolicy(self.options, self.suite_info, MINDAYS))
self.policies.append(RCBugPolicy(self.options, self.suite_info)) self.policies.append(RCBugPolicy(self.options, self.suite_info))
self.policies.append(PiupartsPolicy(self.options, self.suite_info)) self.policies.append(PiupartsPolicy(self.options, self.suite_info))
self.policies.append(BuildDependsPolicy(self.options, self.suite_info))
for policy in self.policies: for policy in self.policies:
policy.register_hints(self._hint_parser) policy.register_hints(self._hint_parser)
@ -568,6 +569,7 @@ class Britney(object):
[], [],
None, None,
True, True,
None
) )
self.sources['testing'][pkg_name] = src_data self.sources['testing'][pkg_name] = src_data
@ -642,6 +644,7 @@ class Britney(object):
[], [],
None, None,
True, True,
None,
) )
self.sources['testing'][pkg_name] = src_data self.sources['testing'][pkg_name] = src_data
self.sources['unstable'][pkg_name] = src_data self.sources['unstable'][pkg_name] = src_data
@ -843,7 +846,7 @@ class Britney(object):
srcdist[source].binaries.append(pkg_id) srcdist[source].binaries.append(pkg_id)
# if the source package doesn't exist, create a fake one # if the source package doesn't exist, create a fake one
else: else:
srcdist[source] = SourcePackage(source_version, 'faux', [pkg_id], None, True) srcdist[source] = SourcePackage(source_version, 'faux', [pkg_id], None, True, None)
# add the resulting dictionary to the package list # add the resulting dictionary to the package list
packages[pkg] = dpkg packages[pkg] = dpkg

@ -9,14 +9,15 @@ SuiteInfo = namedtuple('SuiteInfo', [
class SourcePackage(object): class SourcePackage(object):
__slots__ = ['version', 'section', 'binaries', 'maintainer', 'is_fakesrc'] __slots__ = ['version', 'section', 'binaries', 'maintainer', 'is_fakesrc', 'build_deps_arch']
def __init__(self, version, section, binaries, maintainer, is_fakesrc): def __init__(self, version, section, binaries, maintainer, is_fakesrc, build_deps_arch):
self.version = version self.version = version
self.section = section self.section = section
self.binaries = binaries self.binaries = binaries
self.maintainer = maintainer self.maintainer = maintainer
self.is_fakesrc = is_fakesrc self.is_fakesrc = is_fakesrc
self.build_deps_arch = build_deps_arch
def __getitem__(self, item): def __getitem__(self, item):
return getattr(self, self.__slots__[item]) return getattr(self, self.__slots__[item])

@ -75,7 +75,9 @@ class Excuse(object):
self._policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY self._policy_verdict = PolicyVerdict.REJECTED_PERMANENTLY
self.invalid_deps = set() self.invalid_deps = set()
self.invalid_build_deps = set()
self.deps = {} self.deps = {}
self.arch_build_deps = {}
self.sane_deps = [] self.sane_deps = []
self.break_deps = [] self.break_deps = []
self.newbugs = set() self.newbugs = set()
@ -135,10 +137,19 @@ class Excuse(object):
if (name, arch) not in self.break_deps: if (name, arch) not in self.break_deps:
self.break_deps.append( (name, arch) ) self.break_deps.append( (name, 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 invalidate_dep(self, name): def invalidate_dep(self, name):
"""Invalidate dependency""" """Invalidate dependency"""
self.invalid_deps.add(name) self.invalid_deps.add(name)
def invalidate_build_dep(self, name):
"""Invalidate build-dependency"""
self.invalid_build_deps.add(name)
def setdaysold(self, daysold, mindays): def setdaysold(self, daysold, mindays):
"""Set the number of days from the upload and the minimum number of days for the update""" """Set the number of days from the upload and the minimum number of days for the update"""
self.daysold = daysold self.daysold = daysold
@ -209,6 +220,17 @@ class Excuse(object):
for (n,a) in self.break_deps: for (n,a) in self.break_deps:
if n not in self.deps: if n not in self.deps:
res += "<li>Ignoring %s depends: <a href=\"#%s\">%s</a>\n" % (a, n, n) res += "<li>Ignoring %s depends: <a href=\"#%s\">%s</a>\n" % (a, n, n)
lastdep = ""
for x in sorted(self.arch_build_deps, key=lambda x: x.split('/')[0]):
dep = x.split('/')[0]
if dep == lastdep:
continue
lastdep = dep
if x in self.invalid_build_deps:
res = res + "<li>Build-Depends(-Arch): %s <a href=\"#%s\">%s</a> (not ready)\n" % (self.name, dep, dep)
else:
res = res + "<li>Build-Depends(-Arch): %s <a href=\"#%s\">%s</a>\n" % (self.name, dep, dep)
if self.is_valid: if self.is_valid:
res += "<li>Valid candidate\n" res += "<li>Valid candidate\n"
else: else:
@ -258,13 +280,14 @@ class Excuse(object):
'on-architectures': sorted(self.missing_builds), 'on-architectures': sorted(self.missing_builds),
'on-unimportant-architectures': sorted(self.missing_builds_ood_arch), 'on-unimportant-architectures': sorted(self.missing_builds_ood_arch),
} }
if self.deps or self.invalid_deps or self.break_deps: if self.deps or self.invalid_deps or self.arch_build_deps or self.invalid_build_deps or self.break_deps:
excusedata['dependencies'] = dep_data = {} excusedata['dependencies'] = dep_data = {}
migrate_after = sorted(x for x in self.deps if x not in self.invalid_deps) migrate_after = sorted((self.deps.keys() - self.invalid_deps)
| (self.arch_build_deps.keys() - self.invalid_build_deps))
break_deps = [x for x, _ in self.break_deps if x not in self.deps] break_deps = [x for x, _ in self.break_deps if x not in self.deps]
if self.invalid_deps: if self.invalid_deps or self.invalid_build_deps:
dep_data['blocked-by'] = sorted(self.invalid_deps) dep_data['blocked-by'] = sorted(self.invalid_deps | self.invalid_build_deps)
if migrate_after: if migrate_after:
dep_data['migrate-after'] = migrate_after dep_data['migrate-after'] = migrate_after
if break_deps: if break_deps:

@ -8,6 +8,7 @@ import apt_pkg
from britney2.hints import Hint, split_into_one_hint_per_package from britney2.hints import Hint, split_into_one_hint_per_package
from britney2.policies import PolicyVerdict from britney2.policies import PolicyVerdict
from britney2.utils import get_dependency_solvers
class BasePolicy(object): class BasePolicy(object):
@ -616,3 +617,80 @@ class PiupartsPolicy(BasePolicy):
summary[source] = (state, url) summary[source] = (state, url)
return summary return summary
class BuildDependsPolicy(BasePolicy):
def __init__(self, options, suite_info):
super().__init__('build-depends', options, suite_info, {'unstable', 'tpu', 'pu'})
self._britney = None
def initialise(self, britney):
super().initialise(britney)
self._britney = britney
def apply_policy_impl(self, build_deps_info, suite, source_name, source_data_tdist, source_data_srcdist, excuse,
get_dependency_solvers=get_dependency_solvers):
verdict = PolicyVerdict.PASS
britney = self._britney
# local copies for better performance
parse_src_depends = apt_pkg.parse_src_depends
# analyze the dependency fields (if present)
deps = source_data_srcdist.build_deps_arch
if not deps:
return verdict
sources_s = None
sources_t = None
unsat_bd = {}
relevant_archs = {binary.architecture for binary in source_data_srcdist.binaries
if britney.all_binaries[binary].architecture != 'all'}
for arch in (arch for arch in self.options.architectures if arch in relevant_archs):
# retrieve the binary package from the specified suite and arch
binaries_s_a, provides_s_a = britney.binaries[suite][arch]
binaries_t_a, provides_t_a = britney.binaries['testing'][arch]
# for every dependency block (formed as conjunction of disjunction)
for block, block_txt in zip(parse_src_depends(deps, False, arch), deps.split(',')):
# if the block is satisfied in testing, then skip the block
if get_dependency_solvers(block, binaries_t_a, provides_t_a):
# Satisfied in testing; all ok.
continue
# check if the block can be satisfied in the source suite, and list the solving packages
packages = get_dependency_solvers(block, binaries_s_a, provides_s_a)
packages = [binaries_s_a[p].source for p in packages]
# if the dependency can be satisfied by the same source package, skip the block:
# obviously both binary packages will enter testing together
if source_name in packages:
continue
# if no package can satisfy the dependency, add this information to the excuse
if not packages:
excuse.addhtml("%s unsatisfiable Build-Depends(-Arch) on %s: %s" % (source_name, arch, block_txt.strip()))
if arch not in unsat_bd:
unsat_bd[arch] = []
unsat_bd[arch].append(block_txt.strip())
if verdict.value < PolicyVerdict.REJECTED_PERMANENTLY.value:
verdict = PolicyVerdict.REJECTED_PERMANENTLY
continue
if not sources_t:
sources_t = britney.sources['testing']
sources_s = britney.sources[suite]
# for the solving packages, update the excuse to add the dependencies
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)
else:
excuse.add_arch_build_dep(p, arch)
if unsat_bd:
build_deps_info['unsatisfiable-arch-build-depends'] = unsat_bd
return verdict

@ -721,11 +721,18 @@ def read_sources_file(filename, sources=None, intern=sys.intern):
section = get_field('Section') section = get_field('Section')
if section: if section:
section = intern(section.strip()) section = intern(section.strip())
build_deps_arch = ", ".join(x for x in (get_field('Build-Depends'), get_field('Build-Depends-Arch'))
if x is not None)
if build_deps_arch != '':
build_deps_arch = sys.intern(build_deps_arch)
else:
build_deps_arch = None
sources[intern(pkg)] = SourcePackage(intern(ver), sources[intern(pkg)] = SourcePackage(intern(ver),
section, section,
[], [],
maint, maint,
False, False,
build_deps_arch,
) )
return sources return sources
@ -789,14 +796,17 @@ def invalidate_excuses(excuses, valid, invalid):
# build the reverse dependencies # build the reverse dependencies
revdeps = defaultdict(list) revdeps = defaultdict(list)
revbuilddeps = defaultdict(list)
for exc in excuses.values(): for exc in excuses.values():
for d in exc.deps: for d in exc.deps:
revdeps[d].append(exc.name) revdeps[d].append(exc.name)
for d in exc.arch_build_deps:
revbuilddeps[d].append(exc.name)
# loop on the invalid excuses # loop on the invalid excuses
for ename in iter_except(invalid.pop, KeyError): for ename in iter_except(invalid.pop, KeyError):
# if there is no reverse dependency, skip the item # if there is no reverse dependency, skip the item
if ename not in revdeps: if ename not in revdeps and ename not in revbuilddeps:
continue continue
# if the dependency can be satisfied by a testing-proposed-updates excuse, skip the item # if the dependency can be satisfied by a testing-proposed-updates excuse, skip the item
if (ename + "_tpu") in valid: if (ename + "_tpu") in valid:
@ -807,11 +817,10 @@ def invalidate_excuses(excuses, valid, invalid):
rdep_verdict = PolicyVerdict.REJECTED_BLOCKED_BY_ANOTHER_ITEM rdep_verdict = PolicyVerdict.REJECTED_BLOCKED_BY_ANOTHER_ITEM
# loop on the reverse dependencies # loop on the reverse dependencies
if ename in revdeps:
for x in revdeps[ename]: for x in revdeps[ename]:
if x in valid: # if the item is valid and it is not marked as `forced', then we invalidate it
# if the item is valid and it is marked as `forced', skip the item if x in valid and not excuses[x].forced:
if excuses[x].forced:
continue
# otherwise, invalidate the dependency and mark as invalidated and # otherwise, invalidate the dependency and mark as invalidated and
# remove the depending excuses # remove the depending excuses
@ -823,6 +832,20 @@ def invalidate_excuses(excuses, valid, invalid):
if excuses[x].policy_verdict.value < rdep_verdict.value: if excuses[x].policy_verdict.value < rdep_verdict.value:
excuses[x].policy_verdict = rdep_verdict 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
def compile_nuninst(binaries_t, inst_tester, architectures, nobreakall_arches): def compile_nuninst(binaries_t, inst_tester, architectures, nobreakall_arches):
"""Compile a nuninst dict from the current testing """Compile a nuninst dict from the current testing

@ -39,7 +39,7 @@ def create_excuse(name):
def create_source_package(version, section='devel', binaries=None): def create_source_package(version, section='devel', binaries=None):
if binaries is None: if binaries is None:
binaries = [] binaries = []
return SourcePackage(version, section, binaries, 'Random tester', False) return SourcePackage(version, section, binaries, 'Random tester', False, None)
def create_policy_objects(source_name, target_version, source_version): def create_policy_objects(source_name, target_version, source_version):

Loading…
Cancel
Save