Coverage for daklib/packagelist.py: 94%

107 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-01-04 16:18 +0000

1"""parse Package-List field 

2 

3@copyright: 2014, Ansgar Burchardt <ansgar@debian.org> 

4@license: GPL-2+ 

5""" 

6 

7# This program is free software; you can redistribute it and/or modify 

8# it under the terms of the GNU General Public License as published by 

9# the Free Software Foundation; either version 2 of the License, or 

10# (at your option) any later version. 

11# 

12# This program is distributed in the hope that it will be useful, 

13# but WITHOUT ANY WARRANTY; without even the implied warranty of 

14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

15# GNU General Public License for more details. 

16# 

17# You should have received a copy of the GNU General Public License 

18# along with this program; if not, write to the Free Software 

19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

20 

21from typing import TYPE_CHECKING, Optional 

22 

23from daklib.architecture import match_architecture 

24from daklib.utils import extract_component_from_section 

25 

26if TYPE_CHECKING: 

27 from collections.abc import Mapping 

28 

29 from daklib.dbconn import MetadataProxy, Suite 

30 

31 

32class InvalidSource(Exception): 

33 pass 

34 

35 

36class PackageListEntry: 

37 def __init__( 

38 self, 

39 name: str, 

40 package_type: str | None, 

41 section: str | None, 

42 component: str | None, 

43 priority: str | None, 

44 **other: str, 

45 ): 

46 self.name = name 

47 self.type = package_type 

48 self.section = section 

49 self.component = component 

50 self.priority = priority 

51 self.other = other 

52 

53 self.architectures = self._architectures() 

54 

55 def _architectures(self) -> Optional[list[str]]: 

56 archs = self.other.get("arch", None) 

57 if archs is None: 

58 return None 

59 return archs.split(",") 

60 

61 def built_on_architecture(self, architecture: str) -> Optional[bool]: 

62 archs = self.architectures 

63 if archs is None: 

64 return None 

65 for arch in archs: 

66 if match_architecture(architecture, arch): 

67 return True 

68 return False 

69 

70 def built_in_suite(self, suite: "Suite") -> Optional[bool]: 

71 built: Optional[bool] = False 

72 for arch in suite.architectures: 

73 if arch.arch_string == "source": 

74 continue 

75 built_on_arch = self.built_on_architecture(arch.arch_string) 

76 if built_on_arch: 

77 return True 

78 if built_on_arch is None: 

79 built = None 

80 return built 

81 

82 def built_in_default_profile(self) -> bool: 

83 # See man:dsc(5) and https://bugs.debian.org/913965#77 

84 profiles_and = self.other.get("profile") 

85 if profiles_and is None: 

86 return True 

87 return all( 

88 any(profile.startswith("!") for profile in profiles_or.split("+")) 

89 for profiles_or in profiles_and.split(",") 

90 ) 

91 

92 

93class PackageList: 

94 def __init__(self, source: "Mapping[str, str] | MetadataProxy"): 

95 if "Package-List" in source: 

96 self.package_list = self._parse(source) 

97 elif "Binary" in source: 97 ↛ 100line 97 didn't jump to line 100 because the condition on line 97 was always true

98 self.package_list = self._parse_fallback(source) 

99 else: 

100 raise InvalidSource( 

101 "Source package has neither Package-List nor Binary field." 

102 ) 

103 

104 self.fallback = any(entry.architectures is None for entry in self.package_list) 

105 

106 def _binaries(self, source: "Mapping[str, str] | MetadataProxy") -> set[str]: 

107 return set(name.strip() for name in source["Binary"].split(",")) 

108 

109 def _parse( 

110 self, source: "Mapping[str, str] | MetadataProxy" 

111 ) -> list[PackageListEntry]: 

112 package_list = [] 

113 

114 binaries_binary = self._binaries(source) 

115 binaries_package_list = set() 

116 

117 for line in source["Package-List"].split("\n"): 

118 if not line: 

119 continue 

120 fields = line.split() 

121 if len(fields) < 4: 121 ↛ 122line 121 didn't jump to line 122 because the condition on line 121 was never true

122 raise InvalidSource("Package-List entry has less than four fields.") 

123 

124 # <name> <type> <component/section> <priority> [arch=<arch>[,<arch>]...] 

125 name = fields[0] 

126 package_type = fields[1] 

127 section, component = extract_component_from_section(fields[2]) 

128 priority = fields[3] 

129 other = dict(kv.split("=", 1) for kv in fields[4:]) 

130 

131 if name in binaries_package_list: 131 ↛ 132line 131 didn't jump to line 132 because the condition on line 131 was never true

132 raise InvalidSource( 

133 "Package-List has two entries for '{0}'.".format(name) 

134 ) 

135 if name not in binaries_binary: 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true

136 raise InvalidSource( 

137 "Package-List lists {0} which is not listed in Binary.".format(name) 

138 ) 

139 binaries_package_list.add(name) 

140 

141 entry = PackageListEntry( 

142 name, package_type, section, component, priority, **other 

143 ) 

144 package_list.append(entry) 

145 

146 if len(binaries_binary) != len(binaries_package_list): 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true

147 raise InvalidSource( 

148 "Package-List and Binaries fields have a different number of entries." 

149 ) 

150 

151 return package_list 

152 

153 def _parse_fallback( 

154 self, source: "Mapping[str, str] | MetadataProxy" 

155 ) -> list[PackageListEntry]: 

156 return [ 

157 PackageListEntry(binary, None, None, None, None) 

158 for binary in self._binaries(source) 

159 ] 

160 

161 def packages_for_suite( 

162 self, suite: "Suite", only_default_profile=True 

163 ) -> list[PackageListEntry]: 

164 packages = [] 

165 for entry in self.package_list: 

166 if only_default_profile and not entry.built_in_default_profile(): 

167 continue 

168 built = entry.built_in_suite(suite) 

169 if built or built is None: 

170 packages.append(entry) 

171 return packages 

172 

173 def has_arch_indep_packages(self) -> Optional[bool]: 

174 has_arch_indep: Optional[bool] = False 

175 for entry in self.package_list: 

176 built = entry.built_on_architecture("all") 

177 if built: 

178 return True 

179 if built is None: 

180 has_arch_indep = None 

181 return has_arch_indep 

182 

183 def has_arch_dep_packages(self) -> Optional[bool]: 

184 has_arch_dep: Optional[bool] = False 

185 for entry in self.package_list: 

186 built_on_all = entry.built_on_architecture("all") 

187 if built_on_all is False: 

188 return True 

189 if built_on_all is None: 

190 has_arch_dep = None 

191 return has_arch_dep