Coverage for britney2/installability/universe.py: 96%
42 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-17 17:32 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-17 17:32 +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 typing import TYPE_CHECKING
16if TYPE_CHECKING: 16 ↛ 17line 16 didn't jump to line 17 because the condition on line 16 was never true
17 from .. import BinaryPackageId
20class BinaryPackageRelation:
21 """All relations of a given binary package"""
23 __slots__ = [
24 "pkg_ids",
25 "dependencies",
26 "negative_dependencies",
27 "reverse_dependencies",
28 ]
30 def __init__(
31 self,
32 pkg_ids: frozenset["BinaryPackageId"],
33 dependencies: frozenset[frozenset["BinaryPackageId"]],
34 negative_dependencies: frozenset["BinaryPackageId"],
35 reverse_dependencies: set["BinaryPackageId"],
36 ) -> None:
37 self.pkg_ids = pkg_ids
38 self.dependencies = dependencies
39 self.negative_dependencies = negative_dependencies
40 self.reverse_dependencies = reverse_dependencies
43class BinaryPackageUniverse:
44 """A "universe" of all binary packages and their relations
46 The package universe is a read-only ("immutable") data structure
47 that knows of all binary packages and their internal relations.
48 The relations are either in Conjunctive Normal Form (CNF) represented
49 via sets of sets of the package ids or simply sets of package ids.
51 Being immutable, the universe does *not* track stateful data such
52 as "which package is in what suite?" nor "is this package installable
53 in that suite?".
55 The universe also includes some packages that are considered "broken".
56 These packages have been identified to always be uninstallability
57 regardless of the selection of package available (e.g. the depend
58 on a non-existent package or has a relation that is impossible to
59 satisfy).
61 For these packages, the universe only tracks that they
62 exist and that they are broken. This implies that their relations
63 have been nulled into empty sets and they have been removed from
64 the relations of other packages. This optimizes analysis of the
65 universe on packages that is/can be installable at the expense
66 of a "minor" lie about the "broken" packages.
67 """
69 def __init__(
70 self,
71 relations: dict["BinaryPackageId", BinaryPackageRelation],
72 essential_packages: frozenset["BinaryPackageId"],
73 broken_packages: frozenset["BinaryPackageId"],
74 equivalent_packages: frozenset["BinaryPackageId"],
75 ) -> None:
76 self._relations = relations
77 self._essential_packages = essential_packages
78 self._broken_packages = broken_packages
79 self._equivalent_packages = equivalent_packages
81 def dependencies_of(
82 self, pkg_id: "BinaryPackageId"
83 ) -> frozenset[frozenset["BinaryPackageId"]]:
84 """Returns the set of dependencies of a given package
86 :return: A set containing the package ids all of the dependencies
87 of the input package in CNF.
88 """
89 return self._relations[pkg_id].dependencies
91 def negative_dependencies_of(
92 self, pkg_id: "BinaryPackageId"
93 ) -> frozenset["BinaryPackageId"]:
94 """Returns the set of negative dependencies of a given package
96 Note that there is no "reverse_negative_dependencies_of" method,
97 since negative dependencies have no "direction" unlike positive
98 dependencies.
100 :return: A set containing the package ids all of the negative
101 dependencies of the input package.
102 """
103 return self._relations[pkg_id].negative_dependencies
105 def reverse_dependencies_of(
106 self, pkg_id: "BinaryPackageId"
107 ) -> set["BinaryPackageId"]:
108 """Returns the set of reverse dependencies of a given package
110 Note that a package is considered a reverse dependency of the
111 given package as long as at least one of its dependency relations
112 *could* be satisfied by the given package.
114 :return: A set containing the package ids all of the reverse
115 dependencies of the input package.
116 """
117 return self._relations[pkg_id].reverse_dependencies
119 def are_equivalent(
120 self, pkg_id1: "BinaryPackageId", pkg_id2: "BinaryPackageId"
121 ) -> bool:
122 """Test if pkg_id1 and pkg_id2 are equivalent
124 :param pkg_id1: The id of the first package
125 :param pkg_id2: The id of the second package
126 :return: True if pkg_id1 and pkg_id2 have the same "signature" in
127 the package dependency graph (i.e. relations can not tell
128 them apart semantically except for their name). Otherwise False.
130 Note that this can return True even if pkg_id1 and pkg_id2 can
131 tell each other apart.
132 """
133 return pkg_id2 in self.packages_equivalent_to(pkg_id1)
135 def packages_equivalent_to(
136 self, pkg_id: "BinaryPackageId"
137 ) -> frozenset["BinaryPackageId"]:
138 """Determine which packages are equivalent to a given package
140 :return: A frozenset of all package ids that are equivalent to the
141 input package.
142 """
143 return self._relations[pkg_id].pkg_ids
145 def relations_of(self, pkg_id: "BinaryPackageId") -> BinaryPackageRelation:
146 """Get the direct relations of a given package
148 :param pkg_id: The BinaryPackageId of a binary package.
149 :return: A BinaryPackageRelation describing all known direct
150 relations for the package.
151 """
152 return self._relations[pkg_id]
154 @property
155 def essential_packages(self) -> frozenset["BinaryPackageId"]:
156 """A frozenset of all "Essential: yes" binaries in the universe
158 :return: All of the binaries that are marked as essential.
159 """
160 return self._essential_packages
162 @property
163 def broken_packages(self) -> frozenset["BinaryPackageId"]:
164 """A frozenset of all broken binaries in the universe
166 :return: All binaries that are considered "broken" and had their relations nulled.
167 """
168 return self._broken_packages
170 @property
171 def equivalent_packages(self) -> frozenset["BinaryPackageId"]:
172 """A frozenset of all binary packages that are equivalent to at least one other package
174 The binary packages in this set has the property that "universe.packages_equivalent_to(pkg_id)"
175 will return a set of at least 2 or more elements for each of them.
177 :return: All packages that are equivalent to other packages.
178 """
179 return self._equivalent_packages
181 def __contains__(self, pkg_id: "BinaryPackageId") -> bool:
182 return pkg_id in self._relations
184 def __iter__(self) -> Iterator["BinaryPackageId"]:
185 yield from self._relations