Performance improvements and detailed documentation for the UpgradeRun.

bzr-import-20160707
Fabio Tranchitella 19 years ago
parent 1f9ba4410c
commit 3a323bdc41

@ -406,7 +406,7 @@ class Britney:
# add the resulting dictionary to the package list # add the resulting dictionary to the package list
packages[pkg] = dpkg packages[pkg] = dpkg
# loop again on the list of packages to register reverse dependencies # loop again on the list of packages to register reverse dependencies and conflicts
register_reverses = self.register_reverses register_reverses = self.register_reverses
for pkg in packages: for pkg in packages:
register_reverses(pkg, packages, provides, check_doubles=False) register_reverses(pkg, packages, provides, check_doubles=False)
@ -430,10 +430,13 @@ class Britney:
dependencies.extend(parse_depends(packages[pkg]['depends'])) dependencies.extend(parse_depends(packages[pkg]['depends']))
if 'pre-depends' in packages[pkg]: if 'pre-depends' in packages[pkg]:
dependencies.extend(parse_depends(packages[pkg]['pre-depends'])) dependencies.extend(parse_depends(packages[pkg]['pre-depends']))
# go through the list
for p in dependencies: for p in dependencies:
for a in p: for a in p:
# register real packages
if a[0] in packages and (not check_doubles or pkg not in packages[a[0]]['rdepends']): if a[0] in packages and (not check_doubles or pkg not in packages[a[0]]['rdepends']):
packages[a[0]]['rdepends'].append(pkg) packages[a[0]]['rdepends'].append(pkg)
# register packages which provides a virtual package
elif a[0] in provides: elif a[0] in provides:
for i in provides.get(a[0]): for i in provides.get(a[0]):
if i not in packages: continue if i not in packages: continue
@ -443,8 +446,10 @@ class Britney:
if 'conflicts' in packages[pkg]: if 'conflicts' in packages[pkg]:
for p in parse_depends(packages[pkg]['conflicts']): for p in parse_depends(packages[pkg]['conflicts']):
for a in p: for a in p:
# register real packages
if a[0] in packages and (not check_doubles or pkg not in packages[a[0]]['rconflicts']): if a[0] in packages and (not check_doubles or pkg not in packages[a[0]]['rconflicts']):
packages[a[0]]['rconflicts'].append(pkg) packages[a[0]]['rconflicts'].append(pkg)
# register packages which provides a virtual package
elif a[0] in provides: elif a[0] in provides:
for i in provides[a[0]]: for i in provides[a[0]]:
if i not in packages: continue if i not in packages: continue
@ -1304,28 +1309,42 @@ class Britney:
# Upgrade run # Upgrade run
# ----------- # -----------
def slist_subtract(self, base, sub):
res = []
for x in base:
if x not in sub: res.append(x)
return res
def newlyuninst(self, nuold, nunew): def newlyuninst(self, nuold, nunew):
"""Return a nuninst statstic with only new uninstallable packages
This method subtract the uninstallabla packages of the statistic
`nunew` from the statistic `nuold`.
It returns a dictionary with the architectures as keys and the list
of uninstallable packages as values.
"""
res = {} res = {}
for arch in self.options.architectures: for arch in nuold:
if arch not in nuold or arch not in nunew: if arch not in nunew: continue
continue res[arch] = [x for x in nunew[arch] if x not in nuold[arch]]
res[arch] = \
self.slist_subtract(nunew[arch], nuold[arch])
return res return res
def get_nuninst(self): def get_nuninst(self):
"""Return the uninstallability statistic for all the architectures
To calculate the uninstallability counters, the method checks the
installability of all the packages for all the architectures, and
tracking dependencies in a recursive way. The architecture
indipendent packages are checked only for the `nobreakall`
architectures.
It returns a dictionary with the architectures as keys and the list
of uninstallable packages as values.
"""
nuninst = {} nuninst = {}
# local copies for better performances # local copies for better performances
binaries = self.binaries['testing'] binaries = self.binaries['testing']
check_installable = self.check_installable check_installable = self.check_installable
# when a new uninstallable package is discovered, check again all the
# reverse dependencies and if they are uninstallable, too, call itself
# recursively
def add_nuninst(pkg, arch): def add_nuninst(pkg, arch):
if pkg not in nuninst[arch]: if pkg not in nuninst[arch]:
nuninst[arch].append(pkg) nuninst[arch].append(pkg)
@ -1337,11 +1356,15 @@ class Britney:
if not r: if not r:
add_nuninst(p, arch) add_nuninst(p, arch)
# for all the architectures
for arch in self.options.architectures: for arch in self.options.architectures:
# if it is in the nobreakall ones, check arch-indipendent packages too
if arch not in self.options.nobreakall_arches: if arch not in self.options.nobreakall_arches:
skip_archall = True skip_archall = True
else: skip_archall = False else: skip_archall = False
# check all the packages for this architecture, calling add_nuninst if a new
# uninstallable package is found
nuninst[arch] = [] nuninst[arch] = []
for pkg_name in binaries[arch][0]: for pkg_name in binaries[arch][0]:
pkg = binaries[arch][0][pkg_name] pkg = binaries[arch][0][pkg_name]
@ -1351,9 +1374,23 @@ class Britney:
if not r: if not r:
add_nuninst(pkg_name, arch) add_nuninst(pkg_name, arch)
# return the dictionary with the results
return nuninst return nuninst
def eval_nuninst(self, nuninst, original=None): def eval_nuninst(self, nuninst, original=None):
"""Return a string which represents the uninstallability counters
This method returns a string which represents the uninstallability
counters reading the uninstallability statistics `nuninst` and, if
present, merging the results with the `original` one.
An example of the output string is:
1+2: i-0:a-0:a-0:h-0:i-1:m-0:m-0:p-0:a-0:m-0:s-2:s-0
where the first part is the number of broken packages in non-break
architectures + the total number of broken packages for all the
architectures.
"""
res = [] res = []
total = 0 total = 0
totalbreak = 0 totalbreak = 0
@ -1371,22 +1408,33 @@ class Britney:
return "%d+%d: %s" % (total, totalbreak, ":".join(res)) return "%d+%d: %s" % (total, totalbreak, ":".join(res))
def eval_uninst(self, nuninst): def eval_uninst(self, nuninst):
res = "" """Return a string which represents the uninstallable packages
This method returns a string which represents the uninstallable
packages reading the uninstallability statistics `nuninst`.
An example of the output string is:
* i386: broken-pkg1, broken-pkg2
"""
parts = []
for arch in self.options.architectures: for arch in self.options.architectures:
if arch in nuninst and nuninst[arch] != []: if arch in nuninst and len(nuninst[arch]) > 0:
res = res + " * %s: %s\n" % (arch, parts.append(" * %s: %s\n" % (arch,", ".join(sorted(nuninst[arch]))))
", ".join(sorted(nuninst[arch]))) return "".join(parts)
return res
def check_installable(self, pkg, arch, suite, excluded=[], conflicts=False): def check_installable(self, pkg, arch, suite, excluded=[], conflicts=False):
"""Check if a package is installable """Check if a package is installable
This method analyzes the dependencies of the binary package specified This method analyzes the dependencies of the binary package specified
by the parameter `pkg' for the architecture `arch' within the suite by the parameter `pkg' for the architecture `arch' within the suite
`suite'. If the dependency can be satisfied in the given `suite`, `suite'. If the dependency can be satisfied in the given `suite` and
then the co-installability with conflicts handling is checked, too. `conflicts` parameter is True, then the co-installability with
conflicts handling is checked.
The dependency fields checked are Pre-Depends and Depends. The dependency fields checked are Pre-Depends and Depends.
The method returns a boolean which is True if the given package is
installable.
""" """
# retrieve the binary package from the specified suite and arch # retrieve the binary package from the specified suite and arch
binary_u = self.binaries[suite][arch][0][pkg] binary_u = self.binaries[suite][arch][0][pkg]
@ -1402,34 +1450,52 @@ class Britney:
# for every block of dependency (which is formed as conjunction of disconjunction) # for every block of dependency (which is formed as conjunction of disconjunction)
for block in parse_depends(binary_u[type]): for block in parse_depends(binary_u[type]):
# if the block is satisfied in testing, then skip the block # if the block is not satisfied, return False
solved, packages = get_dependency_solvers(block, arch, 'testing', excluded, strict=True) solved, packages = get_dependency_solvers(block, arch, 'testing', excluded, strict=True)
if solved: continue if not solved:
else:
return False return False
# otherwise, the package is installable (not considering conflicts) # otherwise, the package is installable (not considering conflicts)
# if we have been called inside UpgradeRun, then check conflicts before # if the conflicts handling is enabled, then check conflicts before
# saying that the package is really installable # saying that the package is really installable
if conflicts: if conflicts:
return self.check_conflicts(pkg, arch, {}, {}) return self.check_conflicts(pkg, arch, excluded, {}, {})
return True return True
def check_conflicts(self, pkg, arch, system, conflicts): 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 # local copies for better performances
binaries = self.binaries['testing'][arch] binaries = self.binaries['testing'][arch]
parse_depends = apt_pkg.ParseDepends parse_depends = apt_pkg.ParseDepends
check_depends = apt_pkg.CheckDep check_depends = apt_pkg.CheckDep
# unregister conflicts # unregister conflicts, local method to remove conflicts
# registered from a given package.
def unregister_conflicts(pkg, conflicts): def unregister_conflicts(pkg, conflicts):
for c in conflicts.keys(): for c in conflicts.keys():
if conflicts[c][3] == pkg: if conflicts[c][3] == pkg:
del conflicts[c] del conflicts[c]
# try to remove the offeding package # 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): def handle_conflict(pkg, source, system, conflicts):
# reached the top of the tree # reached the top of the tree
if not system[source][1]: if not system[source][1]:
@ -1442,41 +1508,51 @@ class Britney:
if satisfy(alt, [x for x in alternatives if x != alt], pkg_from=system[source][1], if satisfy(alt, [x for x in alternatives if x != alt], pkg_from=system[source][1],
system=system, conflicts=conflicts, excluded=[source]): system=system, conflicts=conflicts, excluded=[source]):
return (system, conflicts) return (system, conflicts)
# there are no alternatives, so remove the package which depends on it # there are no good alternatives, so remove the package which depends on it
return handle_conflict(pkg, system[source][1], system, conflicts) return handle_conflict(pkg, system[source][1], system, conflicts)
# try to satisfy the dependency tree # 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=[]): def satisfy(pkg, pkg_alt=None, pkg_from=None, system=system, conflicts=conflicts, excluded=[]):
# if it is real package and it is already installed, skip it and return True
# real package
if pkg in binaries[0]: if pkg in binaries[0]:
binary_u = binaries[0][pkg]
if pkg in system: if pkg in system:
return True return True
binary_u = binaries[0][pkg]
else: binary_u = None else: binary_u = None
# virtual package # if it is a virtual package
providers = [] providers = []
if pkg in binaries[1]: if pkg in binaries[1]:
providers = binaries[1][pkg] providers = binaries[1][pkg]
# it is both real and virtual, so the providers are alternatives
if binary_u: if binary_u:
providers = filter(lambda x: (not pkg_alt or x not in pkg_alt) and x != pkg, providers) providers = filter(lambda x: (not pkg_alt or x not in pkg_alt) and x != pkg, providers)
if not pkg_alt: if not pkg_alt:
pkg_alt = [] pkg_alt = []
pkg_alt.extend(providers) pkg_alt.extend(providers)
# try all the alternatives and if none of them suits, give up and return False
else: 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: if len(filter(lambda x: x in providers and x not in excluded, system)) > 0:
return True return True
for p in providers: 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 if p in excluded: continue
elif satisfy(p, [a for a in providers if a != p], pkg_from): elif satisfy(p, [a for a in providers if a != p], pkg_from):
return True return True
# if none of them suits, return False
return False return False
# missing package # if the package doesn't exist, return False
if not binary_u: return False if not binary_u: return False
# add the package to the system # install the package itto the system, recording which package required it
# FIXME: what if more than one package requires it???
system[pkg] = (pkg_alt, pkg_from) system[pkg] = (pkg_alt, pkg_from)
# register provided packages # register provided packages
@ -1489,6 +1565,9 @@ class Britney:
name, version, op, conflicting = conflicts[pkg] name, version, op, conflicting = conflicts[pkg]
if conflicting not in binary_u['provides'] and ( \ if conflicting not in binary_u['provides'] and ( \
op == '' and version == '' or check_depends(binary_u['version'], op, version)): 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 broking the system; if
# this is not possible, give up and return False
output = handle_conflict(pkg, conflicting, system.copy(), conflicts.copy()) output = handle_conflict(pkg, conflicting, system.copy(), conflicts.copy())
if output: if output:
system, conflicts = output system, conflicts = output
@ -1496,16 +1575,21 @@ class Britney:
del system[pkg] del system[pkg]
return False return False
# register conflicts # register conflicts from the just-installed package
if 'conflicts' in binary_u: if 'conflicts' in binary_u:
for block in map(operator.itemgetter(0), parse_depends(binary_u.get('conflicts', []))): for block in map(operator.itemgetter(0), parse_depends(binary_u.get('conflicts', []))):
name, version, op = block name, version, op = block
# skip conflicts for packages provided by itself
if name in binary_u['provides']: continue if name in binary_u['provides']: continue
# if the conflicting package is in the system (and it is not a self-conflict)
if block[0] != pkg and block[0] in system: if block[0] != pkg and block[0] in system:
if block[0] in binaries[0]: if block[0] in binaries[0]:
binary_c = binaries[0][block[0]] binary_c = binaries[0][block[0]]
else: binary_c = None else: binary_c = None
if op == '' and version == '' or binary_c and check_depends(binary_c['version'], op, version): 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 broking the system; if
# this is not possible, give up and return False
output = handle_conflict(name, pkg, system.copy(), conflicts.copy()) output = handle_conflict(name, pkg, system.copy(), conflicts.copy())
if output: if output:
system, conflicts = output system, conflicts = output
@ -1516,20 +1600,27 @@ class Britney:
# FIXME: what if more than one package conflicts with it??? # FIXME: what if more than one package conflicts with it???
conflicts[block[0]] = (name, version, op, pkg) conflicts[block[0]] = (name, version, op, pkg)
# its dependencies # list all its dependencies ...
dependencies = [] dependencies = []
for type in ('pre-depends', 'depends'): for type in ('pre-depends', 'depends'):
if type not in binary_u: continue if type not in binary_u: continue
dependencies.extend(parse_depends(binary_u[type])) dependencies.extend(parse_depends(binary_u[type]))
# go through them # ... and go through them
for block in dependencies: for block in dependencies:
# list the possible alternatives, in case of a conflict
alternatives = map(operator.itemgetter(0), block) alternatives = map(operator.itemgetter(0), block)
valid = False valid = False
for name, version, op in block: for name, version, op in block:
# if the package is broken, don't try it at all
if name in broken: continue
# 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): if name in system or satisfy(name, [a for a in alternatives if a != name], pkg):
valid = True valid = True
break 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: if not valid:
del system[pkg] del system[pkg]
unregister_conflicts(pkg, conflicts) unregister_conflicts(pkg, conflicts)
@ -1538,13 +1629,22 @@ class Britney:
return True return True
return False return False
# if all the blocks have been satisfied, the package is installable
return True return True
# check the package at the top of the tree # check the package at the top of the tree
return satisfy(pkg) return satisfy(pkg)
def doop_source(self, pkg): def doop_source(self, pkg):
"""Apply a change to the testing distribution as requested by `pkg`
This method apply the changes required by the action `pkg` tracking
them so it will be possible to revert them.
The method returns a list of the package name, the suite where the
package comes from, the list of packages affected by the change and
the dictionary undo which can be used to rollback the changes.
"""
undo = {'binaries': {}, 'sources': {}, 'virtual': {}, 'nvirtual': []} undo = {'binaries': {}, 'sources': {}, 'virtual': {}, 'nvirtual': []}
affected = [] affected = []
@ -1558,7 +1658,7 @@ class Britney:
if "/" in pkg: if "/" in pkg:
pkg_name, arch = pkg.split("/") pkg_name, arch = pkg.split("/")
suite = "unstable" suite = "unstable"
# removals = "-<source>", # removal of source packages = "-<source>",
elif pkg[0] == "-": elif pkg[0] == "-":
pkg_name = pkg[1:] pkg_name = pkg[1:]
suite = "testing" suite = "testing"
@ -1566,7 +1666,7 @@ class Britney:
elif pkg[0].endswith("_tpu"): elif pkg[0].endswith("_tpu"):
pkg_name = pkg[:-4] pkg_name = pkg[:-4]
suite = "tpu" suite = "tpu"
# normal = "<source>" # normal update of source packages = "<source>"
else: else:
pkg_name = pkg pkg_name = pkg
suite = "unstable" suite = "unstable"
@ -1575,22 +1675,30 @@ class Britney:
if not arch: if not arch:
if pkg_name in sources['testing']: if pkg_name in sources['testing']:
source = sources['testing'][pkg_name] source = sources['testing'][pkg_name]
# remove all the binaries
for p in source['binaries']: for p in source['binaries']:
binary, arch = p.split("/") binary, arch = p.split("/")
# save the old binary for undo
undo['binaries'][p] = binaries[arch][0][binary] undo['binaries'][p] = binaries[arch][0][binary]
# all the reverse dependencies are affected by the change
for j in binaries[arch][0][binary]['rdepends']: for j in binaries[arch][0][binary]['rdepends']:
key = (j, arch) key = (j, arch)
if key not in affected: affected.append(key) if key not in affected: affected.append(key)
# remove the provided virtual packages
for j in binaries[arch][0][binary]['provides']: for j in binaries[arch][0][binary]['provides']:
if j + "/" + arch not in undo['virtual']: key = j + "/" + arch
undo['virtual'][j + "/" + arch] = binaries[arch][1][j][:] if key not in undo['virtual']:
undo['virtual'][key] = binaries[arch][1][j][:]
binaries[arch][1][j].remove(binary) binaries[arch][1][j].remove(binary)
if len(binaries[arch][1][j]) == 0: if len(binaries[arch][1][j]) == 0:
del binaries[arch][1][j] del binaries[arch][1][j]
# finally, remove the binary package
del binaries[arch][0][binary] del binaries[arch][0][binary]
# remove the source package
undo['sources'][pkg_name] = source undo['sources'][pkg_name] = source
del sources['testing'][pkg_name] del sources['testing'][pkg_name]
else: else:
# the package didn't exist, so we mark it as to-be-removed in case of undo
undo['sources']['-' + pkg_name] = True undo['sources']['-' + pkg_name] = True
# single architecture update (eg. binNMU) # single architecture update (eg. binNMU)
@ -1607,37 +1715,57 @@ class Britney:
for p in source['binaries']: for p in source['binaries']:
binary, arch = p.split("/") binary, arch = p.split("/")
key = (binary, arch) key = (binary, arch)
# obviously, added/modified packages are affected
if key not in affected: affected.append(key) if key not in affected: affected.append(key)
# if the binary already exists (built from another source)
if binary in binaries[arch][0]: if binary in binaries[arch][0]:
# save the old binary package
undo['binaries'][p] = binaries[arch][0][binary] undo['binaries'][p] = binaries[arch][0][binary]
# all the reverse dependencies are affected by the change
for j in binaries[arch][0][binary]['rdepends']: for j in binaries[arch][0][binary]['rdepends']:
key = (j, arch) key = (j, arch)
if key not in affected: affected.append(key) if key not in affected: affected.append(key)
# all the reverse conflicts and their dependency tree are affected by the change
for j in binaries[arch][0][binary]['rconflicts']: for j in binaries[arch][0][binary]['rconflicts']:
key = (j, arch) key = (j, arch)
if key not in affected: affected.append(key) if key not in affected: affected.append(key)
for p in self.get_full_tree(j, arch, 'testing'): for p in self.get_full_tree(j, arch, 'testing'):
key = (p, arch) key = (p, arch)
if key not in affected: affected.append(key) if key not in affected: affected.append(key)
# add/update the binary package
binaries[arch][0][binary] = self.binaries[suite][arch][0][binary] binaries[arch][0][binary] = self.binaries[suite][arch][0][binary]
# register new provided packages
for j in binaries[arch][0][binary]['provides']: for j in binaries[arch][0][binary]['provides']:
key = j + "/" + arch
if j not in binaries[arch][1]: if j not in binaries[arch][1]:
undo['nvirtual'].append(j + "/" + arch) undo['nvirtual'].append(key)
binaries[arch][1][j] = [] binaries[arch][1][j] = []
elif j + "/" + arch not in undo['virtual']: elif key not in undo['virtual']:
undo['virtual'][j + "/" + arch] = binaries[arch][1][j][:] undo['virtual'][key] = binaries[arch][1][j][:]
binaries[arch][1][j].append(binary) binaries[arch][1][j].append(binary)
# all the reverse dependencies are affected by the change
for j in binaries[arch][0][binary]['rdepends']: for j in binaries[arch][0][binary]['rdepends']:
key = (j, arch) key = (j, arch)
if key not in affected: affected.append(key) if key not in affected: affected.append(key)
# FIXME: why not the conflicts and their tree, too?
# register reverse dependencies and conflicts for the new binary packages
for p in source['binaries']: for p in source['binaries']:
binary, arch = p.split("/") binary, arch = p.split("/")
self.register_reverses(binary, binaries[arch][0] , binaries[arch][1]) self.register_reverses(binary, binaries[arch][0] , binaries[arch][1])
# add/update the source package
sources['testing'][pkg_name] = sources[suite][pkg_name] sources['testing'][pkg_name] = sources[suite][pkg_name]
# return the package name, the suite, the list of affected packages and the undo dictionary
return (pkg_name, suite, affected, undo) return (pkg_name, suite, affected, undo)
def get_full_tree(self, pkg, arch, suite): def get_full_tree(self, pkg, arch, suite):
"""Calculate the full dependency tree for the given package
This method returns the full dependency tree for the package `pkg`,
inside the `arch` architecture for the suite `suite`.
"""
packages = [pkg] packages = [pkg]
binaries = self.binaries[suite][arch][0] binaries = self.binaries[suite][arch][0]
l = n = 0 l = n = 0
@ -1649,6 +1777,13 @@ class Britney:
return packages return packages
def iter_packages(self, packages, output): def iter_packages(self, packages, output):
"""Iter on the list of actions and apply them one-by-one
This method apply the changes from `packages` to testing, checking the uninstallability
counters for every action performed. If the action do not improve the it, it is reverted.
The method returns the new uninstallability counters and the remaining actions if the
final result is successful, otherwise (None, None).
"""
extra = [] extra = []
nuninst_comp = self.get_nuninst() nuninst_comp = self.get_nuninst()
@ -1663,6 +1798,7 @@ class Britney:
output.write("recur: [%s] %s %d/%d\n" % (",".join(self.selected), "", len(packages), len(extra))) output.write("recur: [%s] %s %d/%d\n" % (",".join(self.selected), "", len(packages), len(extra)))
# loop on the packages (or better, actions)
while packages: while packages:
pkg = packages.pop(0) pkg = packages.pop(0)
output.write("trying: %s\n" % (pkg)) output.write("trying: %s\n" % (pkg))
@ -1670,8 +1806,10 @@ class Britney:
better = True better = True
nuninst = {} nuninst = {}
# apply the changes
pkg_name, suite, affected, undo = self.doop_source(pkg) pkg_name, suite, affected, undo = self.doop_source(pkg)
# check the affected packages on all the architectures
for arch in ("/" in pkg and (pkg.split("/")[1],) or architectures): for arch in ("/" in pkg and (pkg.split("/")[1],) or architectures):
if arch not in nobreakall_arches: if arch not in nobreakall_arches:
skip_archall = True skip_archall = True
@ -1681,20 +1819,27 @@ class Britney:
broken = nuninst[arch][:] broken = nuninst[arch][:]
to_check = [x[0] for x in affected if x[1] == arch] to_check = [x[0] for x in affected if x[1] == arch]
# broken packages (first round)
old_broken = None old_broken = None
last_broken = None
while old_broken != broken: while old_broken != broken:
old_broken = broken[:] old_broken = broken[:]
for p in to_check: for p in to_check:
if p == last_broken: break
if p not in binaries[arch][0] or \ if p not in binaries[arch][0] or \
skip_archall and binaries[arch][0][p]['architecture'] == 'all': continue skip_archall and binaries[arch][0][p]['architecture'] == 'all': continue
r = check_installable(p, arch, 'testing', excluded=broken, conflicts=True) r = check_installable(p, arch, 'testing', excluded=broken, conflicts=True)
if not r and p not in broken: if not r and p not in broken:
last_broken = p
broken.append(p) broken.append(p)
elif r and p in nuninst[arch]: elif r and p in nuninst[arch]:
last_broken = p
broken.remove(p) broken.remove(p)
nuninst[arch].remove(p) nuninst[arch].remove(p)
# broken packages (second round, reverse dependencies of the first round)
l = 0 l = 0
last_broken = None
while l < len(broken): while l < len(broken):
l = len(broken) l = len(broken)
for j in broken: for j in broken:
@ -1704,17 +1849,23 @@ class Britney:
skip_archall and binaries[arch][0][p]['architecture'] == 'all': continue skip_archall and binaries[arch][0][p]['architecture'] == 'all': continue
r = check_installable(p, arch, 'testing', excluded=broken, conflicts=True) r = check_installable(p, arch, 'testing', excluded=broken, conflicts=True)
if not r and p not in broken: if not r and p not in broken:
l = -1
last_broken = j
broken.append(p) broken.append(p)
if l != -1 and last_broken == j: break
# update the uninstallability counter
for b in broken: for b in broken:
if b not in nuninst[arch]: if b not in nuninst[arch]:
nuninst[arch].append(b) nuninst[arch].append(b)
# if the uninstallability counter is worse than before, break the loop
if (("/" in pkg and arch not in new_arches) or \ if (("/" in pkg and arch not in new_arches) or \
(arch not in break_arches)) and len(nuninst[arch]) > len(nuninst_comp[arch]): (arch not in break_arches)) and len(nuninst[arch]) > len(nuninst_comp[arch]):
better = False better = False
break break
# check if the action improved the uninstallability counters
if better: if better:
self.selected.append(pkg) self.selected.append(pkg)
packages.extend(extra) packages.extend(extra)
@ -1756,9 +1907,11 @@ class Britney:
for p in undo['nvirtual']: for p in undo['nvirtual']:
j, arch = p.split("/") j, arch = p.split("/")
del binaries[arch][1][j] del binaries[arch][1][j]
for p in undo['virtual'].keys(): for p in undo['virtual']:
j, arch = p.split("/") j, arch = p.split("/")
binaries[arch][1][j] = undo['virtual'][p] if j[0] == '-':
del binaries[arch][1][j[1:]]
else: binaries[arch][1][j] = undo['virtual'][p]
output.write(" finish: [%s]\n" % ",".join(self.selected)) output.write(" finish: [%s]\n" % ",".join(self.selected))
output.write("endloop: %s\n" % (self.eval_nuninst(self.nuninst_orig))) output.write("endloop: %s\n" % (self.eval_nuninst(self.nuninst_orig)))
@ -1770,6 +1923,12 @@ class Britney:
return (nuninst_comp, extra) return (nuninst_comp, extra)
def do_all(self, output, maxdepth=0, init=None): def do_all(self, output, maxdepth=0, init=None):
"""Testing update runner
This method tries to update testing checking the uninstallability
counters before and after the actions to decide if the update was
successful or not.
"""
self.__log("> Calculating current uninstallability counters", type="I") self.__log("> Calculating current uninstallability counters", type="I")
nuninst_start = self.get_nuninst() nuninst_start = self.get_nuninst()
output.write("start: %s\n" % self.eval_nuninst(nuninst_start)) output.write("start: %s\n" % self.eval_nuninst(nuninst_start))

Loading…
Cancel
Save