|
|
|
@ -1767,271 +1767,6 @@ class Britney:
|
|
|
|
|
diff = diff + (len(new[arch]) - len(old[arch]))
|
|
|
|
|
return diff <= 0
|
|
|
|
|
|
|
|
|
|
def check_installable(self, pkg, arch, suite, excluded=[], conflicts=False):
|
|
|
|
|
"""Check if a package is installable
|
|
|
|
|
|
|
|
|
|
This method analyzes the dependencies of the binary package specified
|
|
|
|
|
by the parameter `pkg' for the architecture `arch' within the suite
|
|
|
|
|
`suite'. If the dependency can be satisfied in the given `suite` and
|
|
|
|
|
`conflicts` parameter is True, then the co-installability with
|
|
|
|
|
conflicts handling is checked.
|
|
|
|
|
|
|
|
|
|
The dependency fields checked are Pre-Depends and Depends.
|
|
|
|
|
|
|
|
|
|
The method returns a boolean which is True if the given package is
|
|
|
|
|
installable.
|
|
|
|
|
|
|
|
|
|
NOTE: this method has been deprecated, actually we use is_installable
|
|
|
|
|
from the britney c extension called within a testing system. See
|
|
|
|
|
self.build_systems for more information.
|
|
|
|
|
"""
|
|
|
|
|
self.__log("WARNING: method check_installable is deprecated: use is_installable instead!", type="E")
|
|
|
|
|
|
|
|
|
|
# retrieve the binary package from the specified suite and arch
|
|
|
|
|
binary_u = self.binaries[suite][arch][0][pkg]
|
|
|
|
|
|
|
|
|
|
# local copies for better performances
|
|
|
|
|
parse_depends = apt_pkg.ParseDepends
|
|
|
|
|
get_dependency_solvers = self.get_dependency_solvers
|
|
|
|
|
|
|
|
|
|
# analyze the dependency fields (if present)
|
|
|
|
|
for type in (PREDEPENDS, DEPENDS):
|
|
|
|
|
if not binary_u[type]:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# for every block of dependency (which is formed as conjunction of disconjunction)
|
|
|
|
|
for block in parse_depends(binary_u[type]):
|
|
|
|
|
# if the block is not satisfied, return False
|
|
|
|
|
solved, packages = get_dependency_solvers(block, arch, 'testing', excluded, strict=True)
|
|
|
|
|
if not solved:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# otherwise, the package is installable (not considering conflicts)
|
|
|
|
|
# if the conflicts handling is enabled, then check conflicts before
|
|
|
|
|
# saying that the package is really installable
|
|
|
|
|
if conflicts:
|
|
|
|
|
return self.check_conflicts(pkg, arch, excluded, {}, {})
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def check_conflicts(self, pkg, arch, broken, system, conflicts):
|
|
|
|
|
"""Check if a package can be installed satisfying the conflicts
|
|
|
|
|
|
|
|
|
|
This method checks if the `pkg` package from the `arch` architecture
|
|
|
|
|
can be installed (excluding `broken` packages) within the system
|
|
|
|
|
`system` along with all its dependencies. This means that all the
|
|
|
|
|
conflicts relationships are checked in order to achieve the test
|
|
|
|
|
co-installability of the package.
|
|
|
|
|
|
|
|
|
|
The method returns a boolean which is True if the given package is
|
|
|
|
|
co-installable in the given system.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# local copies for better performances
|
|
|
|
|
binaries = self.binaries['testing'][arch]
|
|
|
|
|
parse_depends = apt_pkg.ParseDepends
|
|
|
|
|
check_depends = apt_pkg.CheckDep
|
|
|
|
|
|
|
|
|
|
# unregister conflicts, local method to remove conflicts
|
|
|
|
|
# registered from a given package.
|
|
|
|
|
def unregister_conflicts(pkg, conflicts):
|
|
|
|
|
for c in conflicts.keys():
|
|
|
|
|
i = 0
|
|
|
|
|
while i < len(conflicts[c]):
|
|
|
|
|
if conflicts[c][i][3] == pkg:
|
|
|
|
|
del conflicts[c][i]
|
|
|
|
|
else: i = i + 1
|
|
|
|
|
if len(conflicts[c]) == 0:
|
|
|
|
|
del conflicts[c]
|
|
|
|
|
|
|
|
|
|
def remove_package(pkg, system, conflicts):
|
|
|
|
|
for k in system:
|
|
|
|
|
if pkg in system[k][1]:
|
|
|
|
|
system[k][1].remove(pkg)
|
|
|
|
|
unregister_conflicts(pkg, conflicts)
|
|
|
|
|
|
|
|
|
|
# handle a conflict, local method to solve a conflict which happened
|
|
|
|
|
# in the system; the behaviour of the conflict-solver is:
|
|
|
|
|
# 1. If there are alternatives for the package which must be removed,
|
|
|
|
|
# try them, and if one of them resolves the system return True;
|
|
|
|
|
# 2. If none of the alternatives can solve the conflict, then call
|
|
|
|
|
# itself for the package which depends on the conflicting package.
|
|
|
|
|
# 3. If the top of the dependency tree is reached, then the conflict
|
|
|
|
|
# can't be solved, so return False.
|
|
|
|
|
def handle_conflict(pkg, source, system, conflicts):
|
|
|
|
|
# skip packages which don't have reverse dependencies
|
|
|
|
|
if source not in system or system[source][1] == []:
|
|
|
|
|
remove_package(source, system, conflicts)
|
|
|
|
|
return (system, conflicts)
|
|
|
|
|
# reached the top of the tree
|
|
|
|
|
if not system[source][1][0]:
|
|
|
|
|
return False
|
|
|
|
|
# remove its conflicts
|
|
|
|
|
unregister_conflicts(source, conflicts)
|
|
|
|
|
# if there are alternatives, try them
|
|
|
|
|
alternatives = system[source][0]
|
|
|
|
|
for alt in alternatives:
|
|
|
|
|
if satisfy(alt, [x for x in alternatives if x != alt], pkg_from=system[source][1],
|
|
|
|
|
system=system, conflicts=conflicts, excluded=[source]):
|
|
|
|
|
remove_package(source, system, conflicts)
|
|
|
|
|
return (system, conflicts)
|
|
|
|
|
# there are no good alternatives, so remove the package which depends on it
|
|
|
|
|
for p in system[source][1]:
|
|
|
|
|
# the package does not exist, we reached the top of the tree
|
|
|
|
|
if not p: return False
|
|
|
|
|
# we are providing the package we conflict on (eg. exim4 and mail-transfer-agent), skip it
|
|
|
|
|
if p == pkg: continue
|
|
|
|
|
output = handle_conflict(pkg, p, system, conflicts)
|
|
|
|
|
if output:
|
|
|
|
|
system, conflicts = output
|
|
|
|
|
else: return False
|
|
|
|
|
remove_package(source, system, conflicts)
|
|
|
|
|
return (system, conflicts)
|
|
|
|
|
|
|
|
|
|
# dependency tree satisfier, local method which tries to satisfy the dependency
|
|
|
|
|
# tree for a given package. It calls itself recursively in order to check the
|
|
|
|
|
# co-installability of the full tree of dependency of the starting package.
|
|
|
|
|
# If a conflict is detected, it tries to handle it calling the handle_conflict
|
|
|
|
|
# method; if it can't be resolved, then it returns False.
|
|
|
|
|
def satisfy(pkg, pkg_alt=None, pkg_from=None, system=system, conflicts=conflicts, excluded=[]):
|
|
|
|
|
# if it is a real package and it is already installed, skip it and return True
|
|
|
|
|
if pkg in binaries[0]:
|
|
|
|
|
if pkg in system:
|
|
|
|
|
if type(pkg_from) == list:
|
|
|
|
|
system[pkg][1].extend(pkg_from)
|
|
|
|
|
else:
|
|
|
|
|
system[pkg][1].append(pkg_from)
|
|
|
|
|
system[pkg] = (system[pkg][1], filter(lambda x: x in pkg_alt, system[pkg][0]))
|
|
|
|
|
return True
|
|
|
|
|
binary_u = binaries[0][pkg]
|
|
|
|
|
else: binary_u = None
|
|
|
|
|
|
|
|
|
|
# if it is a virtual package
|
|
|
|
|
providers = []
|
|
|
|
|
if pkg_from and pkg in binaries[1]:
|
|
|
|
|
providers = binaries[1][pkg]
|
|
|
|
|
# it is both real and virtual, so the providers are alternatives
|
|
|
|
|
if binary_u:
|
|
|
|
|
providers = filter(lambda x: (not pkg_alt or x not in pkg_alt) and x != pkg, providers)
|
|
|
|
|
if not pkg_alt:
|
|
|
|
|
pkg_alt = []
|
|
|
|
|
pkg_alt.extend(providers)
|
|
|
|
|
# try all the alternatives and if none of them suits, give up and return False
|
|
|
|
|
else:
|
|
|
|
|
# if we already have a provider in the system, everything is ok and return True
|
|
|
|
|
if len(filter(lambda x: x in providers and x not in excluded, system)) > 0:
|
|
|
|
|
return True
|
|
|
|
|
for p in providers:
|
|
|
|
|
# try to install the providers skipping excluded packages,
|
|
|
|
|
# which we already tried but do not work
|
|
|
|
|
if p in excluded: continue
|
|
|
|
|
elif satisfy(p, [a for a in providers if a != p], pkg_from):
|
|
|
|
|
return True
|
|
|
|
|
# if none of them suits, return False
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# if the package doesn't exist, return False
|
|
|
|
|
if not binary_u: return False
|
|
|
|
|
|
|
|
|
|
# it is broken, but we have providers
|
|
|
|
|
if pkg in broken and pkg_from:
|
|
|
|
|
for p in providers:
|
|
|
|
|
# try to install the providers skipping excluded packages,
|
|
|
|
|
# which we already tried but do not work
|
|
|
|
|
if p in excluded: continue
|
|
|
|
|
elif satisfy(p, [a for a in providers if a != p], pkg_from):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# install the package into the system, recording which package required it
|
|
|
|
|
if type(pkg_from) != list:
|
|
|
|
|
pkg_from = [pkg_from]
|
|
|
|
|
system[pkg] = (pkg_alt or [], pkg_from)
|
|
|
|
|
|
|
|
|
|
# register provided packages
|
|
|
|
|
if binary_u[PROVIDES]:
|
|
|
|
|
for p in binary_u[PROVIDES]:
|
|
|
|
|
if p in system:
|
|
|
|
|
# do not consider packages providing the one which we are checking
|
|
|
|
|
if len(system[p][1]) == 1 and system[p][1][0] == None: continue
|
|
|
|
|
system[p][1].append(pkg)
|
|
|
|
|
else:
|
|
|
|
|
system[p] = ([], [pkg])
|
|
|
|
|
|
|
|
|
|
# check the conflicts
|
|
|
|
|
if pkg in conflicts:
|
|
|
|
|
for name, version, op, conflicting in conflicts[pkg]:
|
|
|
|
|
if conflicting in binary_u[PROVIDES] and system[conflicting][1] == [pkg]: continue
|
|
|
|
|
if op == '' and version == '' or check_depends(binary_u[VERSION], op, version):
|
|
|
|
|
# if conflict is found, check if it can be solved removing
|
|
|
|
|
# already-installed packages without breaking the system; if
|
|
|
|
|
# this is not possible, give up and return False
|
|
|
|
|
output = handle_conflict(pkg, conflicting, system.copy(), conflicts.copy())
|
|
|
|
|
if output:
|
|
|
|
|
system, conflicts = output
|
|
|
|
|
else:
|
|
|
|
|
del system[pkg]
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# register conflicts from the just-installed package
|
|
|
|
|
if binary_u[CONFLICTS]:
|
|
|
|
|
for block in map(operator.itemgetter(0), parse_depends(binary_u[CONFLICTS] or [])):
|
|
|
|
|
name, version, op = block
|
|
|
|
|
# skip conflicts for packages provided by itself
|
|
|
|
|
# if the conflicting package is in the system (and it is not a self-conflict)
|
|
|
|
|
if not (name in binary_u[PROVIDES] and system[name][1] == [pkg]) and \
|
|
|
|
|
block[0] != pkg and block[0] in system:
|
|
|
|
|
if block[0] in binaries[0]:
|
|
|
|
|
binary_c = binaries[0][block[0]]
|
|
|
|
|
else: binary_c = None
|
|
|
|
|
if op == '' and version == '' or binary_c and check_depends(binary_c[VERSION], op, version):
|
|
|
|
|
# if conflict is found, check if it can be solved removing
|
|
|
|
|
# already-installed packages without breaking the system; if
|
|
|
|
|
# this is not possible, give up and return False
|
|
|
|
|
output = handle_conflict(pkg, name, system.copy(), conflicts.copy())
|
|
|
|
|
if output:
|
|
|
|
|
system, conflicts = output
|
|
|
|
|
else:
|
|
|
|
|
del system[pkg]
|
|
|
|
|
unregister_conflicts(pkg, conflicts)
|
|
|
|
|
return False
|
|
|
|
|
# register the conflict
|
|
|
|
|
if block[0] not in conflicts:
|
|
|
|
|
conflicts[block[0]] = []
|
|
|
|
|
conflicts[block[0]].append((name, version, op, pkg))
|
|
|
|
|
|
|
|
|
|
# list all its dependencies ...
|
|
|
|
|
dependencies = []
|
|
|
|
|
for key in (PREDEPENDS, DEPENDS):
|
|
|
|
|
if not binary_u[key]: continue
|
|
|
|
|
dependencies.extend(parse_depends(binary_u[key]))
|
|
|
|
|
|
|
|
|
|
# ... and go through them
|
|
|
|
|
for block in dependencies:
|
|
|
|
|
# list the possible alternatives, in case of a conflict
|
|
|
|
|
alternatives = map(operator.itemgetter(0), block)
|
|
|
|
|
valid = False
|
|
|
|
|
for name, version, op in block:
|
|
|
|
|
# otherwise, if it is already installed or it is installable, the block is satisfied
|
|
|
|
|
if name in system or satisfy(name, [a for a in alternatives if a != name], pkg):
|
|
|
|
|
valid = True
|
|
|
|
|
break
|
|
|
|
|
# if the block can't be satisfied, the package is not installable so
|
|
|
|
|
# we need to remove it, its conflicts and its provided packages and
|
|
|
|
|
# return False
|
|
|
|
|
if not valid:
|
|
|
|
|
del system[pkg]
|
|
|
|
|
unregister_conflicts(pkg, conflicts)
|
|
|
|
|
for p in providers:
|
|
|
|
|
if satisfy(p, [a for a in providers if a != p], pkg_from):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# if all the blocks have been satisfied, the package is installable
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# check the package at the top of the tree
|
|
|
|
|
return satisfy(pkg)
|
|
|
|
|
|
|
|
|
|
def doop_source(self, pkg, hint_undo=[]):
|
|
|
|
|
"""Apply a change to the testing distribution as requested by `pkg`
|
|
|
|
|