mirror of
				https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
				synced 2025-11-04 10:34:05 +00:00 
			
		
		
		
	Extract a BinaryPackageUniverse from the InstallabilityTester
The InstallabilityTester is suffering from a lack of clear purpose because it serves multiple. This commit extracts most of one of these purposes into the BinaryPackageUniverse class while retaining the original API of the InstallabilityTester. Signed-off-by: Niels Thykier <niels@thykier.net>
This commit is contained in:
		
							parent
							
								
									b7fe352713
								
							
						
					
					
						commit
						530db5d3f7
					
				@ -18,6 +18,7 @@ from itertools import product
 | 
			
		||||
 | 
			
		||||
from britney2.utils import ifilter_except, iter_except, get_dependency_solvers
 | 
			
		||||
from britney2.installability.solver import InstallabilitySolver
 | 
			
		||||
from britney2.installability.universe import BinaryPackageRelation, BinaryPackageUniverse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def build_installability_tester(suite_info, archs):
 | 
			
		||||
@ -305,10 +306,11 @@ class InstallabilityTesterBuilder(object):
 | 
			
		||||
                if b in reverse_package_table:
 | 
			
		||||
                    del reverse_package_table[b]
 | 
			
		||||
 | 
			
		||||
        eqv_table = self._build_eqv_packages_table(package_table, reverse_package_table)
 | 
			
		||||
        relations, eqv_table = self._build_eqv_packages_table(package_table, reverse_package_table)
 | 
			
		||||
 | 
			
		||||
        return InstallabilitySolver(package_table,
 | 
			
		||||
                                    reverse_package_table,
 | 
			
		||||
        universe = BinaryPackageUniverse(relations)
 | 
			
		||||
 | 
			
		||||
        return InstallabilitySolver(universe,
 | 
			
		||||
                                    self._testing,
 | 
			
		||||
                                    self._broken,
 | 
			
		||||
                                    self._essentials,
 | 
			
		||||
@ -369,6 +371,8 @@ class InstallabilityTesterBuilder(object):
 | 
			
		||||
 | 
			
		||||
        find_eqv_table = defaultdict(list)
 | 
			
		||||
        eqv_table = {}
 | 
			
		||||
        relations = {}
 | 
			
		||||
        emptyset = frozenset()
 | 
			
		||||
 | 
			
		||||
        for pkg in reverse_package_table:
 | 
			
		||||
            rdeps = reverse_package_table[pkg][2]
 | 
			
		||||
@ -380,12 +384,21 @@ class InstallabilityTesterBuilder(object):
 | 
			
		||||
            ekey = (deps, con, rdeps)
 | 
			
		||||
            find_eqv_table[ekey].append(pkg)
 | 
			
		||||
 | 
			
		||||
        for pkg_list in find_eqv_table.values():
 | 
			
		||||
        for pkg_relations, pkg_list in find_eqv_table.items():
 | 
			
		||||
            rel = BinaryPackageRelation(pkg_relations[0], pkg_relations[1], reverse_package_table[pkg_list[0]][0])
 | 
			
		||||
            if len(pkg_list) < 2:
 | 
			
		||||
                relations[pkg_list[0]] = rel
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            eqv_set = frozenset(pkg_list)
 | 
			
		||||
            for pkg in pkg_list:
 | 
			
		||||
                eqv_table[pkg] = eqv_set
 | 
			
		||||
                relations[pkg] = rel
 | 
			
		||||
 | 
			
		||||
        return eqv_table
 | 
			
		||||
        for pkg, forward_relations in package_table.items():
 | 
			
		||||
            if pkg in relations:
 | 
			
		||||
                continue
 | 
			
		||||
            rel = BinaryPackageRelation(forward_relations[0], forward_relations[1], emptyset)
 | 
			
		||||
            relations[pkg] = rel
 | 
			
		||||
 | 
			
		||||
        return relations, eqv_table
 | 
			
		||||
 | 
			
		||||
@ -132,15 +132,10 @@ def compute_scc(graph):
 | 
			
		||||
 | 
			
		||||
class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, universe, revuniverse, testing, broken, essentials,
 | 
			
		||||
                 eqv_table):
 | 
			
		||||
    def __init__(self, universe, testing, broken, essentials, eqv_table):
 | 
			
		||||
        """Create a new installability solver
 | 
			
		||||
 | 
			
		||||
        universe is a dict mapping package tuples to their
 | 
			
		||||
        dependencies and conflicts.
 | 
			
		||||
 | 
			
		||||
        revuniverse is a dict mapping package tuples to their reverse
 | 
			
		||||
        dependencies and reverse conflicts.
 | 
			
		||||
        universe is a BinaryPackageUniverse.
 | 
			
		||||
 | 
			
		||||
        testing is a (mutable) set of package tuples that determines
 | 
			
		||||
        which of the packages in universe are currently in testing.
 | 
			
		||||
@ -152,13 +147,11 @@ class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
          - NB: arch:all packages are "re-mapped" to given architecture.
 | 
			
		||||
            (simplifies caches and dependency checking)
 | 
			
		||||
        """
 | 
			
		||||
        super().__init__(universe, revuniverse, testing,
 | 
			
		||||
                         broken, essentials, eqv_table)
 | 
			
		||||
        super().__init__(universe, testing, broken, essentials, eqv_table)
 | 
			
		||||
 | 
			
		||||
    def solve_groups(self, groups):
 | 
			
		||||
        sat_in_testing = self._testing.isdisjoint
 | 
			
		||||
        universe = self._universe
 | 
			
		||||
        revuniverse = self._revuniverse
 | 
			
		||||
        result = []
 | 
			
		||||
        emitted = set()
 | 
			
		||||
        queue = deque()
 | 
			
		||||
@ -191,9 +184,9 @@ class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
            oldcons = set()
 | 
			
		||||
            newcons = set()
 | 
			
		||||
            for r in rms:
 | 
			
		||||
                oldcons.update(universe[r][1])
 | 
			
		||||
                oldcons.update(universe.negative_dependencies_of(r))
 | 
			
		||||
            for a in adds:
 | 
			
		||||
                newcons.update(universe[a][1])
 | 
			
		||||
                newcons.update(universe.negative_dependencies_of(a))
 | 
			
		||||
            current = newcons & oldcons
 | 
			
		||||
            oldcons -= current
 | 
			
		||||
            newcons -= current
 | 
			
		||||
@ -213,11 +206,11 @@ class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
                    order[key]['before'].add(other)
 | 
			
		||||
                    order[other]['after'].add(key)
 | 
			
		||||
 | 
			
		||||
            for r in ifilter_only(revuniverse, rms):
 | 
			
		||||
            for r in rms:
 | 
			
		||||
                # The binaries have reverse dependencies in testing;
 | 
			
		||||
                # check if we can/should migrate them first.
 | 
			
		||||
                for rdep in revuniverse[r][0]:
 | 
			
		||||
                    for depgroup in universe[rdep][0]:
 | 
			
		||||
                for rdep in universe.reverse_dependencies_of(r):
 | 
			
		||||
                    for depgroup in universe.dependencies_of(rdep):
 | 
			
		||||
                        rigid = depgroup - going_out
 | 
			
		||||
                        if not sat_in_testing(rigid):
 | 
			
		||||
                            # (partly) satisfied by testing, assume it is okay
 | 
			
		||||
@ -236,7 +229,7 @@ class InstallabilitySolver(InstallabilityTester):
 | 
			
		||||
                # Check if this item should migrate before others
 | 
			
		||||
                # (e.g. because they depend on a new [version of a]
 | 
			
		||||
                # binary provided by this item).
 | 
			
		||||
                for depgroup in universe[a][0]:
 | 
			
		||||
                for depgroup in universe.dependencies_of(a):
 | 
			
		||||
                    rigid = depgroup - going_out
 | 
			
		||||
                    if not sat_in_testing(rigid):
 | 
			
		||||
                        # (partly) satisfied by testing, assume it is okay
 | 
			
		||||
 | 
			
		||||
@ -22,15 +22,10 @@ from britney2.utils import iter_except
 | 
			
		||||
 | 
			
		||||
class InstallabilityTester(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, universe, revuniverse, testing, broken, essentials,
 | 
			
		||||
                 eqv_table):
 | 
			
		||||
    def __init__(self, universe, testing, broken, essentials, eqv_table):
 | 
			
		||||
        """Create a new installability tester
 | 
			
		||||
 | 
			
		||||
        universe is a dict mapping package ids to their
 | 
			
		||||
        dependencies and conflicts.
 | 
			
		||||
 | 
			
		||||
        revuniverse is a table containing all packages with reverse
 | 
			
		||||
        relations mapping them to their reverse relations.
 | 
			
		||||
        universe is a BinaryPackageUniverse
 | 
			
		||||
 | 
			
		||||
        testing is a (mutable) set of package ids that determines
 | 
			
		||||
        which of the packages in universe are currently in testing.
 | 
			
		||||
@ -49,7 +44,6 @@ class InstallabilityTester(object):
 | 
			
		||||
        self._testing = testing
 | 
			
		||||
        self._broken = broken
 | 
			
		||||
        self._essentials = essentials
 | 
			
		||||
        self._revuniverse = revuniverse
 | 
			
		||||
        self._eqv_table = eqv_table
 | 
			
		||||
        self._stats = InstallabilityStats()
 | 
			
		||||
        logger_name = ".".join((self.__class__.__module__, self.__class__.__name__))
 | 
			
		||||
@ -122,10 +116,7 @@ class InstallabilityTester(object):
 | 
			
		||||
        :return: A set containing the package ids all of the reverse
 | 
			
		||||
        dependencies of the input package.  The result is suite agnostic.
 | 
			
		||||
        """
 | 
			
		||||
        revuniverse = self._revuniverse
 | 
			
		||||
        if pkg_id not in revuniverse:
 | 
			
		||||
            return frozenset()
 | 
			
		||||
        return revuniverse[pkg_id][0]
 | 
			
		||||
        return self._universe.reverse_dependencies_of(pkg_id)
 | 
			
		||||
 | 
			
		||||
    def negative_dependencies_of(self, pkg_id):
 | 
			
		||||
        """Returns the set of negative dependencies of a given package
 | 
			
		||||
@ -138,7 +129,7 @@ class InstallabilityTester(object):
 | 
			
		||||
        :return: A set containing the package ids all of the negative
 | 
			
		||||
        dependencies of the input package.  The result is suite agnostic.
 | 
			
		||||
        """
 | 
			
		||||
        return self._universe[pkg_id][1]
 | 
			
		||||
        return self._universe.negative_dependencies_of(pkg_id)
 | 
			
		||||
 | 
			
		||||
    def dependencies_of(self, pkg_id):
 | 
			
		||||
        """Returns the set of dependencies of a given package
 | 
			
		||||
@ -147,7 +138,7 @@ class InstallabilityTester(object):
 | 
			
		||||
        :return: A set containing the package ids all of the dependencies
 | 
			
		||||
        of the input package.  The result is suite agnostic.
 | 
			
		||||
        """
 | 
			
		||||
        return self._universe[pkg_id][0]
 | 
			
		||||
        return self._universe.dependencies_of(pkg_id)
 | 
			
		||||
 | 
			
		||||
    def any_of_these_are_in_testing(self, pkgs):
 | 
			
		||||
        """Test if at least one package of a given set is in testing
 | 
			
		||||
@ -214,7 +205,7 @@ class InstallabilityTester(object):
 | 
			
		||||
                # Removes a package from the "pseudo-essential set"
 | 
			
		||||
                del self._cache_ess[pkg_id.architecture]
 | 
			
		||||
 | 
			
		||||
            if pkg_id not in self._revuniverse:
 | 
			
		||||
            if not self._universe.reverse_dependencies_of(pkg_id):
 | 
			
		||||
                # no reverse relations - safe
 | 
			
		||||
                return True
 | 
			
		||||
            if pkg_id not in self._broken and pkg_id in self._cache_inst:
 | 
			
		||||
@ -483,9 +474,9 @@ class InstallabilityTester(object):
 | 
			
		||||
        # While we have guaranteed dependencies (in check), examine all
 | 
			
		||||
        # of them.
 | 
			
		||||
        for cur in iter_except(check.pop, IndexError):
 | 
			
		||||
            (deps, cons) = universe[cur]
 | 
			
		||||
            relations = universe.relations_of(cur)
 | 
			
		||||
 | 
			
		||||
            if cons:
 | 
			
		||||
            if relations.negative_dependencies:
 | 
			
		||||
                # Conflicts?
 | 
			
		||||
                if cur in never:
 | 
			
		||||
                    # cur adds a (reverse) conflict, so check if cur
 | 
			
		||||
@ -498,11 +489,11 @@ class InstallabilityTester(object):
 | 
			
		||||
                    return False
 | 
			
		||||
                # We must install cur for the package to be installable,
 | 
			
		||||
                # so "obviously" we can never choose any of its conflicts
 | 
			
		||||
                never.update(cons & testing)
 | 
			
		||||
                never.update(relations.negative_dependencies & testing)
 | 
			
		||||
 | 
			
		||||
            # depgroup can be satisfied by picking something that is
 | 
			
		||||
            # already in musts - lets pick that (again).  :)
 | 
			
		||||
            for depgroup in not_satisfied(deps):
 | 
			
		||||
            for depgroup in not_satisfied(relations.dependencies):
 | 
			
		||||
 | 
			
		||||
                # Of all the packages listed in the relation remove those that
 | 
			
		||||
                # are either:
 | 
			
		||||
@ -587,8 +578,9 @@ class InstallabilityTester(object):
 | 
			
		||||
                    for choice in not_satisfied(ess_choices):
 | 
			
		||||
                        b = False
 | 
			
		||||
                        for c in choice:
 | 
			
		||||
                            if universe[c][1] <= ess_never and \
 | 
			
		||||
                                    not any(not_satisfied(universe[c][0])):
 | 
			
		||||
                            relations = universe.relations_of(c)
 | 
			
		||||
                            if relations.negative_dependencies <= ess_never and \
 | 
			
		||||
                                    not any(not_satisfied(relations.dependencies)):
 | 
			
		||||
                                ess_base.append(c)
 | 
			
		||||
                                b = True
 | 
			
		||||
                                break
 | 
			
		||||
@ -599,7 +591,7 @@ class InstallabilityTester(object):
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            for x in start:
 | 
			
		||||
                ess_never.update(universe[x][1])
 | 
			
		||||
                ess_never.update(universe.negative_dependencies_of(x))
 | 
			
		||||
            self._cache_ess[arch] = (frozenset(start), frozenset(ess_never), frozenset(ess_choices))
 | 
			
		||||
 | 
			
		||||
        return self._cache_ess[arch]
 | 
			
		||||
@ -612,7 +604,7 @@ class InstallabilityTester(object):
 | 
			
		||||
 | 
			
		||||
        for pkg in universe:
 | 
			
		||||
            (pkg_name, pkg_version, pkg_arch) = pkg
 | 
			
		||||
            deps, con = universe[pkg]
 | 
			
		||||
            relations = universe.relations_of(pkg)
 | 
			
		||||
            arch_stats = graph_stats[pkg_arch]
 | 
			
		||||
 | 
			
		||||
            arch_stats.nodes += 1
 | 
			
		||||
@ -621,8 +613,8 @@ class InstallabilityTester(object):
 | 
			
		||||
                eqv = [e for e in eqv_table[pkg] if e.architecture == pkg_arch]
 | 
			
		||||
                arch_stats.eqv_nodes += len(eqv)
 | 
			
		||||
 | 
			
		||||
            arch_stats.add_dep_edges(deps)
 | 
			
		||||
            arch_stats.add_con_edges(con)
 | 
			
		||||
            arch_stats.add_dep_edges(relations.dependencies)
 | 
			
		||||
            arch_stats.add_con_edges(relations.negative_dependencies)
 | 
			
		||||
 | 
			
		||||
        for stat in graph_stats.values():
 | 
			
		||||
            stat.compute_all()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										115
									
								
								britney2/installability/universe.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								britney2/installability/universe.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,115 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright (C) 2012 Niels Thykier <niels@thykier.net>
 | 
			
		||||
 | 
			
		||||
# This program is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BinaryPackageRelation(object):
 | 
			
		||||
    """All relations of a given binary package"""
 | 
			
		||||
 | 
			
		||||
    __slots__ = ['dependencies', 'negative_dependencies', 'reverse_dependencies']
 | 
			
		||||
 | 
			
		||||
    def __init__(self, dependencies, negative_dependencies, reverse_dependencies):
 | 
			
		||||
        self.dependencies = dependencies
 | 
			
		||||
        self.negative_dependencies = negative_dependencies
 | 
			
		||||
        self.reverse_dependencies = reverse_dependencies
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BinaryPackageUniverse(object):
 | 
			
		||||
    """A "universe" of all binary packages and their relations
 | 
			
		||||
 | 
			
		||||
    The package universe is a read-only ("immutable") data structure
 | 
			
		||||
    that knows of all binary packages and their internal relations.
 | 
			
		||||
    The relations are either in Conjunctive Normal Form (CNF) represented
 | 
			
		||||
    via sets of sets of the package ids or simply sets of package ids.
 | 
			
		||||
 | 
			
		||||
    Being immutable, the universe does *not* track stateful data such
 | 
			
		||||
    as "which package is in what suite?" nor "is this package installable
 | 
			
		||||
    in that suite?".
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, relations):
 | 
			
		||||
        self._relations = relations
 | 
			
		||||
 | 
			
		||||
    def dependencies_of(self, pkg_id):
 | 
			
		||||
        """Returns the set of dependencies of a given package
 | 
			
		||||
 | 
			
		||||
        :param pkg_id: The BinaryPackageId of a binary package.
 | 
			
		||||
        :return: A set containing the package ids all of the dependencies
 | 
			
		||||
        of the input package in CNF.
 | 
			
		||||
        """
 | 
			
		||||
        return self._relations[pkg_id].dependencies
 | 
			
		||||
 | 
			
		||||
    def negative_dependencies_of(self, pkg_id):
 | 
			
		||||
        """Returns the set of negative dependencies of a given package
 | 
			
		||||
 | 
			
		||||
        Note that there is no "reverse_negative_dependencies_of" method,
 | 
			
		||||
        since negative dependencies have no "direction" unlike positive
 | 
			
		||||
        dependencies.
 | 
			
		||||
 | 
			
		||||
        :param pkg_id: The BinaryPackageId of a binary package.
 | 
			
		||||
        :return: A set containing the package ids all of the negative
 | 
			
		||||
        dependencies of the input package.
 | 
			
		||||
        """
 | 
			
		||||
        return self._relations[pkg_id].negative_dependencies
 | 
			
		||||
 | 
			
		||||
    def reverse_dependencies_of(self, pkg_id):
 | 
			
		||||
        """Returns the set of reverse dependencies of a given package
 | 
			
		||||
 | 
			
		||||
        Note that a package is considered a reverse dependency of the
 | 
			
		||||
        given package as long as at least one of its dependency relations
 | 
			
		||||
        *could* be satisfied by the given package.
 | 
			
		||||
 | 
			
		||||
        :param pkg_id: The BinaryPackageId of a binary package.
 | 
			
		||||
        :return: A set containing the package ids all of the reverse
 | 
			
		||||
        dependencies of the input package.
 | 
			
		||||
        """
 | 
			
		||||
        return self._relations[pkg_id].reverse_dependencies
 | 
			
		||||
 | 
			
		||||
    def are_equivalent(self, pkg_id1, pkg_id2):
 | 
			
		||||
        """Test if pkg_id1 and pkg_id2 are equivalent
 | 
			
		||||
 | 
			
		||||
        :param pkg_id1 The id of the first package
 | 
			
		||||
        :param pkg_id2 The id of the second package
 | 
			
		||||
        :return: True if pkg_id1 and pkg_id2 have the same "signature" in
 | 
			
		||||
        the package dependency graph (i.e. relations can not tell
 | 
			
		||||
        them apart semantically except for their name). Otherwise False.
 | 
			
		||||
 | 
			
		||||
        Note that this can return True even if pkg_id1 and pkg_id2 can
 | 
			
		||||
        tell each other apart.
 | 
			
		||||
        """
 | 
			
		||||
        return pkg_id2 in self.packages_equivalent_to(pkg_id1)
 | 
			
		||||
 | 
			
		||||
    def packages_equivalent_to(self, pkg_id):
 | 
			
		||||
        """Determine which packages are equivalent to a given package
 | 
			
		||||
 | 
			
		||||
        :param pkg_id: The BinaryPackageId of a binary package.
 | 
			
		||||
        :return: A frozenset of all package ids that are equivalent to the
 | 
			
		||||
        input package.  Note that this set always includes the input
 | 
			
		||||
        package assuming it is a known package.
 | 
			
		||||
        """
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    def relations_of(self, pkg_id):
 | 
			
		||||
        """Get the direct relations of a given packge
 | 
			
		||||
 | 
			
		||||
        :param pkg_id: The BinaryPackageId of a binary package.
 | 
			
		||||
        :return: A BinaryPackageRelation describing all known direct
 | 
			
		||||
        relations for the package.
 | 
			
		||||
        """
 | 
			
		||||
        return self._relations[pkg_id]
 | 
			
		||||
 | 
			
		||||
    def __contains__(self, pkg_id):
 | 
			
		||||
        return pkg_id in self._relations
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        yield from self._relations
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user