mirror of
https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
synced 2025-05-21 07:21:30 +00:00
Add implicit dependency policy
Based in part on patches by Niels Thykier <niels@thykier.net> Signed-off-by: Ivo De Decker <ivodd@debian.org>
This commit is contained in:
parent
9865db0448
commit
a08b7ebc48
15
britney.py
15
britney.py
@ -200,9 +200,17 @@ from britney2.installability.builder import build_installability_tester
|
|||||||
from britney2.installability.solver import InstallabilitySolver
|
from britney2.installability.solver import InstallabilitySolver
|
||||||
from britney2.migration import MigrationManager
|
from britney2.migration import MigrationManager
|
||||||
from britney2.migrationitem import MigrationItemFactory
|
from britney2.migrationitem import MigrationItemFactory
|
||||||
from britney2.policies.policy import (AgePolicy, RCBugPolicy, PiupartsPolicy, DependsPolicy,
|
from britney2.policies.policy import (AgePolicy,
|
||||||
BuildDependsPolicy, PolicyEngine,
|
RCBugPolicy,
|
||||||
BlockPolicy, BuiltUsingPolicy, BuiltOnBuilddPolicy)
|
PiupartsPolicy,
|
||||||
|
DependsPolicy,
|
||||||
|
BuildDependsPolicy,
|
||||||
|
PolicyEngine,
|
||||||
|
BlockPolicy,
|
||||||
|
BuiltUsingPolicy,
|
||||||
|
BuiltOnBuilddPolicy,
|
||||||
|
ImplicitDependencyPolicy,
|
||||||
|
)
|
||||||
from britney2.policies.autopkgtest import AutopkgtestPolicy
|
from britney2.policies.autopkgtest import AutopkgtestPolicy
|
||||||
from britney2.utils import (log_and_format_old_libraries,
|
from britney2.utils import (log_and_format_old_libraries,
|
||||||
read_nuninst, write_nuninst, write_heidi,
|
read_nuninst, write_nuninst, write_heidi,
|
||||||
@ -501,6 +509,7 @@ class Britney(object):
|
|||||||
self._policy_engine.add_policy(BuildDependsPolicy(self.options, self.suite_info))
|
self._policy_engine.add_policy(BuildDependsPolicy(self.options, self.suite_info))
|
||||||
self._policy_engine.add_policy(BlockPolicy(self.options, self.suite_info))
|
self._policy_engine.add_policy(BlockPolicy(self.options, self.suite_info))
|
||||||
self._policy_engine.add_policy(BuiltUsingPolicy(self.options, self.suite_info))
|
self._policy_engine.add_policy(BuiltUsingPolicy(self.options, self.suite_info))
|
||||||
|
self._policy_engine.add_policy(ImplicitDependencyPolicy(self.options, self.suite_info))
|
||||||
if getattr(self.options, 'check_buildd', 'no') == 'yes':
|
if getattr(self.options, 'check_buildd', 'no') == 'yes':
|
||||||
self._policy_engine.add_policy(BuiltOnBuilddPolicy(self.options, self.suite_info))
|
self._policy_engine.add_policy(BuiltOnBuilddPolicy(self.options, self.suite_info))
|
||||||
|
|
||||||
|
@ -9,6 +9,11 @@ class DependencyType(Enum):
|
|||||||
BUILD_DEPENDS = ('Build-Depends(-Arch)', 'build-depends', 'build-dependency')
|
BUILD_DEPENDS = ('Build-Depends(-Arch)', 'build-depends', 'build-dependency')
|
||||||
BUILD_DEPENDS_INDEP = ('Build-Depends-Indep', 'build-depends-indep', 'build-dependency (indep)')
|
BUILD_DEPENDS_INDEP = ('Build-Depends-Indep', 'build-depends-indep', 'build-dependency (indep)')
|
||||||
BUILT_USING = ('Built-Using', 'built-using', 'built-using')
|
BUILT_USING = ('Built-Using', 'built-using', 'built-using')
|
||||||
|
# Pseudo dependency where Breaks/Conflicts effectively become a inverted dependency. E.g.
|
||||||
|
# p Depends on q plus q/2 breaks p/1 implies that p/2 must migrate before q/2 can migrate
|
||||||
|
# (or they go at the same time).
|
||||||
|
# - can also happen with version ranges
|
||||||
|
IMPLICIT_DEPENDENCY = ('Implicit dependency', 'implicit-dependency', 'implicit-dependency')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value[0]
|
return self.value[0]
|
||||||
|
@ -13,7 +13,7 @@ from britney2 import SuiteClass, PackageId
|
|||||||
from britney2.hints import Hint, split_into_one_hint_per_package
|
from britney2.hints import Hint, split_into_one_hint_per_package
|
||||||
from britney2.inputs.suiteloader import SuiteContentLoader
|
from britney2.inputs.suiteloader import SuiteContentLoader
|
||||||
from britney2.policies import PolicyVerdict, ApplySrcPolicy
|
from britney2.policies import PolicyVerdict, ApplySrcPolicy
|
||||||
from britney2.utils import get_dependency_solvers
|
from britney2.utils import get_dependency_solvers, find_newer_binaries
|
||||||
from britney2 import DependencyType
|
from britney2 import DependencyType
|
||||||
from britney2.excusedeps import DependencySpec
|
from britney2.excusedeps import DependencySpec
|
||||||
|
|
||||||
@ -1397,3 +1397,315 @@ class BuiltOnBuilddPolicy(BasePolicy):
|
|||||||
signerinfo = json.load(fd)
|
signerinfo = json.load(fd)
|
||||||
|
|
||||||
return signerinfo
|
return signerinfo
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitDependencyPolicy(BasePolicy):
|
||||||
|
"""Implicit Dependency policy
|
||||||
|
|
||||||
|
Upgrading a package pkg-a can break the installability of a package pkg-b.
|
||||||
|
A newer version (or the removal) of pkg-b might fix the issue. In that
|
||||||
|
case, pkg-a has an 'implicit dependency' on pkg-b, because pkg-a can only
|
||||||
|
migrate if pkg-b also migrates.
|
||||||
|
|
||||||
|
This policy tries to discover a few common cases, and adds the relevant
|
||||||
|
info to the excuses. If another item is needed to fix the
|
||||||
|
uninstallability, a dependency is added. If no newer item can fix it, this
|
||||||
|
excuse will be blocked.
|
||||||
|
|
||||||
|
Note that the migration step will check the installability of every
|
||||||
|
package, so this policy doesn't need to handle every corner case. It
|
||||||
|
must, however, make sure that no excuse is unnecessarily blocked.
|
||||||
|
|
||||||
|
Some cases that should be detected by this policy:
|
||||||
|
|
||||||
|
* pkg-a is upgraded from 1.0-1 to 2.0-1, while
|
||||||
|
pkg-b has "Depends: pkg-a (<< 2.0)"
|
||||||
|
This typically happens if pkg-b has a strict dependency on pkg-a because
|
||||||
|
it uses some non-stable internal interface (examples are glibc,
|
||||||
|
binutils, python3-defaults, ...)
|
||||||
|
|
||||||
|
* pkg-a is upgraded from 1.0-1 to 2.0-1, and
|
||||||
|
pkg-a 1.0-1 has "Provides: provides-1",
|
||||||
|
pkg-a 2.0-1 has "Provides: provides-2",
|
||||||
|
pkg-b has "Depends: provides-1"
|
||||||
|
This typically happens when pkg-a has an interface that changes between
|
||||||
|
versions, and a virtual package is used to identify the version of this
|
||||||
|
interface (e.g. perl-api-x.y)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, options, suite_info):
|
||||||
|
super().__init__('implicit-deps', options, suite_info,
|
||||||
|
{SuiteClass.PRIMARY_SOURCE_SUITE, SuiteClass.ADDITIONAL_SOURCE_SUITE},
|
||||||
|
ApplySrcPolicy.RUN_ON_EVERY_ARCH_ONLY)
|
||||||
|
self._pkg_universe = None
|
||||||
|
self._all_binaries = None
|
||||||
|
self._smooth_updates = None
|
||||||
|
self._nobreakall_arches = None
|
||||||
|
self._new_arches = None
|
||||||
|
self._break_arches = None
|
||||||
|
self._allow_uninst = None
|
||||||
|
|
||||||
|
def initialise(self, britney):
|
||||||
|
super().initialise(britney)
|
||||||
|
self._pkg_universe = britney.pkg_universe
|
||||||
|
self._all_binaries = britney.all_binaries
|
||||||
|
self._smooth_updates = britney.options.smooth_updates
|
||||||
|
self._nobreakall_arches = self.options.nobreakall_arches
|
||||||
|
self._new_arches = self.options.new_arches
|
||||||
|
self._break_arches = self.options.break_arches
|
||||||
|
self._allow_uninst = britney.allow_uninst
|
||||||
|
|
||||||
|
def can_be_removed(self, pkg):
|
||||||
|
src = pkg.source
|
||||||
|
target_suite = self.suite_info.target_suite
|
||||||
|
|
||||||
|
# TODO these conditions shouldn't be hardcoded here
|
||||||
|
# ideally, we would be able to look up excuses to see if the removal
|
||||||
|
# is in there, but in the current flow, this policy is called before
|
||||||
|
# all possible excuses exist, so there is no list for us to check
|
||||||
|
|
||||||
|
if src not in self.suite_info.primary_source_suite.sources:
|
||||||
|
# source for pkg not in unstable: candidate for removal
|
||||||
|
return True
|
||||||
|
|
||||||
|
source_t = target_suite.sources[src]
|
||||||
|
for hint in self.hints.search('remove', package=src, version=source_t.version):
|
||||||
|
# removal hint for the source in testing: candidate for removal
|
||||||
|
return True
|
||||||
|
|
||||||
|
if target_suite.is_cruft(pkg):
|
||||||
|
# if pkg is cruft in testing, removal will be tried
|
||||||
|
return True
|
||||||
|
|
||||||
|
# the case were the newer version of the source no longer includes the
|
||||||
|
# binary (or includes a cruft version of the binary) will be handled
|
||||||
|
# separately (in that case there might be an implicit dependency on
|
||||||
|
# the newer source)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def should_skip_rdep(self, pkg, source_name, myarch):
|
||||||
|
target_suite = self.suite_info.target_suite
|
||||||
|
|
||||||
|
if not target_suite.is_pkg_in_the_suite(pkg.pkg_id):
|
||||||
|
# it is not in the target suite, migration cannot break anything
|
||||||
|
return True
|
||||||
|
|
||||||
|
if pkg.source == source_name:
|
||||||
|
# if it is built from the same source, it will be upgraded
|
||||||
|
# with the source
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.can_be_removed(pkg):
|
||||||
|
# could potentially be removed, so if that happens, it won't be
|
||||||
|
# broken
|
||||||
|
return True
|
||||||
|
|
||||||
|
if pkg.architecture == 'all' and \
|
||||||
|
myarch not in self._nobreakall_arches:
|
||||||
|
# arch all on non nobreakarch is allowed to become uninstallable
|
||||||
|
return True
|
||||||
|
|
||||||
|
if pkg.pkg_id.package_name in self._allow_uninst[myarch]:
|
||||||
|
# there is a hint to allow this binary to become uninstallable
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not target_suite.is_installable(pkg.pkg_id):
|
||||||
|
# it is already uninstallable in the target suite, migration
|
||||||
|
# cannot break anything
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def breaks_installability(self, pkg_id_t, pkg_id_s, pkg_to_check):
|
||||||
|
"""
|
||||||
|
Check if upgrading pkg_id_t to pkg_id_s breaks the installability of
|
||||||
|
pkg_to_check.
|
||||||
|
|
||||||
|
To check if removing pkg_id_t breaks pkg_to_check, set pkg_id_s to
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pkg_universe = self._pkg_universe
|
||||||
|
negative_deps = pkg_universe.negative_dependencies_of(pkg_to_check)
|
||||||
|
|
||||||
|
for dep in pkg_universe.dependencies_of(pkg_to_check):
|
||||||
|
if pkg_id_t not in dep:
|
||||||
|
# this depends doesn't have pkg_id_t as alternative, so
|
||||||
|
# upgrading pkg_id_t cannot break this dependency clause
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We check all the alternatives for this dependency, to find one
|
||||||
|
# that can satisfy it when pkg_id_t is upgraded to pkg_id_s
|
||||||
|
found_alternative = False
|
||||||
|
for d in dep:
|
||||||
|
if d in negative_deps:
|
||||||
|
# If this alternative dependency conflicts with
|
||||||
|
# pkg_to_check, it cannot be used to satisfy the
|
||||||
|
# dependency.
|
||||||
|
# This commonly happens when breaks are added to pkg_id_s.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if d.package_name != pkg_id_t.package_name:
|
||||||
|
# a binary different from pkg_id_t can satisfy the dep, so
|
||||||
|
# upgrading pkg_id_t won't break this dependency
|
||||||
|
found_alternative = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if d != pkg_id_s:
|
||||||
|
# We want to know the impact of the upgrade of
|
||||||
|
# pkg_id_t to pkg_id_s. If pkg_id_s migrates to the
|
||||||
|
# target suite, any other version of this binary will
|
||||||
|
# not be there, so it cannot satisfy this dependency.
|
||||||
|
# This includes pkg_id_t, but also other versions.
|
||||||
|
continue
|
||||||
|
|
||||||
|
# pkg_id_s can satisfy the dep
|
||||||
|
found_alternative = True
|
||||||
|
|
||||||
|
if not found_alternative:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_upgrade(self, pkg_id_t, pkg_id_s, source_name, myarch, broken_binaries, excuse):
|
||||||
|
verdict = PolicyVerdict.PASS
|
||||||
|
|
||||||
|
pkg_universe = self._pkg_universe
|
||||||
|
all_binaries = self._all_binaries
|
||||||
|
|
||||||
|
# check all rdeps of the package in testing
|
||||||
|
rdeps_t = pkg_universe.reverse_dependencies_of(pkg_id_t)
|
||||||
|
|
||||||
|
for rdep_pkg in sorted(rdeps_t):
|
||||||
|
rdep_p = all_binaries[rdep_pkg]
|
||||||
|
|
||||||
|
# check some cases where the rdep won't become uninstallable, or
|
||||||
|
# where we don't care if it does
|
||||||
|
if self.should_skip_rdep(rdep_p, source_name, myarch):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not self.breaks_installability(pkg_id_t, pkg_id_s, rdep_pkg):
|
||||||
|
# if upgrading pkg_id_t to pkg_id_s doesn't break rdep_pkg,
|
||||||
|
# there is no implicit dependency
|
||||||
|
continue
|
||||||
|
|
||||||
|
# The upgrade breaks the installability of the rdep. We need to
|
||||||
|
# find out if there is a newer version of the rdep that solves the
|
||||||
|
# uninstallability. If that is the case, there is an implicit
|
||||||
|
# dependency. If not, the upgrade will fail.
|
||||||
|
|
||||||
|
# check source versions
|
||||||
|
newer_versions = find_newer_binaries(self.suite_info, rdep_p,
|
||||||
|
add_source_for_dropped_bin=True)
|
||||||
|
good_newer_versions = set()
|
||||||
|
for npkg, suite in newer_versions:
|
||||||
|
if npkg.architecture == 'source':
|
||||||
|
# When a newer version of the source package doesn't have
|
||||||
|
# the binary, we get the source as 'newer version'. In
|
||||||
|
# this case, the binary will not be uninstallable if the
|
||||||
|
# newer source migrates, because it is no longer there.
|
||||||
|
good_newer_versions.add(npkg)
|
||||||
|
continue
|
||||||
|
if not self.breaks_installability(pkg_id_t, pkg_id_s, npkg):
|
||||||
|
good_newer_versions.add(npkg)
|
||||||
|
|
||||||
|
if good_newer_versions:
|
||||||
|
spec = DependencySpec(DependencyType.IMPLICIT_DEPENDENCY, myarch)
|
||||||
|
excuse.add_package_depends(spec, good_newer_versions)
|
||||||
|
else:
|
||||||
|
# no good newer versions: no possible solution
|
||||||
|
broken_binaries.add(rdep_pkg.name)
|
||||||
|
if pkg_id_s:
|
||||||
|
action = "migrating %s to %s" % (
|
||||||
|
pkg_id_s.name,
|
||||||
|
self.suite_info.target_suite.name)
|
||||||
|
else:
|
||||||
|
action = "removing %s from %s" % (
|
||||||
|
pkg_id_t.name,
|
||||||
|
self.suite_info.target_suite.name)
|
||||||
|
info = "%s makes %s uninstallable" % (
|
||||||
|
action, rdep_pkg.name)
|
||||||
|
verdict = PolicyVerdict.REJECTED_PERMANENTLY
|
||||||
|
excuse.add_verdict_info(verdict, info)
|
||||||
|
|
||||||
|
return verdict
|
||||||
|
|
||||||
|
def apply_srcarch_policy_impl(self, implicit_dep_info, item, arch, source_data_tdist,
|
||||||
|
source_data_srcdist, excuse):
|
||||||
|
verdict = PolicyVerdict.PASS
|
||||||
|
|
||||||
|
if not source_data_tdist:
|
||||||
|
# this item is not currently in testing: no implicit dependency
|
||||||
|
return verdict
|
||||||
|
|
||||||
|
source_suite = item.suite
|
||||||
|
source_name = item.package
|
||||||
|
target_suite = self.suite_info.target_suite
|
||||||
|
sources_t = target_suite.sources
|
||||||
|
all_binaries = self._all_binaries
|
||||||
|
|
||||||
|
# we check all binaries for this excuse that are currently in testing
|
||||||
|
relevant_binaries = [x for x in source_data_tdist.binaries if (arch == 'source' or x.architecture == arch)
|
||||||
|
and x.package_name in target_suite.binaries[x.architecture]
|
||||||
|
and x.architecture not in self._new_arches
|
||||||
|
and x.architecture not in self._break_arches]
|
||||||
|
|
||||||
|
broken_binaries = set()
|
||||||
|
|
||||||
|
for pkg_id_t in sorted(relevant_binaries):
|
||||||
|
mypkg = pkg_id_t.package_name
|
||||||
|
myarch = pkg_id_t.architecture
|
||||||
|
binaries_t_a = target_suite.binaries[myarch]
|
||||||
|
binaries_s_a = source_suite.binaries[myarch]
|
||||||
|
|
||||||
|
if target_suite.is_cruft(all_binaries[pkg_id_t]):
|
||||||
|
# this binary is cruft in testing: it will stay around as long
|
||||||
|
# as necessary to satisfy dependencies, so we don't need to
|
||||||
|
# care
|
||||||
|
continue
|
||||||
|
|
||||||
|
if mypkg in binaries_s_a:
|
||||||
|
mybin = binaries_s_a[mypkg]
|
||||||
|
pkg_id_s = mybin.pkg_id
|
||||||
|
if mybin.source != source_name:
|
||||||
|
# hijack: this is too complicated to check, so we ignore
|
||||||
|
# it (the migration code will check the installability
|
||||||
|
# later anyway)
|
||||||
|
pass
|
||||||
|
elif mybin.source_version != source_data_srcdist.version:
|
||||||
|
# cruft in source suite: pretend the binary doesn't exist
|
||||||
|
pkg_id_s = None
|
||||||
|
elif pkg_id_t == pkg_id_s:
|
||||||
|
# same binary (probably arch: all from a binNMU):
|
||||||
|
# 'upgrading' doesn't change anything, for this binary, so
|
||||||
|
# it won't break anything
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
pkg_id_s = None
|
||||||
|
|
||||||
|
if not pkg_id_s and (
|
||||||
|
'ALL' in self._smooth_updates or
|
||||||
|
binaries_t_a[mypkg].section in self._smooth_updates):
|
||||||
|
# the binary isn't in the new version (or is cruft there), and
|
||||||
|
# smooth updates are allowed: the binary can stay around if
|
||||||
|
# that is necessary to satisfy dependencies, so we don't need
|
||||||
|
# to check it
|
||||||
|
continue
|
||||||
|
|
||||||
|
v = self.check_upgrade(pkg_id_t, pkg_id_s, source_name, myarch, broken_binaries, excuse)
|
||||||
|
|
||||||
|
if v > verdict:
|
||||||
|
verdict = v
|
||||||
|
|
||||||
|
# each arch is processed separately, so if we already have info from
|
||||||
|
# other archs, we need to merge the info from this arch
|
||||||
|
broken_old = set()
|
||||||
|
if 'implicit-deps' not in implicit_dep_info:
|
||||||
|
implicit_dep_info['implicit-deps'] = {}
|
||||||
|
else:
|
||||||
|
broken_old = set(implicit_dep_info['implicit-deps']['broken-binaries'])
|
||||||
|
|
||||||
|
implicit_dep_info['implicit-deps']['broken-binaries'] = \
|
||||||
|
sorted(broken_old | broken_binaries)
|
||||||
|
|
||||||
|
return verdict
|
||||||
|
Loading…
x
Reference in New Issue
Block a user