mirror of
				https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
				synced 2025-11-04 10:34:05 +00:00 
			
		
		
		
	Merge trunk up to 2014-08-05
This commit is contained in:
		
						commit
						7c8fd39803
					
				
							
								
								
									
										323
									
								
								britney.py
									
									
									
									
									
								
							
							
						
						
									
										323
									
								
								britney.py
									
									
									
									
									
								
							@ -192,6 +192,7 @@ import urllib
 | 
			
		||||
 | 
			
		||||
import apt_pkg
 | 
			
		||||
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from functools import reduce, partial
 | 
			
		||||
from itertools import chain, ifilter, product
 | 
			
		||||
from operator import attrgetter
 | 
			
		||||
@ -220,7 +221,7 @@ from britney_util import (old_libraries_format, same_source, undo_changes,
 | 
			
		||||
                          read_nuninst, write_nuninst, write_heidi,
 | 
			
		||||
                          eval_uninst, newly_uninst, make_migrationitem,
 | 
			
		||||
                          write_excuses, write_heidi_delta, write_controlfiles,
 | 
			
		||||
                          old_libraries, ensuredir)
 | 
			
		||||
                          old_libraries, is_nuninst_asgood_generous, ensuredir)
 | 
			
		||||
from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
 | 
			
		||||
                   SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
 | 
			
		||||
                   PROVIDES, RDEPENDS, RCONFLICTS, MULTIARCH, ESSENTIAL)
 | 
			
		||||
@ -454,10 +455,12 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
                depends = []
 | 
			
		||||
                conflicts = []
 | 
			
		||||
                possible_dep_ranges = {}
 | 
			
		||||
 | 
			
		||||
                # We do not differ between depends and pre-depends
 | 
			
		||||
                if pkgdata[DEPENDS]:
 | 
			
		||||
                    depends.extend(apt_pkg.parse_depends(pkgdata[DEPENDS], False))
 | 
			
		||||
 | 
			
		||||
                if pkgdata[CONFLICTS]:
 | 
			
		||||
                    conflicts = apt_pkg.parse_depends(pkgdata[CONFLICTS], False)
 | 
			
		||||
 | 
			
		||||
@ -465,8 +468,10 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
                    for (al, dep) in [(depends, True), \
 | 
			
		||||
                                      (conflicts, False)]:
 | 
			
		||||
 | 
			
		||||
                        for block in al:
 | 
			
		||||
                            sat = set()
 | 
			
		||||
 | 
			
		||||
                            for dep_dist in binaries:
 | 
			
		||||
                                (_, pkgs) = solvers(block, arch, dep_dist)
 | 
			
		||||
                                for p in pkgs:
 | 
			
		||||
@ -483,7 +488,37 @@ class Britney(object):
 | 
			
		||||
                                        # is using §7.6.2
 | 
			
		||||
                                        relations.add_breaks(pt)
 | 
			
		||||
                            if dep:
 | 
			
		||||
                                relations.add_dependency_clause(sat)
 | 
			
		||||
                                if len(block) != 1:
 | 
			
		||||
                                    relations.add_dependency_clause(sat)
 | 
			
		||||
                                else:
 | 
			
		||||
                                    # This dependency might be a part
 | 
			
		||||
                                    # of a version-range a la:
 | 
			
		||||
                                    #
 | 
			
		||||
                                    #   Depends: pkg-a (>= 1),
 | 
			
		||||
                                    #            pkg-a (<< 2~)
 | 
			
		||||
                                    #
 | 
			
		||||
                                    # In such a case we want to reduce
 | 
			
		||||
                                    # that to a single clause for
 | 
			
		||||
                                    # efficiency.
 | 
			
		||||
                                    #
 | 
			
		||||
                                    # In theory, it could also happen
 | 
			
		||||
                                    # with "non-minimal" dependencies
 | 
			
		||||
                                    # a la:
 | 
			
		||||
                                    #
 | 
			
		||||
                                    #   Depends: pkg-a, pkg-a (>= 1)
 | 
			
		||||
                                    #
 | 
			
		||||
                                    # But dpkg is known to fix that up
 | 
			
		||||
                                    # at build time, so we will
 | 
			
		||||
                                    # probably only see "ranges" here.
 | 
			
		||||
                                    key = block[0][0]
 | 
			
		||||
                                    if key in possible_dep_ranges:
 | 
			
		||||
                                        possible_dep_ranges[key] &= sat
 | 
			
		||||
                                    else:
 | 
			
		||||
                                        possible_dep_ranges[key] = sat
 | 
			
		||||
 | 
			
		||||
                        if dep:
 | 
			
		||||
                            for clause in possible_dep_ranges.itervalues():
 | 
			
		||||
                                relations.add_dependency_clause(clause)
 | 
			
		||||
 | 
			
		||||
        self._inst_tester = builder.build()
 | 
			
		||||
 | 
			
		||||
@ -731,7 +766,7 @@ class Britney(object):
 | 
			
		||||
        The method returns a dictionary where the key is the binary package
 | 
			
		||||
        name and the value is the list of open RC bugs for it.
 | 
			
		||||
        """
 | 
			
		||||
        bugs = {}
 | 
			
		||||
        bugs = defaultdict(list)
 | 
			
		||||
        filename = os.path.join(basedir, "BugsV")
 | 
			
		||||
        self.__log("Loading RC bugs data from %s" % filename)
 | 
			
		||||
        try:
 | 
			
		||||
@ -742,7 +777,6 @@ class Britney(object):
 | 
			
		||||
                               type='W')
 | 
			
		||||
                    continue
 | 
			
		||||
                pkg = l[0]
 | 
			
		||||
                bugs.setdefault(pkg, [])
 | 
			
		||||
                bugs[pkg] += l[1].split(",")
 | 
			
		||||
        except IOError:
 | 
			
		||||
            self.__log("%s missing; skipping bug-based processing" % filename)
 | 
			
		||||
@ -1196,7 +1230,7 @@ class Britney(object):
 | 
			
		||||
            binary_u = self.binaries[suite][arch][0][pkg_name]
 | 
			
		||||
 | 
			
		||||
            # this is the source version for the new binary package
 | 
			
		||||
            pkgsv = self.binaries[suite][arch][0][pkg_name][SOURCEVER]
 | 
			
		||||
            pkgsv = binary_u[SOURCEVER]
 | 
			
		||||
 | 
			
		||||
            # if the new binary package is architecture-independent, then skip it
 | 
			
		||||
            if binary_u[ARCHITECTURE] == 'all':
 | 
			
		||||
@ -1972,14 +2006,6 @@ class Britney(object):
 | 
			
		||||
        return "%d+%d: %s" % (total, totalbreak, ":".join(res))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def is_nuninst_asgood_generous(self, old, new):
 | 
			
		||||
        diff = 0
 | 
			
		||||
        for arch in self.options.architectures:
 | 
			
		||||
            if arch in self.options.break_arches.split(): continue
 | 
			
		||||
            diff = diff + (len(new[arch]) - len(old[arch]))
 | 
			
		||||
        return diff <= 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _compute_groups(self, source_name, suite, migration_architecture,
 | 
			
		||||
                        is_removal, include_hijacked=False,
 | 
			
		||||
                        allow_smooth_updates=True,
 | 
			
		||||
@ -2159,9 +2185,9 @@ class Britney(object):
 | 
			
		||||
        This method applies the changes required by the action `item` 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 set of packages affected by the change and
 | 
			
		||||
        the dictionary undo which can be used to rollback the changes.
 | 
			
		||||
        The method returns a tuple containing a set of packages
 | 
			
		||||
        affected by the change (as (name, arch)-tuples) and the
 | 
			
		||||
        dictionary undo which can be used to rollback the changes.
 | 
			
		||||
        """
 | 
			
		||||
        undo = {'binaries': {}, 'sources': {}, 'virtual': {}, 'nvirtual': []}
 | 
			
		||||
 | 
			
		||||
@ -2169,39 +2195,63 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
        # local copies for better performances
 | 
			
		||||
        sources = self.sources
 | 
			
		||||
        binaries = self.binaries['testing']
 | 
			
		||||
        get_reverse_tree = partial(compute_reverse_tree, self.binaries["testing"])
 | 
			
		||||
        packages_t = self.binaries['testing']
 | 
			
		||||
        get_reverse_tree = partial(compute_reverse_tree, packages_t)
 | 
			
		||||
        inst_tester = self._inst_tester
 | 
			
		||||
        eqv_set = set()
 | 
			
		||||
 | 
			
		||||
        # remove all binary packages (if the source already exists)
 | 
			
		||||
        if item.architecture == 'source' or not item.is_removal:
 | 
			
		||||
            if item.package in sources['testing']:
 | 
			
		||||
                source = sources['testing'][item.package]
 | 
			
		||||
 | 
			
		||||
                _, bins, _ = self._compute_groups(item.package,
 | 
			
		||||
                                                  item.suite,
 | 
			
		||||
                                                  item.architecture,
 | 
			
		||||
                                                  item.is_removal,
 | 
			
		||||
                                                  removals=removals)
 | 
			
		||||
                updates, rms, _ = self._compute_groups(item.package,
 | 
			
		||||
                                                       item.suite,
 | 
			
		||||
                                                       item.architecture,
 | 
			
		||||
                                                       item.is_removal,
 | 
			
		||||
                                                       removals=removals)
 | 
			
		||||
 | 
			
		||||
                eqv_table = {}
 | 
			
		||||
 | 
			
		||||
                for binary, version, parch in rms:
 | 
			
		||||
                    key = (binary, parch)
 | 
			
		||||
                    eqv_table[key] = version
 | 
			
		||||
 | 
			
		||||
                for p1 in updates:
 | 
			
		||||
                    binary, _, parch = p1
 | 
			
		||||
                    key = (binary, parch)
 | 
			
		||||
                    old_version = eqv_table.get(key)
 | 
			
		||||
                    if old_version is not None:
 | 
			
		||||
                        p2 = (binary, old_version, parch)
 | 
			
		||||
                        if inst_tester.are_equivalent(p1, p2):
 | 
			
		||||
                            eqv_set.add(key)
 | 
			
		||||
 | 
			
		||||
                # remove all the binaries which aren't being smooth updated
 | 
			
		||||
                for bin_data in bins:
 | 
			
		||||
                    binary, _, parch = bin_data
 | 
			
		||||
                for rm_tuple in rms:
 | 
			
		||||
                    binary, version, parch = rm_tuple
 | 
			
		||||
                    p = binary + "/" + parch
 | 
			
		||||
                    binaries_t_a, provides_t_a = packages_t[parch]
 | 
			
		||||
                    pkey = (binary, parch)
 | 
			
		||||
 | 
			
		||||
                    pkg_data = binaries_t_a[binary]
 | 
			
		||||
                    # save the old binary for undo
 | 
			
		||||
                    undo['binaries'][p] = binaries[parch][0][binary]
 | 
			
		||||
                    # all the reverse dependencies are affected by the change
 | 
			
		||||
                    affected.update(get_reverse_tree(binary, parch))
 | 
			
		||||
                    undo['binaries'][p] = pkg_data
 | 
			
		||||
                    if pkey not in eqv_set:
 | 
			
		||||
                        # all the reverse dependencies are affected by
 | 
			
		||||
                        # the change
 | 
			
		||||
                        affected.update(get_reverse_tree(binary, parch))
 | 
			
		||||
 | 
			
		||||
                    # remove the provided virtual packages
 | 
			
		||||
                    for j in binaries[parch][0][binary][PROVIDES]:
 | 
			
		||||
                    for j in pkg_data[PROVIDES]:
 | 
			
		||||
                        key = j + "/" + parch
 | 
			
		||||
                        if key not in undo['virtual']:
 | 
			
		||||
                            undo['virtual'][key] = binaries[parch][1][j][:]
 | 
			
		||||
                        binaries[parch][1][j].remove(binary)
 | 
			
		||||
                        if len(binaries[parch][1][j]) == 0:
 | 
			
		||||
                            del binaries[parch][1][j]
 | 
			
		||||
                            undo['virtual'][key] = provides_t_a[j][:]
 | 
			
		||||
                        provides_t_a[j].remove(binary)
 | 
			
		||||
                        if not provides_t_a[j]:
 | 
			
		||||
                            del provides_t_a[j]
 | 
			
		||||
                    # finally, remove the binary package
 | 
			
		||||
                    version = binaries[parch][0][binary][VERSION]
 | 
			
		||||
                    del binaries[parch][0][binary]
 | 
			
		||||
                    self._inst_tester.remove_testing_binary(binary, version, parch)
 | 
			
		||||
                    del binaries_t_a[binary]
 | 
			
		||||
                    inst_tester.remove_testing_binary(binary, version, parch)
 | 
			
		||||
                # remove the source package
 | 
			
		||||
                if item.architecture == 'source':
 | 
			
		||||
                    undo['sources'][item.package] = source
 | 
			
		||||
@ -2212,37 +2262,47 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
        # single binary removal; used for clearing up after smooth
 | 
			
		||||
        # updates but not supported as a manual hint
 | 
			
		||||
        elif item.package in binaries[item.architecture][0]:
 | 
			
		||||
            undo['binaries'][item.package + "/" + item.architecture] = binaries[item.architecture][0][item.package]
 | 
			
		||||
        elif item.package in packages_t[item.architecture][0]:
 | 
			
		||||
            binaries_t_a = packages_t[item.architecture][0]
 | 
			
		||||
            undo['binaries'][item.package + "/" + item.architecture] = binaries_t_a[item.package]
 | 
			
		||||
            affected.update(get_reverse_tree(item.package, item.architecture))
 | 
			
		||||
            version = binaries[item.architecture][0][item.package][VERSION]
 | 
			
		||||
            del binaries[item.architecture][0][item.package]
 | 
			
		||||
            self._inst_tester.remove_testing_binary(item.package, version, item.architecture)
 | 
			
		||||
            version = binaries_t_a[item.package][VERSION]
 | 
			
		||||
            del binaries_t_a[item.package]
 | 
			
		||||
            inst_tester.remove_testing_binary(item.package, version, item.architecture)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # add the new binary packages (if we are not removing)
 | 
			
		||||
        if not item.is_removal:
 | 
			
		||||
            source = sources[item.suite][item.package]
 | 
			
		||||
            packages_s = self.binaries[item.suite]
 | 
			
		||||
            for p in source[BINARIES]:
 | 
			
		||||
                binary, parch = p.split("/")
 | 
			
		||||
                if item.architecture not in ['source', parch]: continue
 | 
			
		||||
                key = (binary, parch)
 | 
			
		||||
                binaries_t_a, provides_t_a = packages_t[parch]
 | 
			
		||||
                equivalent_replacement = key in eqv_set
 | 
			
		||||
 | 
			
		||||
                # obviously, added/modified packages are affected
 | 
			
		||||
                if key not in affected: affected.add(key)
 | 
			
		||||
                if not equivalent_replacement and key not in affected:
 | 
			
		||||
                    affected.add(key)
 | 
			
		||||
                # if the binary already exists in testing, it is currently
 | 
			
		||||
                # built by another source package. we therefore remove the
 | 
			
		||||
                # version built by the other source package, after marking
 | 
			
		||||
                # all of its reverse dependencies as affected
 | 
			
		||||
                if binary in binaries[parch][0]:
 | 
			
		||||
                if binary in binaries_t_a:
 | 
			
		||||
                    old_pkg_data = binaries_t_a[binary]
 | 
			
		||||
                    # save the old binary package
 | 
			
		||||
                    undo['binaries'][p] = binaries[parch][0][binary]
 | 
			
		||||
                    # all the reverse dependencies are affected by the change
 | 
			
		||||
                    affected.update(get_reverse_tree(binary, parch))
 | 
			
		||||
                    # all the reverse conflicts and their dependency tree are affected by the change
 | 
			
		||||
                    for j in binaries[parch][0][binary][RCONFLICTS]:
 | 
			
		||||
                        affected.update(get_reverse_tree(j, parch))
 | 
			
		||||
                    version = binaries[parch][0][binary][VERSION]
 | 
			
		||||
                    self._inst_tester.remove_testing_binary(binary, version, parch)
 | 
			
		||||
                    undo['binaries'][p] = old_pkg_data
 | 
			
		||||
                    if not equivalent_replacement:
 | 
			
		||||
                        # all the reverse dependencies are affected by
 | 
			
		||||
                        # the change
 | 
			
		||||
                        affected.update(get_reverse_tree(binary, parch))
 | 
			
		||||
                        # all the reverse conflicts and their
 | 
			
		||||
                        # dependency tree are affected by the change
 | 
			
		||||
                        for j in old_pkg_data[RCONFLICTS]:
 | 
			
		||||
                            affected.update(get_reverse_tree(j, parch))
 | 
			
		||||
                    old_version = old_pkg_data[VERSION]
 | 
			
		||||
                    inst_tester.remove_testing_binary(binary, old_version, parch)
 | 
			
		||||
                else:
 | 
			
		||||
                    # the binary isn't in testing, but it may have been at
 | 
			
		||||
                    # the start of the current hint and have been removed
 | 
			
		||||
@ -2258,23 +2318,26 @@ class Britney(object):
 | 
			
		||||
                    for (tundo, tpkg) in hint_undo:
 | 
			
		||||
                        if p in tundo['binaries']:
 | 
			
		||||
                            for rdep in tundo['binaries'][p][RDEPENDS]:
 | 
			
		||||
                                if rdep in binaries[parch][0] and rdep not in source[BINARIES]:
 | 
			
		||||
                                if rdep in binaries_t_a and rdep not in source[BINARIES]:
 | 
			
		||||
                                    affected.update(get_reverse_tree(rdep, parch))
 | 
			
		||||
                # add/update the binary package
 | 
			
		||||
                binaries[parch][0][binary] = self.binaries[item.suite][parch][0][binary]
 | 
			
		||||
                version = binaries[parch][0][binary][VERSION]
 | 
			
		||||
                self._inst_tester.add_testing_binary(binary, version, parch)
 | 
			
		||||
 | 
			
		||||
                # add/update the binary package from the source suite
 | 
			
		||||
                new_pkg_data = packages_s[parch][0][binary]
 | 
			
		||||
                new_version = new_pkg_data[VERSION]
 | 
			
		||||
                binaries_t_a[binary] = new_pkg_data
 | 
			
		||||
                inst_tester.add_testing_binary(binary, new_version, parch)
 | 
			
		||||
                # register new provided packages
 | 
			
		||||
                for j in binaries[parch][0][binary][PROVIDES]:
 | 
			
		||||
                for j in new_pkg_data[PROVIDES]:
 | 
			
		||||
                    key = j + "/" + parch
 | 
			
		||||
                    if j not in binaries[parch][1]:
 | 
			
		||||
                    if j not in provides_t_a:
 | 
			
		||||
                        undo['nvirtual'].append(key)
 | 
			
		||||
                        binaries[parch][1][j] = []
 | 
			
		||||
                        provides_t_a[j] = []
 | 
			
		||||
                    elif key not in undo['virtual']:
 | 
			
		||||
                        undo['virtual'][key] = binaries[parch][1][j][:]
 | 
			
		||||
                    binaries[parch][1][j].append(binary)
 | 
			
		||||
                # all the reverse dependencies are affected by the change
 | 
			
		||||
                affected.update(get_reverse_tree(binary, parch))
 | 
			
		||||
                        undo['virtual'][key] = provides_t_a[j][:]
 | 
			
		||||
                    provides_t_a[j].append(binary)
 | 
			
		||||
                if not equivalent_replacement:
 | 
			
		||||
                    # all the reverse dependencies are affected by the change
 | 
			
		||||
                    affected.update(get_reverse_tree(binary, parch))
 | 
			
		||||
 | 
			
		||||
            # register reverse dependencies and conflicts for the new binary packages
 | 
			
		||||
            if item.architecture == 'source':
 | 
			
		||||
@ -2282,14 +2345,14 @@ class Britney(object):
 | 
			
		||||
            else:
 | 
			
		||||
                ext = "/" + item.architecture
 | 
			
		||||
                pkg_iter = (p.split("/")[0] for p in source[BINARIES] if p.endswith(ext))
 | 
			
		||||
            register_reverses(binaries[parch][0], binaries[parch][1], iterator=pkg_iter)
 | 
			
		||||
            register_reverses(binaries_t_a, provides_t_a, iterator=pkg_iter)
 | 
			
		||||
 | 
			
		||||
            # add/update the source package
 | 
			
		||||
            if item.architecture == 'source':
 | 
			
		||||
                sources['testing'][item.package] = sources[item.suite][item.package]
 | 
			
		||||
 | 
			
		||||
        # return the package name, the suite, the list of affected packages and the undo dictionary
 | 
			
		||||
        return (item, affected, undo)
 | 
			
		||||
        return (affected, undo)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _check_packages(self, binaries, arch, affected, skip_archall, nuninst):
 | 
			
		||||
@ -2324,7 +2387,51 @@ class Britney(object):
 | 
			
		||||
                self._installability_test(p, version, arch, broken, to_check, nuninst_arch)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def iter_packages(self, packages, selected, hint=False, nuninst=None, lundo=None):
 | 
			
		||||
    def iter_packages_hint(self, hinted_packages, lundo=None):
 | 
			
		||||
        """Iter on hinted list of actions and apply them in one go
 | 
			
		||||
 | 
			
		||||
        This method applies the changes from "hinted_packages" to
 | 
			
		||||
        testing and computes the uninstallability counters after te
 | 
			
		||||
        actions are performed.
 | 
			
		||||
 | 
			
		||||
        The method returns the new uninstallability counters.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        removals = set()
 | 
			
		||||
        all_affected = set()
 | 
			
		||||
        nobreakall_arches = self.options.nobreakall_arches.split()
 | 
			
		||||
        binaries_t = self.binaries['testing']
 | 
			
		||||
        check_packages = partial(self._check_packages, binaries_t)
 | 
			
		||||
        # Deep copy nuninst (in case the hint is undone)
 | 
			
		||||
        nuninst = {k:v.copy() for k,v in self.nuninst_orig.iteritems()}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for item in hinted_packages:
 | 
			
		||||
            _, rms, _ = self._compute_groups(item.package, item.suite,
 | 
			
		||||
                                             item.architecture,
 | 
			
		||||
                                             item.is_removal,
 | 
			
		||||
                                             allow_smooth_updates=False)
 | 
			
		||||
            removals.update(rms)
 | 
			
		||||
 | 
			
		||||
        for item in hinted_packages:
 | 
			
		||||
            affected, undo = self.doop_source(item,
 | 
			
		||||
                                              removals=removals)
 | 
			
		||||
            all_affected.update(affected)
 | 
			
		||||
            if lundo is not None:
 | 
			
		||||
                lundo.append((undo,item))
 | 
			
		||||
 | 
			
		||||
        for arch in self.options.architectures:
 | 
			
		||||
            if arch not in nobreakall_arches:
 | 
			
		||||
                skip_archall = True
 | 
			
		||||
            else:
 | 
			
		||||
                skip_archall = False
 | 
			
		||||
 | 
			
		||||
            check_packages(arch, all_affected, skip_archall, nuninst)
 | 
			
		||||
 | 
			
		||||
        return nuninst
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def iter_packages(self, packages, selected, nuninst=None, lundo=None):
 | 
			
		||||
        """Iter on the list of actions and apply them one-by-one
 | 
			
		||||
 | 
			
		||||
        This method applies the changes from `packages` to testing, checking the uninstallability
 | 
			
		||||
@ -2353,29 +2460,14 @@ class Britney(object):
 | 
			
		||||
        dependencies = self.dependencies
 | 
			
		||||
        check_packages = partial(self._check_packages, binaries)
 | 
			
		||||
 | 
			
		||||
        # pre-process a hint batch
 | 
			
		||||
        pre_process = {}
 | 
			
		||||
        if selected and hint:
 | 
			
		||||
            removals = set()
 | 
			
		||||
            for item in selected:
 | 
			
		||||
                _, rms, _ = self._compute_groups(item.package, item.suite,
 | 
			
		||||
                                                 item.architecture,
 | 
			
		||||
                                                 item.is_removal,
 | 
			
		||||
                                                 allow_smooth_updates=False)
 | 
			
		||||
                removals.update(rms)
 | 
			
		||||
            for package in selected:
 | 
			
		||||
                pkg, affected, undo = self.doop_source(package,
 | 
			
		||||
                                                       removals=removals)
 | 
			
		||||
                pre_process[package] = (pkg, affected, undo)
 | 
			
		||||
 | 
			
		||||
        if lundo is None:
 | 
			
		||||
            lundo = []
 | 
			
		||||
        if not hint:
 | 
			
		||||
            self.output_write("recur: [%s] %s %d/%d\n" % ("", ",".join(x.uvname for x in selected), len(packages), len(extra)))
 | 
			
		||||
 | 
			
		||||
        self.output_write("recur: [%s] %s %d/%d\n" % ("", ",".join(x.uvname for x in selected), len(packages), len(extra)))
 | 
			
		||||
 | 
			
		||||
        # loop on the packages (or better, actions)
 | 
			
		||||
        while packages:
 | 
			
		||||
            pkg = packages.pop(0)
 | 
			
		||||
            item = packages.pop(0)
 | 
			
		||||
 | 
			
		||||
            # this is the marker for the first loop
 | 
			
		||||
            if not mark_passed and position < 0:
 | 
			
		||||
@ -2387,61 +2479,48 @@ class Britney(object):
 | 
			
		||||
            # defer packages if their dependency has been already skipped
 | 
			
		||||
            if not mark_passed:
 | 
			
		||||
                defer = False
 | 
			
		||||
                for p in dependencies.get(pkg, []):
 | 
			
		||||
                for p in dependencies.get(item, []):
 | 
			
		||||
                    if p in skipped:
 | 
			
		||||
                        deferred.append(make_migrationitem(pkg, self.sources))
 | 
			
		||||
                        skipped.append(make_migrationitem(pkg, self.sources))
 | 
			
		||||
                        deferred.append(item)
 | 
			
		||||
                        skipped.append(item)
 | 
			
		||||
                        defer = True
 | 
			
		||||
                        break
 | 
			
		||||
                if defer: continue
 | 
			
		||||
 | 
			
		||||
            if not hint:
 | 
			
		||||
                self.output_write("trying: %s\n" % (pkg.uvname))
 | 
			
		||||
            self.output_write("trying: %s\n" % (item.uvname))
 | 
			
		||||
 | 
			
		||||
            better = True
 | 
			
		||||
            nuninst = {}
 | 
			
		||||
 | 
			
		||||
            # apply the changes
 | 
			
		||||
            if pkg in pre_process:
 | 
			
		||||
                item, affected, undo = pre_process[pkg]
 | 
			
		||||
            else:
 | 
			
		||||
                item, affected, undo = self.doop_source(pkg, lundo)
 | 
			
		||||
            if hint:
 | 
			
		||||
                lundo.append((undo, item))
 | 
			
		||||
            affected, undo = self.doop_source(item, lundo)
 | 
			
		||||
 | 
			
		||||
            # check the affected packages on all the architectures
 | 
			
		||||
            for arch in (item.architecture == 'source' and architectures or (item.architecture,)):
 | 
			
		||||
                if arch not in nobreakall_arches:
 | 
			
		||||
                    skip_archall = True
 | 
			
		||||
                else: skip_archall = False
 | 
			
		||||
                else:
 | 
			
		||||
                    skip_archall = False
 | 
			
		||||
 | 
			
		||||
                nuninst[arch] = set(x for x in nuninst_comp[arch] if x in binaries[arch][0])
 | 
			
		||||
                nuninst[arch + "+all"] = set(x for x in nuninst_comp[arch + "+all"] if x in binaries[arch][0])
 | 
			
		||||
 | 
			
		||||
                check_packages(arch, affected, skip_archall, nuninst)
 | 
			
		||||
 | 
			
		||||
                # if we are processing hints, go ahead
 | 
			
		||||
                if hint:
 | 
			
		||||
                    nuninst_comp[arch] = nuninst[arch]
 | 
			
		||||
                    nuninst_comp[arch + "+all"] = nuninst[arch + "+all"]
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # if the uninstallability counter is worse than before, break the loop
 | 
			
		||||
                if ((item.architecture != 'source' and arch not in new_arches) or \
 | 
			
		||||
                    (arch not in break_arches)) and len(nuninst[arch]) > len(nuninst_comp[arch]):
 | 
			
		||||
                    better = False
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            # if we are processing hints or the package is already accepted, go ahead
 | 
			
		||||
            if hint or item in selected: continue
 | 
			
		||||
 | 
			
		||||
            # check if the action improved the uninstallability counters
 | 
			
		||||
            if better:
 | 
			
		||||
                lundo.append((undo, item))
 | 
			
		||||
                selected.append(pkg)
 | 
			
		||||
                selected.append(item)
 | 
			
		||||
                packages.extend(extra)
 | 
			
		||||
                extra = []
 | 
			
		||||
                self.output_write("accepted: %s\n" % (pkg.uvname))
 | 
			
		||||
                self.output_write("accepted: %s\n" % (item.uvname))
 | 
			
		||||
                self.output_write("   ori: %s\n" % (self.eval_nuninst(self.nuninst_orig)))
 | 
			
		||||
                self.output_write("   pre: %s\n" % (self.eval_nuninst(nuninst_comp)))
 | 
			
		||||
                self.output_write("   now: %s\n" % (self.eval_nuninst(nuninst, nuninst_comp)))
 | 
			
		||||
@ -2452,8 +2531,8 @@ class Britney(object):
 | 
			
		||||
                for k in nuninst:
 | 
			
		||||
                    nuninst_comp[k] = nuninst[k]
 | 
			
		||||
            else:
 | 
			
		||||
                self.output_write("skipped: %s (%d <- %d)\n" % (pkg.uvname, len(extra), len(packages)))
 | 
			
		||||
                self.output_write("    got: %s\n" % (self.eval_nuninst(nuninst, pkg.architecture != 'source' and nuninst_comp or None)))
 | 
			
		||||
                self.output_write("skipped: %s (%d <- %d)\n" % (item.uvname, len(extra), len(packages)))
 | 
			
		||||
                self.output_write("    got: %s\n" % (self.eval_nuninst(nuninst, item.architecture != 'source' and nuninst_comp or None)))
 | 
			
		||||
                self.output_write("    * %s: %s\n" % (arch, ", ".join(sorted(b for b in nuninst[arch] if b not in nuninst_comp[arch]))))
 | 
			
		||||
 | 
			
		||||
                extra.append(item)
 | 
			
		||||
@ -2463,9 +2542,6 @@ class Britney(object):
 | 
			
		||||
                # (local-scope) binaries is actually self.binaries["testing"] so we cannot use it here.
 | 
			
		||||
                undo_changes(single_undo, self._inst_tester, sources, self.binaries)
 | 
			
		||||
 | 
			
		||||
        # if we are processing hints, return now
 | 
			
		||||
        if hint:
 | 
			
		||||
            return (nuninst_comp, [])
 | 
			
		||||
 | 
			
		||||
        self.output_write(" finish: [%s]\n" % ",".join( x.uvname for x in selected ))
 | 
			
		||||
        self.output_write("endloop: %s\n" % (self.eval_nuninst(self.nuninst_orig)))
 | 
			
		||||
@ -2476,6 +2552,7 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
        return (nuninst_comp, extra)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def do_all(self, hinttype=None, init=None, actions=None):
 | 
			
		||||
        """Testing update runner
 | 
			
		||||
 | 
			
		||||
@ -2495,6 +2572,8 @@ class Britney(object):
 | 
			
		||||
        recurse = True
 | 
			
		||||
        lundo = None
 | 
			
		||||
        nuninst_end = None
 | 
			
		||||
        better = True
 | 
			
		||||
        extra = () # empty tuple
 | 
			
		||||
 | 
			
		||||
        if hinttype == "easy" or hinttype == "force-hint":
 | 
			
		||||
            force = hinttype == "force-hint"
 | 
			
		||||
@ -2519,7 +2598,11 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
        if init:
 | 
			
		||||
            # init => a hint (e.g. "easy") - so do the hint run
 | 
			
		||||
            (nuninst_end, extra) = self.iter_packages(init, selected, hint=True, lundo=lundo)
 | 
			
		||||
            nuninst_end = self.iter_packages_hint(selected, lundo=lundo)
 | 
			
		||||
            if recurse:
 | 
			
		||||
                # Ensure upgrade_me and selected do not overlap, if we
 | 
			
		||||
                # follow-up with a recurse ("hint"-hint).
 | 
			
		||||
                upgrade_me = [x for x in upgrade_me if x not in set(selected)]
 | 
			
		||||
 | 
			
		||||
        if recurse:
 | 
			
		||||
            # Either the main run or the recursive run of a "hint"-hint.
 | 
			
		||||
@ -2537,7 +2620,14 @@ class Britney(object):
 | 
			
		||||
                self.output_write(eval_uninst(self.options.architectures,
 | 
			
		||||
                                              newly_uninst(nuninst_start, nuninst_end)))
 | 
			
		||||
 | 
			
		||||
        if force or self.is_nuninst_asgood_generous(self.nuninst_orig, nuninst_end):
 | 
			
		||||
        if not force:
 | 
			
		||||
            break_arches = self.options.break_arches.split()
 | 
			
		||||
            better = is_nuninst_asgood_generous(self.options.architectures,
 | 
			
		||||
                                                self.nuninst_orig,
 | 
			
		||||
                                                nuninst_end,
 | 
			
		||||
                                                break_arches)
 | 
			
		||||
 | 
			
		||||
        if better:
 | 
			
		||||
            # Result accepted either by force or by being better than the original result.
 | 
			
		||||
            if recurse:
 | 
			
		||||
                self.output_write("Apparently successful\n")
 | 
			
		||||
@ -2559,7 +2649,7 @@ class Britney(object):
 | 
			
		||||
                if recurse:
 | 
			
		||||
                    self.upgrade_me = sorted(extra)
 | 
			
		||||
                else:
 | 
			
		||||
                    self.upgrade_me = [x for x in self.upgrade_me if x not in selected]
 | 
			
		||||
                    self.upgrade_me = [x for x in self.upgrade_me if x not in set(selected)]
 | 
			
		||||
                self.sort_actions()
 | 
			
		||||
        else:
 | 
			
		||||
            self.output_write("FAILED\n")
 | 
			
		||||
@ -2952,10 +3042,11 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
    def nuninst_arch_report(self, nuninst, arch):
 | 
			
		||||
        """Print a report of uninstallable packages for one architecture."""
 | 
			
		||||
        all = {}
 | 
			
		||||
        all = defaultdict(set)
 | 
			
		||||
        for p in nuninst[arch]:
 | 
			
		||||
            pkg = self.binaries['testing'][arch][0][p]
 | 
			
		||||
            all.setdefault((pkg[SOURCE], pkg[SOURCEVER]), set()).add(p)
 | 
			
		||||
            all[(pkg[SOURCE], pkg[SOURCEVER])].add(p)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        print '* %s' % (arch,)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -583,3 +583,24 @@ def old_libraries(sources, packages, same_source=same_source):
 | 
			
		||||
                migration = "-" + "/".join((pkg_name, arch, pkg[SOURCEVER]))
 | 
			
		||||
                removals.append(MigrationItem(migration))
 | 
			
		||||
    return removals
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_nuninst_asgood_generous(architectures, old, new, break_arches=frozenset()):
 | 
			
		||||
    """Compares the nuninst counters to see if they improved
 | 
			
		||||
 | 
			
		||||
    Given a list of architecters, the previous and the current nuninst
 | 
			
		||||
    counters, this function determines if the current nuninst counter
 | 
			
		||||
    is better than the previous one.  Optionally it also accepts a set
 | 
			
		||||
    of "break_arches", the nuninst counter for any architecture listed
 | 
			
		||||
    in this set are completely ignored.
 | 
			
		||||
 | 
			
		||||
    Returns True if the new nuninst counter is better than the
 | 
			
		||||
    previous.  Returns False otherwise.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    diff = 0
 | 
			
		||||
    for arch in architectures:
 | 
			
		||||
        if arch in break_arches:
 | 
			
		||||
            continue
 | 
			
		||||
        diff = diff + (len(new[arch]) - len(old[arch]))
 | 
			
		||||
    return diff <= 0
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
 | 
			
		||||
from britney_util import ifilter_except, iter_except
 | 
			
		||||
@ -28,7 +29,7 @@ class _RelationBuilder(object):
 | 
			
		||||
        self._new_breaks = set(binary_data[1])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_dependency_clause(self, or_clause):
 | 
			
		||||
    def add_dependency_clause(self, or_clause, frozenset=frozenset):
 | 
			
		||||
        """Add a dependency clause
 | 
			
		||||
 | 
			
		||||
        The clause must be a sequence of (name, version, architecture)
 | 
			
		||||
@ -48,12 +49,12 @@ class _RelationBuilder(object):
 | 
			
		||||
        binary = self._binary
 | 
			
		||||
        itbuilder = self._itbuilder
 | 
			
		||||
        package_table = itbuilder._package_table
 | 
			
		||||
        reverse_package_table = itbuilder._reverse_package_table
 | 
			
		||||
        okay = False
 | 
			
		||||
        for dep_tuple in clause:
 | 
			
		||||
            okay = True
 | 
			
		||||
            reverse_relations = itbuilder._reverse_relations(dep_tuple)
 | 
			
		||||
            reverse_relations[0].add(binary)
 | 
			
		||||
            rdeps, _, rdep_relations = itbuilder._reverse_relations(dep_tuple)
 | 
			
		||||
            rdeps.add(binary)
 | 
			
		||||
            rdep_relations.add(clause)
 | 
			
		||||
 | 
			
		||||
        self._new_deps.add(clause)
 | 
			
		||||
        if not okay:
 | 
			
		||||
@ -193,15 +194,17 @@ class InstallabilityTesterBuilder(object):
 | 
			
		||||
 | 
			
		||||
        if binary in self._reverse_package_table:
 | 
			
		||||
            return self._reverse_package_table[binary]
 | 
			
		||||
        rel = [set(), set()]
 | 
			
		||||
        rel = [set(), set(), set()]
 | 
			
		||||
        self._reverse_package_table[binary] = rel
 | 
			
		||||
        return rel
 | 
			
		||||
 | 
			
		||||
    def build(self):
 | 
			
		||||
        # Merge reverse conflicts with conflicts - this saves some
 | 
			
		||||
        # operations in _check_loop since we only have to check one
 | 
			
		||||
        # set (instead of two) and we remove a few duplicates here
 | 
			
		||||
        # and there.
 | 
			
		||||
        """Compile the installability tester
 | 
			
		||||
 | 
			
		||||
        This method will compile an installability tester from the
 | 
			
		||||
        information given and (where possible) try to optimise a
 | 
			
		||||
        few things.
 | 
			
		||||
        """
 | 
			
		||||
        package_table = self._package_table
 | 
			
		||||
        reverse_package_table = self._reverse_package_table
 | 
			
		||||
        intern_set = self._intern_set
 | 
			
		||||
@ -220,18 +223,26 @@ class InstallabilityTesterBuilder(object):
 | 
			
		||||
                    return False
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # Merge reverse conflicts with conflicts - this saves some
 | 
			
		||||
        # operations in _check_loop since we only have to check one
 | 
			
		||||
        # set (instead of two) and we remove a few duplicates here
 | 
			
		||||
        # and there.
 | 
			
		||||
        #
 | 
			
		||||
        # At the same time, intern the rdep sets
 | 
			
		||||
        for pkg in reverse_package_table:
 | 
			
		||||
            if pkg not in package_table:
 | 
			
		||||
                raise RuntimeError("%s/%s/%s referenced but not added!" % pkg)
 | 
			
		||||
            if not reverse_package_table[pkg][1]:
 | 
			
		||||
                # no rconflicts - ignore
 | 
			
		||||
                continue
 | 
			
		||||
            deps, con = package_table[pkg]
 | 
			
		||||
            if not con:
 | 
			
		||||
                con = intern_set(reverse_package_table[pkg][1])
 | 
			
		||||
            else:
 | 
			
		||||
                con = intern_set(con | reverse_package_table[pkg][1])
 | 
			
		||||
            package_table[pkg] = (deps, con)
 | 
			
		||||
            rdeps, rcon, rdep_relations = reverse_package_table[pkg]
 | 
			
		||||
            if rcon:
 | 
			
		||||
                if not con:
 | 
			
		||||
                    con = intern_set(rcon)
 | 
			
		||||
                else:
 | 
			
		||||
                    con = intern_set(con | rcon)
 | 
			
		||||
                package_table[pkg] = (deps, con)
 | 
			
		||||
            reverse_package_table[pkg] = (intern_set(rdeps), con,
 | 
			
		||||
                                          intern_set(rdep_relations))
 | 
			
		||||
 | 
			
		||||
        # Check if we can expand broken.
 | 
			
		||||
        for t in not_broken(iter_except(check.pop, KeyError)):
 | 
			
		||||
@ -301,8 +312,88 @@ class InstallabilityTesterBuilder(object):
 | 
			
		||||
                    # add all rdeps (except those already in the safe_set)
 | 
			
		||||
                    check.update(reverse_package_table[pkg][0] - safe_set)
 | 
			
		||||
 | 
			
		||||
        eqv_table = self._build_eqv_packages_table(package_table,
 | 
			
		||||
                                       reverse_package_table)
 | 
			
		||||
 | 
			
		||||
        return InstallabilitySolver(package_table,
 | 
			
		||||
                                    reverse_package_table,
 | 
			
		||||
                                    self._testing, self._broken,
 | 
			
		||||
                                    self._essentials, safe_set)
 | 
			
		||||
                                    self._essentials, safe_set,
 | 
			
		||||
                                    eqv_table)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _build_eqv_packages_table(self, package_table,
 | 
			
		||||
                                  reverse_package_table,
 | 
			
		||||
                                  frozenset=frozenset):
 | 
			
		||||
        """Attempt to build a table of equivalent packages
 | 
			
		||||
 | 
			
		||||
        This method attempts to create a table of packages that are
 | 
			
		||||
        equivalent (in terms of installability).  If two packages (A
 | 
			
		||||
        and B) are equivalent then testing the installability of A is
 | 
			
		||||
        the same as testing the installability of B.  This equivalency
 | 
			
		||||
        also applies to co-installability.
 | 
			
		||||
 | 
			
		||||
        The example cases:
 | 
			
		||||
        * aspell-*
 | 
			
		||||
        * ispell-*
 | 
			
		||||
 | 
			
		||||
        Cases that do *not* apply:
 | 
			
		||||
        * MTA's
 | 
			
		||||
 | 
			
		||||
        The theory:
 | 
			
		||||
 | 
			
		||||
        The packages A and B are equivalent iff:
 | 
			
		||||
 | 
			
		||||
          reverse_depends(A) == reverse_depends(B) AND
 | 
			
		||||
                conflicts(A) == conflicts(B)       AND
 | 
			
		||||
                  depends(A) == depends(B)
 | 
			
		||||
 | 
			
		||||
        Where "reverse_depends(X)" is the set of reverse dependencies
 | 
			
		||||
        of X, "conflicts(X)" is the set of negative dependencies of X
 | 
			
		||||
        (Breaks and Conflicts plus the reverse ones of those combined)
 | 
			
		||||
        and "depends(X)" is the set of strong dependencies of X
 | 
			
		||||
        (Depends and Pre-Depends combined).
 | 
			
		||||
 | 
			
		||||
        To be honest, we are actually equally interested another
 | 
			
		||||
        property as well, namely substitutability.  The package A can
 | 
			
		||||
        always used instead of B, iff:
 | 
			
		||||
 | 
			
		||||
          reverse_depends(A) >= reverse_depends(B) AND
 | 
			
		||||
                conflicts(A) <= conflicts(B)       AND
 | 
			
		||||
                  depends(A) == depends(B)
 | 
			
		||||
 | 
			
		||||
        (With the same definitions as above).  Note that equivalency
 | 
			
		||||
        is just a special-case of substitutability, where A and B can
 | 
			
		||||
        substitute each other (i.e. a two-way substituation).
 | 
			
		||||
 | 
			
		||||
        Finally, note that the "depends(A) == depends(B)" for
 | 
			
		||||
        substitutability is actually not a strict requirement.  There
 | 
			
		||||
        are cases where those sets are different without affecting the
 | 
			
		||||
        property.
 | 
			
		||||
        """
 | 
			
		||||
        # Despite talking about substitutability, the method currently
 | 
			
		||||
        # only finds the equivalence cases.  Lets leave
 | 
			
		||||
        # substitutability for a future version.
 | 
			
		||||
 | 
			
		||||
        find_eqv_table = defaultdict(list)
 | 
			
		||||
        eqv_table = {}
 | 
			
		||||
 | 
			
		||||
        for pkg in reverse_package_table:
 | 
			
		||||
            rdeps = reverse_package_table[pkg][2]
 | 
			
		||||
            if not rdeps:
 | 
			
		||||
                # we don't care for things without rdeps (because
 | 
			
		||||
                # it is not worth it)
 | 
			
		||||
                continue
 | 
			
		||||
            deps, con = package_table[pkg]
 | 
			
		||||
            ekey = (deps, con, rdeps)
 | 
			
		||||
            find_eqv_table[ekey].append(pkg)
 | 
			
		||||
 | 
			
		||||
        for pkg_list in find_eqv_table.itervalues():
 | 
			
		||||
            if len(pkg_list) < 2:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            eqv_set = frozenset(pkg_list)
 | 
			
		||||
            for pkg in pkg_list:
 | 
			
		||||
                eqv_table[pkg] = eqv_set
 | 
			
		||||
 | 
			
		||||
        return eqv_table
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ from britney_util import (ifilter_only, iter_except)
 | 
			
		||||
class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, universe, revuniverse, testing, broken, essentials,
 | 
			
		||||
                 safe_set):
 | 
			
		||||
                 safe_set, eqv_table):
 | 
			
		||||
        """Create a new installability solver
 | 
			
		||||
 | 
			
		||||
        universe is a dict mapping package tuples to their
 | 
			
		||||
@ -44,7 +44,7 @@ class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
            (simplifies caches and dependency checking)
 | 
			
		||||
        """
 | 
			
		||||
        InstallabilityTester.__init__(self, universe, revuniverse, testing,
 | 
			
		||||
                                      broken, essentials, safe_set)
 | 
			
		||||
                                      broken, essentials, safe_set, eqv_table)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def solve_groups(self, groups):
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ from britney_util import iter_except
 | 
			
		||||
class InstallabilityTester(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, universe, revuniverse, testing, broken, essentials,
 | 
			
		||||
                 safe_set):
 | 
			
		||||
                 safe_set, eqv_table):
 | 
			
		||||
        """Create a new installability tester
 | 
			
		||||
 | 
			
		||||
        universe is a dict mapping package tuples to their
 | 
			
		||||
@ -51,6 +51,7 @@ class InstallabilityTester(object):
 | 
			
		||||
        self._essentials = essentials
 | 
			
		||||
        self._revuniverse = revuniverse
 | 
			
		||||
        self._safe_set = safe_set
 | 
			
		||||
        self._eqv_table = eqv_table
 | 
			
		||||
 | 
			
		||||
        # Cache of packages known to be broken - we deliberately do not
 | 
			
		||||
        # include "broken" in it.  See _optimize for more info.
 | 
			
		||||
@ -80,11 +81,33 @@ class InstallabilityTester(object):
 | 
			
		||||
        check_inst = self._check_inst
 | 
			
		||||
        cbroken = self._cache_broken
 | 
			
		||||
        cache_inst = self._cache_inst
 | 
			
		||||
        tcopy = [x for x in self._testing]
 | 
			
		||||
        eqv_table = self._eqv_table
 | 
			
		||||
        testing = self._testing
 | 
			
		||||
        tcopy = [x for x in testing]
 | 
			
		||||
        for t in ifilterfalse(cache_inst.__contains__, tcopy):
 | 
			
		||||
            if t in cbroken:
 | 
			
		||||
                continue
 | 
			
		||||
            check_inst(t)
 | 
			
		||||
            res = check_inst(t)
 | 
			
		||||
            if t in eqv_table:
 | 
			
		||||
                eqv = (x for x in eqv_table[t] if x in testing)
 | 
			
		||||
                if res:
 | 
			
		||||
                    cache_inst.update(eqv)
 | 
			
		||||
                else:
 | 
			
		||||
                    eqv_set = frozenset(eqv)
 | 
			
		||||
                    testing -= eqv_set
 | 
			
		||||
                    cbroken |= eqv_set
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def are_equivalent(self, p1, p2):
 | 
			
		||||
        """Test if p1 and p2 are equivalent
 | 
			
		||||
 | 
			
		||||
        Returns True if p1 and p2 have the same "signature" in
 | 
			
		||||
        the package dependency graph (i.e. relations can not tell
 | 
			
		||||
        them appart sematically except for their name)
 | 
			
		||||
        """
 | 
			
		||||
        eqv_table = self._eqv_table
 | 
			
		||||
        return p1 in eqv_table and p2 in eqv_table[p1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_testing_binary(self, pkg_name, pkg_version, pkg_arch):
 | 
			
		||||
        """Add a binary package to "testing"
 | 
			
		||||
@ -195,6 +218,7 @@ class InstallabilityTester(object):
 | 
			
		||||
        testing = self._testing
 | 
			
		||||
        cbroken = self._cache_broken
 | 
			
		||||
        safe_set = self._safe_set
 | 
			
		||||
        eqv_table = self._eqv_table
 | 
			
		||||
 | 
			
		||||
        # Our installability verdict - start with "yes" and change if
 | 
			
		||||
        # prove otherwise.
 | 
			
		||||
@ -235,8 +259,9 @@ class InstallabilityTester(object):
 | 
			
		||||
            never.update(ess_never)
 | 
			
		||||
 | 
			
		||||
        # curry check_loop
 | 
			
		||||
        check_loop = partial(self._check_loop, universe, testing, musts,
 | 
			
		||||
                             never, choices, cbroken)
 | 
			
		||||
        check_loop = partial(self._check_loop, universe, testing,
 | 
			
		||||
                             eqv_table, musts, never, choices,
 | 
			
		||||
                             cbroken)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # Useful things to remember:
 | 
			
		||||
@ -258,7 +283,7 @@ class InstallabilityTester(object):
 | 
			
		||||
        #     of t via recursion (calls _check_inst).  In this case
 | 
			
		||||
        #     check and choices are not (always) empty.
 | 
			
		||||
 | 
			
		||||
        def _pick_choice(rebuild):
 | 
			
		||||
        def _pick_choice(rebuild, set=set, len=len):
 | 
			
		||||
            """Picks a choice from choices and updates rebuild.
 | 
			
		||||
 | 
			
		||||
            Prunes the choices and updates "rebuild" to reflect the
 | 
			
		||||
@ -317,18 +342,55 @@ class InstallabilityTester(object):
 | 
			
		||||
            last = next(choice) # pick one to go last
 | 
			
		||||
            for p in choice:
 | 
			
		||||
                musts_copy = musts.copy()
 | 
			
		||||
                never_copy = never.copy()
 | 
			
		||||
                choices_copy = choices.copy()
 | 
			
		||||
                if self._check_inst(p, musts_copy, never_copy, choices_copy):
 | 
			
		||||
                never_tmp = set()
 | 
			
		||||
                choices_tmp = set()
 | 
			
		||||
                check_tmp = set([p])
 | 
			
		||||
                if not self._check_loop(universe, testing, eqv_table,
 | 
			
		||||
                                        musts_copy, never_tmp,
 | 
			
		||||
                                        choices_tmp, cbroken,
 | 
			
		||||
                                        check_tmp):
 | 
			
		||||
                    # p cannot be chosen/is broken (unlikely, but ...)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # Test if we can pick p without any consequences.
 | 
			
		||||
                # - when we can, we avoid a backtrack point.
 | 
			
		||||
                if never_tmp <= never and choices_tmp <= rebuild:
 | 
			
		||||
                    # we can pick p without picking up new conflicts
 | 
			
		||||
                    # or unresolved choices.  Therefore we commit to
 | 
			
		||||
                    # using p.
 | 
			
		||||
                    #
 | 
			
		||||
                    # NB: Optimally, we would go to the start of this
 | 
			
		||||
                    # routine, but to conserve stack-space, we return
 | 
			
		||||
                    # and expect to be called again later.
 | 
			
		||||
                    musts.update(musts_copy)
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
                if not musts.isdisjoint(never_tmp):
 | 
			
		||||
                    # If we pick p, we will definitely end up making
 | 
			
		||||
                    # t uninstallable, so p is a no-go.
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # We are not sure that p is safe, setup a backtrack
 | 
			
		||||
                # point and recurse.
 | 
			
		||||
                never_tmp |= never
 | 
			
		||||
                choices_tmp |= rebuild
 | 
			
		||||
                if self._check_inst(p, musts_copy, never_tmp,
 | 
			
		||||
                                    choices_tmp):
 | 
			
		||||
                    # Success, p was a valid choice and made it all
 | 
			
		||||
                    # installable
 | 
			
		||||
                    return True
 | 
			
		||||
                # If we get here, we failed to find something that would satisfy choice (without breaking
 | 
			
		||||
                # the installability of t).  This means p cannot be used to satisfy the dependencies, so
 | 
			
		||||
                # pretend to conflict with it - hopefully it will reduce future choices.
 | 
			
		||||
 | 
			
		||||
                # If we get here, we failed to find something that
 | 
			
		||||
                # would satisfy choice (without breaking the
 | 
			
		||||
                # installability of t).  This means p cannot be used
 | 
			
		||||
                # to satisfy the dependencies, so pretend to conflict
 | 
			
		||||
                # with it - hopefully it will reduce future choices.
 | 
			
		||||
                never.add(p)
 | 
			
		||||
 | 
			
		||||
            # Optimization for the last case; avoid the recursive call and just
 | 
			
		||||
            # assume the last will lead to a solution.  If it doesn't there is
 | 
			
		||||
            # no solution and if it does, we don't have to back-track anyway.
 | 
			
		||||
            # Optimization for the last case; avoid the recursive call
 | 
			
		||||
            # and just assume the last will lead to a solution.  If it
 | 
			
		||||
            # doesn't there is no solution and if it does, we don't
 | 
			
		||||
            # have to back-track anyway.
 | 
			
		||||
            check.add(last)
 | 
			
		||||
            musts.add(last)
 | 
			
		||||
            return False
 | 
			
		||||
@ -359,8 +421,9 @@ class InstallabilityTester(object):
 | 
			
		||||
        return verdict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _check_loop(self, universe, testing, musts, never,
 | 
			
		||||
                    choices, cbroken, check):
 | 
			
		||||
    def _check_loop(self, universe, testing, eqv_table, musts, never,
 | 
			
		||||
                    choices, cbroken, check, len=len,
 | 
			
		||||
                    frozenset=frozenset):
 | 
			
		||||
        """Finds all guaranteed dependencies via "check".
 | 
			
		||||
 | 
			
		||||
        If it returns False, t is not installable.  If it returns True
 | 
			
		||||
@ -368,8 +431,6 @@ class InstallabilityTester(object):
 | 
			
		||||
        returns True, then t is installable.
 | 
			
		||||
        """
 | 
			
		||||
        # Local variables for faster access...
 | 
			
		||||
        l = len
 | 
			
		||||
        fset = frozenset
 | 
			
		||||
        not_satisfied = partial(ifilter, musts.isdisjoint)
 | 
			
		||||
 | 
			
		||||
        # While we have guaranteed dependencies (in check), examine all
 | 
			
		||||
@ -401,9 +462,9 @@ class InstallabilityTester(object):
 | 
			
		||||
                #  - not in testing
 | 
			
		||||
                #  - known to be broken (by cache)
 | 
			
		||||
                #  - in never
 | 
			
		||||
                candidates = fset((depgroup & testing) - never)
 | 
			
		||||
                candidates = frozenset((depgroup & testing) - never)
 | 
			
		||||
 | 
			
		||||
                if l(candidates) == 0:
 | 
			
		||||
                if len(candidates) == 0:
 | 
			
		||||
                    # We got no candidates to satisfy it - this
 | 
			
		||||
                    # package cannot be installed with the current
 | 
			
		||||
                    # testing
 | 
			
		||||
@ -413,21 +474,43 @@ class InstallabilityTester(object):
 | 
			
		||||
                        cbroken.add(cur)
 | 
			
		||||
                        testing.remove(cur)
 | 
			
		||||
                    return False
 | 
			
		||||
                if l(candidates) == 1:
 | 
			
		||||
                if len(candidates) == 1:
 | 
			
		||||
                    # only one possible solution to this choice and we
 | 
			
		||||
                    # haven't seen it before
 | 
			
		||||
                    check.update(candidates)
 | 
			
		||||
                    musts.update(candidates)
 | 
			
		||||
                else:
 | 
			
		||||
                    possible_eqv = set(x for x in candidates if x in eqv_table)
 | 
			
		||||
                    if len(possible_eqv) > 1:
 | 
			
		||||
                        # Exploit equivalency to reduce the number of
 | 
			
		||||
                        # candidates if possible.  Basically, this
 | 
			
		||||
                        # code maps "similar" candidates into a single
 | 
			
		||||
                        # candidate that will give a identical result
 | 
			
		||||
                        # to any other candidate it eliminates.
 | 
			
		||||
                        #
 | 
			
		||||
                        # See InstallabilityTesterBuilder's
 | 
			
		||||
                        # _build_eqv_packages_table method for more
 | 
			
		||||
                        # information on how this works.
 | 
			
		||||
                        new_cand = set(x for x in candidates if x not in possible_eqv)
 | 
			
		||||
                        for chosen in iter_except(possible_eqv.pop, KeyError):
 | 
			
		||||
                            new_cand.add(chosen)
 | 
			
		||||
                            possible_eqv -= eqv_table[chosen]
 | 
			
		||||
                        if len(new_cand) == 1:
 | 
			
		||||
                            check.update(new_cand)
 | 
			
		||||
                            musts.update(new_cand)
 | 
			
		||||
                            continue
 | 
			
		||||
                        candidates = frozenset(new_cand)
 | 
			
		||||
                    # defer this choice till later
 | 
			
		||||
                    choices.add(candidates)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _get_min_pseudo_ess_set(self, arch):
 | 
			
		||||
        if arch not in self._cache_ess:
 | 
			
		||||
            # The minimal essential set cache is not present -
 | 
			
		||||
            # compute it now.
 | 
			
		||||
            testing = self._testing
 | 
			
		||||
            eqv_table = self._eqv_table
 | 
			
		||||
            cbroken = self._cache_broken
 | 
			
		||||
            universe = self._universe
 | 
			
		||||
            safe_set = self._safe_set
 | 
			
		||||
@ -439,8 +522,9 @@ class InstallabilityTester(object):
 | 
			
		||||
            not_satisified = partial(ifilter, start.isdisjoint)
 | 
			
		||||
 | 
			
		||||
            while ess_base:
 | 
			
		||||
                self._check_loop(universe, testing, start, ess_never,\
 | 
			
		||||
                                     ess_choices, cbroken, ess_base)
 | 
			
		||||
                self._check_loop(universe, testing, eqv_table,
 | 
			
		||||
                                 start, ess_never, ess_choices,
 | 
			
		||||
                                 cbroken, ess_base)
 | 
			
		||||
                if ess_choices:
 | 
			
		||||
                    # Try to break choices where possible
 | 
			
		||||
                    nchoice = set()
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user