mirror of
https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
synced 2025-02-23 19:31:55 +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