Coverage for britney2/installability/universe.py: 96%
42 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-06-17 09:00 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-06-17 09:00 +0000
1# Copyright (C) 2012 Niels Thykier <niels@thykier.net>
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
13from collections.abc import Iterator
14from dataclasses import dataclass
15from typing import TYPE_CHECKING
17if TYPE_CHECKING: 17 ↛ 18line 17 didn't jump to line 18 because the condition on line 17 was never true
18 from .. import BinaryPackageId
21@dataclass(slots=True, frozen=True, eq=False, repr=False)
22class BinaryPackageRelation:
23 """All relations of a given binary package"""
25 pkg_ids: frozenset["BinaryPackageId"]
26 dependencies: frozenset[frozenset["BinaryPackageId"]]
27 negative_dependencies: frozenset["BinaryPackageId"] | None
28 reverse_dependencies: set["BinaryPackageId"] | None
31class BinaryPackageUniverse:
32 """A "universe" of all binary packages and their relations
34 The package universe is a read-only ("immutable") data structure
35 that knows of all binary packages and their internal relations.
36 The relations are either in Conjunctive Normal Form (CNF) represented
37 via sets of sets of the package ids or simply sets of package ids.
39 Being immutable, the universe does *not* track stateful data such
40 as "which package is in what suite?" nor "is this package installable
41 in that suite?".
43 The universe also includes some packages that are considered "broken".
44 These packages have been identified to always be uninstallability
45 regardless of the selection of package available (e.g. the depend
46 on a non-existent package or has a relation that is impossible to
47 satisfy).
49 For these packages, the universe only tracks that they
50 exist and that they are broken. This implies that their relations
51 have been nulled into empty sets and they have been removed from
52 the relations of other packages. This optimizes analysis of the
53 universe on packages that is/can be installable at the expense
54 of a "minor" lie about the "broken" packages.
55 """
57 def __init__(
58 self,
59 relations: dict["BinaryPackageId", BinaryPackageRelation],
60 essential_packages: frozenset["BinaryPackageId"],
61 broken_packages: frozenset["BinaryPackageId"],
62 equivalent_packages: frozenset["BinaryPackageId"],
63 ) -> None:
64 self._relations = relations
65 self._essential_packages = essential_packages
66 self._broken_packages = broken_packages
67 self._equivalent_packages = equivalent_packages
69 def dependencies_of(
70 self, pkg_id: "BinaryPackageId"
71 ) -> frozenset[frozenset["BinaryPackageId"]]:
72 """Returns the set of dependencies of a given package
74 :return: A set containing the package ids all of the dependencies
75 of the input package in CNF.
76 """
77 return self._relations[pkg_id].dependencies
79 def negative_dependencies_of(
80 self, pkg_id: "BinaryPackageId"
81 ) -> frozenset["BinaryPackageId"]:
82 """Returns the set of negative dependencies of a given package
84 Note that there is no "reverse_negative_dependencies_of" method,
85 since negative dependencies have no "direction" unlike positive
86 dependencies.
88 :return: A set containing the package ids all of the negative
89 dependencies of the input package.
90 """
91 return self._relations[pkg_id].negative_dependencies or frozenset()
93 def reverse_dependencies_of(
94 self, pkg_id: "BinaryPackageId"
95 ) -> set["BinaryPackageId"]:
96 """Returns the set of reverse dependencies of a given package
98 Note that a package is considered a reverse dependency of the
99 given package as long as at least one of its dependency relations
100 *could* be satisfied by the given package.
102 :return: A set containing the package ids all of the reverse
103 dependencies of the input package.
104 """
105 return self._relations[pkg_id].reverse_dependencies or set()
107 def are_equivalent(
108 self, pkg_id1: "BinaryPackageId", pkg_id2: "BinaryPackageId"
109 ) -> bool:
110 """Test if pkg_id1 and pkg_id2 are equivalent
112 :param pkg_id1: The id of the first package
113 :param pkg_id2: The id of the second package
114 :return: True if pkg_id1 and pkg_id2 have the same "signature" in
115 the package dependency graph (i.e. relations can not tell
116 them apart semantically except for their name). Otherwise False.
118 Note that this can return True even if pkg_id1 and pkg_id2 can
119 tell each other apart.
120 """
121 return pkg_id2 in self.packages_equivalent_to(pkg_id1)
123 def packages_equivalent_to(
124 self, pkg_id: "BinaryPackageId"
125 ) -> frozenset["BinaryPackageId"]:
126 """Determine which packages are equivalent to a given package
128 :return: A frozenset of all package ids that are equivalent to the
129 input package.
130 """
131 return self._relations[pkg_id].pkg_ids
133 def relations_of(self, pkg_id: "BinaryPackageId") -> BinaryPackageRelation:
134 """Get the direct relations of a given package
136 :param pkg_id: The BinaryPackageId of a binary package.
137 :return: A BinaryPackageRelation describing all known direct
138 relations for the package.
139 """
140 return self._relations[pkg_id]
142 @property
143 def essential_packages(self) -> frozenset["BinaryPackageId"]:
144 """A frozenset of all "Essential: yes" binaries in the universe
146 :return: All of the binaries that are marked as essential.
147 """
148 return self._essential_packages
150 @property
151 def broken_packages(self) -> frozenset["BinaryPackageId"]:
152 """A frozenset of all broken binaries in the universe
154 :return: All binaries that are considered "broken" and had their relations nulled.
155 """
156 return self._broken_packages
158 @property
159 def equivalent_packages(self) -> frozenset["BinaryPackageId"]:
160 """A frozenset of all binary packages that are equivalent to at least one other package
162 The binary packages in this set has the property that "universe.packages_equivalent_to(pkg_id)"
163 will return a set of at least 2 or more elements for each of them.
165 :return: All packages that are equivalent to other packages.
166 """
167 return self._equivalent_packages
169 def __contains__(self, pkg_id: "BinaryPackageId") -> bool:
170 return pkg_id in self._relations
172 def __iter__(self) -> Iterator["BinaryPackageId"]:
173 yield from self._relations