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

1# Copyright (C) 2012 Niels Thykier <niels@thykier.net> 

2 

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. 

7 

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. 

12 

13from collections.abc import Iterator 

14from typing import TYPE_CHECKING 

15 

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 

18 

19 

20class BinaryPackageRelation: 

21 """All relations of a given binary package""" 

22 

23 __slots__ = [ 

24 "pkg_ids", 

25 "dependencies", 

26 "negative_dependencies", 

27 "reverse_dependencies", 

28 ] 

29 

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 

41 

42 

43class BinaryPackageUniverse: 

44 """A "universe" of all binary packages and their relations 

45 

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. 

50 

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?". 

54 

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). 

60 

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 """ 

68 

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 

80 

81 def dependencies_of( 

82 self, pkg_id: "BinaryPackageId" 

83 ) -> frozenset[frozenset["BinaryPackageId"]]: 

84 """Returns the set of dependencies of a given package 

85 

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 

90 

91 def negative_dependencies_of( 

92 self, pkg_id: "BinaryPackageId" 

93 ) -> frozenset["BinaryPackageId"]: 

94 """Returns the set of negative dependencies of a given package 

95 

96 Note that there is no "reverse_negative_dependencies_of" method, 

97 since negative dependencies have no "direction" unlike positive 

98 dependencies. 

99 

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 

104 

105 def reverse_dependencies_of( 

106 self, pkg_id: "BinaryPackageId" 

107 ) -> set["BinaryPackageId"]: 

108 """Returns the set of reverse dependencies of a given package 

109 

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. 

113 

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 

118 

119 def are_equivalent( 

120 self, pkg_id1: "BinaryPackageId", pkg_id2: "BinaryPackageId" 

121 ) -> bool: 

122 """Test if pkg_id1 and pkg_id2 are equivalent 

123 

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. 

129 

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) 

134 

135 def packages_equivalent_to( 

136 self, pkg_id: "BinaryPackageId" 

137 ) -> frozenset["BinaryPackageId"]: 

138 """Determine which packages are equivalent to a given package 

139 

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 

144 

145 def relations_of(self, pkg_id: "BinaryPackageId") -> BinaryPackageRelation: 

146 """Get the direct relations of a given package 

147 

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] 

153 

154 @property 

155 def essential_packages(self) -> frozenset["BinaryPackageId"]: 

156 """A frozenset of all "Essential: yes" binaries in the universe 

157 

158 :return: All of the binaries that are marked as essential. 

159 """ 

160 return self._essential_packages 

161 

162 @property 

163 def broken_packages(self) -> frozenset["BinaryPackageId"]: 

164 """A frozenset of all broken binaries in the universe 

165 

166 :return: All binaries that are considered "broken" and had their relations nulled. 

167 """ 

168 return self._broken_packages 

169 

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 

173 

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. 

176 

177 :return: All packages that are equivalent to other packages. 

178 """ 

179 return self._equivalent_packages 

180 

181 def __contains__(self, pkg_id: "BinaryPackageId") -> bool: 

182 return pkg_id in self._relations 

183 

184 def __iter__(self) -> Iterator["BinaryPackageId"]: 

185 yield from self._relations