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 daklib.architecture import match_architecture 

22from daklib.utils import extract_component_from_section 

23 

24from collections.abc import Mapping 

25from typing import Optional 

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('Source package has neither Package-List nor Binary field.') 

89 

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

91 

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

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

94 

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

96 package_list = [] 

97 

98 binaries_binary = self._binaries(source) 

99 binaries_package_list = set() 

100 

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

102 if not line: 

103 continue 

104 fields = line.split() 

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

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

107 

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

109 name = fields[0] 

110 package_type = fields[1] 

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

112 priority = fields[3] 

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

114 

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

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

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

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

119 binaries_package_list.add(name) 

120 

121 entry = PackageListEntry(name, package_type, section, component, priority, **other) 

122 package_list.append(entry) 

123 

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

125 raise InvalidSource("Package-List and Binaries fields have a different number of entries.") 

126 

127 return package_list 

128 

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

130 package_list = [] 

131 

132 for binary in self._binaries(source): 

133 name = binary 

134 package_type = None 

135 component = None 

136 section = None 

137 priority = None 

138 other = dict() 

139 

140 entry = PackageListEntry(name, package_type, section, component, priority, **other) 

141 package_list.append(entry) 

142 

143 return package_list 

144 

145 def packages_for_suite(self, suite, only_default_profile=True) -> list[PackageListEntry]: 

146 packages = [] 

147 for entry in self.package_list: 

148 if only_default_profile and not entry.built_in_default_profile(): 

149 continue 

150 built = entry.built_in_suite(suite) 

151 if built or built is None: 

152 packages.append(entry) 

153 return packages 

154 

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

156 has_arch_indep: Optional[bool] = False 

157 for entry in self.package_list: 

158 built = entry.built_on_architecture('all') 

159 if built: 

160 return True 

161 if built is None: 

162 has_arch_indep = None 

163 return has_arch_indep 

164 

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

166 has_arch_dep: Optional[bool] = False 

167 for entry in self.package_list: 

168 built_on_all = entry.built_on_architecture('all') 

169 if built_on_all is False: 

170 return True 

171 if built_on_all is None: 

172 has_arch_dep = None 

173 return has_arch_dep