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 collections.abc import Mapping 

22from typing import Optional 

23 

24from daklib.architecture import match_architecture 

25from daklib.utils import extract_component_from_section 

26 

27 

28class InvalidSource(Exception): 

29 pass 

30 

31 

32class PackageListEntry: 

33 def __init__(self, name, package_type, section, component, priority, **other): 

34 self.name = name 

35 self.type = package_type 

36 self.section = section 

37 self.component = component 

38 self.priority = priority 

39 self.other = other 

40 

41 self.architectures = self._architectures() 

42 

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

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

45 if archs is None: 

46 return None 

47 return archs.split(",") 

48 

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

50 archs = self.architectures 

51 if archs is None: 

52 return None 

53 for arch in archs: 

54 if match_architecture(architecture, arch): 

55 return True 

56 return False 

57 

58 def built_in_suite(self, suite) -> Optional[bool]: 

59 built: Optional[bool] = False 

60 for arch in suite.architectures: 

61 if arch.arch_string == "source": 

62 continue 

63 built_on_arch = self.built_on_architecture(arch.arch_string) 

64 if built_on_arch: 

65 return True 

66 if built_on_arch is None: 

67 built = None 

68 return built 

69 

70 def built_in_default_profile(self) -> bool: 

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

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

73 if profiles_and is None: 

74 return True 

75 return all( 

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

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

78 ) 

79 

80 

81class PackageList: 

82 def __init__(self, source: Mapping[str, str]): 

83 if "Package-List" in source: 

84 self.package_list = self._parse(source) 

85 elif "Binary" in source: 85 ↛ 88line 85 didn't jump to line 88, because the condition on line 85 was never false

86 self.package_list = self._parse_fallback(source) 

87 else: 

88 raise InvalidSource( 

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

90 ) 

91 

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

93 

94 def _binaries(self, source: Mapping[str, str]) -> set[str]: 

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

96 

97 def _parse(self, source: Mapping[str, str]) -> list[PackageListEntry]: 

98 package_list = [] 

99 

100 binaries_binary = self._binaries(source) 

101 binaries_package_list = set() 

102 

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

104 if not line: 

105 continue 

106 fields = line.split() 

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

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

109 

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

111 name = fields[0] 

112 package_type = fields[1] 

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

114 priority = fields[3] 

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

116 

117 if name in binaries_package_list: 117 ↛ 118line 117 didn't jump to line 118, because the condition on line 117 was never true

118 raise InvalidSource( 

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

120 ) 

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

122 raise InvalidSource( 

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

124 ) 

125 binaries_package_list.add(name) 

126 

127 entry = PackageListEntry( 

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

129 ) 

130 package_list.append(entry) 

131 

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

133 raise InvalidSource( 

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

135 ) 

136 

137 return package_list 

138 

139 def _parse_fallback(self, source: Mapping[str, str]) -> list[PackageListEntry]: 

140 package_list = [] 

141 

142 for binary in self._binaries(source): 

143 name = binary 

144 package_type = None 

145 component = None 

146 section = None 

147 priority = None 

148 other = dict() 

149 

150 entry = PackageListEntry( 

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

152 ) 

153 package_list.append(entry) 

154 

155 return package_list 

156 

157 def packages_for_suite( 

158 self, suite, only_default_profile=True 

159 ) -> list[PackageListEntry]: 

160 packages = [] 

161 for entry in self.package_list: 

162 if only_default_profile and not entry.built_in_default_profile(): 

163 continue 

164 built = entry.built_in_suite(suite) 

165 if built or built is None: 

166 packages.append(entry) 

167 return packages 

168 

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

170 has_arch_indep: Optional[bool] = False 

171 for entry in self.package_list: 

172 built = entry.built_on_architecture("all") 

173 if built: 

174 return True 

175 if built is None: 

176 has_arch_indep = None 

177 return has_arch_indep 

178 

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

180 has_arch_dep: Optional[bool] = False 

181 for entry in self.package_list: 

182 built_on_all = entry.built_on_architecture("all") 

183 if built_on_all is False: 

184 return True 

185 if built_on_all is None: 

186 has_arch_dep = None 

187 return has_arch_dep