Coverage for src/debputy/packaging/alternatives.py: 74%

75 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-07 12:14 +0200

1import textwrap 

2from typing import List, Dict, Tuple, Mapping 

3 

4from debian.deb822 import Deb822 

5 

6from debputy.maintscript_snippet import MaintscriptSnippetContainer, MaintscriptSnippet 

7from debputy.packager_provided_files import PackagerProvidedFile 

8from debputy.packages import BinaryPackage 

9from debputy.packaging.makeshlibs import resolve_reserved_provided_file 

10from debputy.plugin.api import VirtualPath 

11from debputy.util import _error, escape_shell, POSTINST_DEFAULT_CONDITION 

12 

13# Match debhelper (minus one space in each end, which comes 

14# via join). 

15LINE_PREFIX = "\\\n " 

16 

17 

18def process_alternatives( 

19 binary_package: BinaryPackage, 

20 fs_root: VirtualPath, 

21 reserved_packager_provided_files: Dict[str, List[PackagerProvidedFile]], 

22 maintscript_snippets: Dict[str, MaintscriptSnippetContainer], 

23) -> None: 

24 if binary_package.is_udeb: 24 ↛ 25line 24 didn't jump to line 25, because the condition on line 24 was never true

25 return 

26 

27 provided_alternatives_file = resolve_reserved_provided_file( 

28 "alternatives", 

29 reserved_packager_provided_files, 

30 ) 

31 if provided_alternatives_file is None: 31 ↛ 32line 31 didn't jump to line 32, because the condition on line 31 was never true

32 return 

33 

34 with provided_alternatives_file.open() as fd: 

35 alternatives = list(Deb822.iter_paragraphs(fd)) 

36 

37 for no, alternative in enumerate(alternatives): 

38 process_alternative( 

39 provided_alternatives_file.fs_path, 

40 fs_root, 

41 alternative, 

42 no, 

43 maintscript_snippets, 

44 ) 

45 

46 

47def process_alternative( 

48 provided_alternatives_fs_path: str, 

49 fs_root: VirtualPath, 

50 alternative_deb822: Deb822, 

51 no: int, 

52 maintscript_snippets: Dict[str, MaintscriptSnippetContainer], 

53) -> None: 

54 name = _mandatory_key( 

55 "Name", 

56 alternative_deb822, 

57 provided_alternatives_fs_path, 

58 f"Stanza number {no}", 

59 ) 

60 error_context = f"Alternative named {name}" 

61 link_path = _mandatory_key( 

62 "Link", 

63 alternative_deb822, 

64 provided_alternatives_fs_path, 

65 error_context, 

66 ) 

67 impl_path = _mandatory_key( 

68 "Alternative", 

69 alternative_deb822, 

70 provided_alternatives_fs_path, 

71 error_context, 

72 ) 

73 priority = _mandatory_key( 

74 "Priority", 

75 alternative_deb822, 

76 provided_alternatives_fs_path, 

77 error_context, 

78 ) 

79 if "/" in name: 79 ↛ 80line 79 didn't jump to line 80, because the condition on line 79 was never true

80 _error( 

81 f'The "Name" ({link_path}) key must be a basename and cannot contain slashes' 

82 f" ({error_context} in {provided_alternatives_fs_path})" 

83 ) 

84 if link_path == impl_path: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true

85 _error( 

86 f'The "Link" key and the "Alternative" key must not have the same value' 

87 f" ({error_context} in {provided_alternatives_fs_path})" 

88 ) 

89 impl = fs_root.lookup(impl_path) 

90 if impl is None or impl.is_dir: 90 ↛ 91line 90 didn't jump to line 91, because the condition on line 90 was never true

91 _error( 

92 f'The path listed in "Alternative" ("{impl_path}") does not exist' 

93 f" in the package. ({error_context} in {provided_alternatives_fs_path})" 

94 ) 

95 for key in ["Slave", "Slaves", "Slave-Links"]: 

96 if key in alternative_deb822: 96 ↛ 97line 96 didn't jump to line 97, because the condition on line 96 was never true

97 _error( 

98 f'Please use "Dependents" instead of "{key}".' 

99 f" ({error_context} in {provided_alternatives_fs_path})" 

100 ) 

101 dependents = alternative_deb822.get("Dependents") 

102 install_command = [ 

103 escape_shell( 

104 "update-alternatives", 

105 "--install", 

106 link_path, 

107 name, 

108 impl_path, 

109 priority, 

110 ) 

111 ] 

112 remove_command = [ 

113 escape_shell( 

114 "update-alternatives", 

115 "--remove", 

116 name, 

117 impl_path, 

118 ) 

119 ] 

120 if dependents: 120 ↛ 153line 120 didn't jump to line 153, because the condition on line 120 was never false

121 seen_link_path = set() 

122 for line in dependents.splitlines(): 

123 line = line.strip() 

124 if not line: # First line is usually empty 

125 continue 

126 dlink_path, dlink_name, dimpl_path = parse_dependent_link( 

127 line, 

128 error_context, 

129 provided_alternatives_fs_path, 

130 ) 

131 if dlink_path in seen_link_path: 131 ↛ 132line 131 didn't jump to line 132, because the condition on line 131 was never true

132 _error( 

133 f'The Dependent link path "{dlink_path}" was used twice.' 

134 f" ({error_context} in {provided_alternatives_fs_path})" 

135 ) 

136 dimpl = fs_root.lookup(dimpl_path) 

137 if dimpl is None or dimpl.is_dir: 137 ↛ 138line 137 didn't jump to line 138, because the condition on line 137 was never true

138 _error( 

139 f'The path listed in "Dependents" ("{dimpl_path}") does not exist' 

140 f" in the package. ({error_context} in {provided_alternatives_fs_path})" 

141 ) 

142 seen_link_path.add(dlink_path) 

143 install_command.append(LINE_PREFIX) 

144 install_command.append( 

145 escape_shell( 

146 # update-alternatives still uses this old option name :-/ 

147 "--slave", 

148 dlink_path, 

149 dlink_name, 

150 dimpl_path, 

151 ) 

152 ) 

153 postinst = textwrap.dedent( 

154 """\ 

155 if {CONDITION}; then 

156 {COMMAND} 

157 fi 

158 """ 

159 ).format( 

160 CONDITION=POSTINST_DEFAULT_CONDITION, 

161 COMMAND=" ".join(install_command), 

162 ) 

163 

164 prerm = textwrap.dedent( 

165 """\ 

166 if [ "$1" = "remove" ]; then 

167 {COMMAND} 

168 fi 

169 """ 

170 ).format(COMMAND=" ".join(remove_command)) 

171 maintscript_snippets["postinst"].append( 

172 MaintscriptSnippet( 

173 f"debputy (via {provided_alternatives_fs_path})", 

174 snippet=postinst, 

175 ) 

176 ) 

177 maintscript_snippets["prerm"].append( 

178 MaintscriptSnippet( 

179 f"debputy (via {provided_alternatives_fs_path})", 

180 snippet=prerm, 

181 ) 

182 ) 

183 

184 

185def parse_dependent_link( 

186 line: str, 

187 error_context: str, 

188 provided_alternatives_file: str, 

189) -> Tuple[str, str, str]: 

190 parts = line.split() 

191 if len(parts) != 3: 191 ↛ 192line 191 didn't jump to line 192, because the condition on line 191 was never true

192 if len(parts) > 1: 

193 pass 

194 _error( 

195 f"The each line in Dependents links must have exactly 3 space separated parts." 

196 f' The "{line}" split into {len(parts)} part(s).' 

197 f" ({error_context} in {provided_alternatives_file})" 

198 ) 

199 

200 dlink_path, dlink_name, dimpl_path = parts 

201 if "/" in dlink_name: 201 ↛ 202line 201 didn't jump to line 202, because the condition on line 201 was never true

202 _error( 

203 f'The Dependent link name "{dlink_path}" must be a basename and cannot contain slashes' 

204 f" ({error_context} in {provided_alternatives_file})" 

205 ) 

206 if dlink_path == dimpl_path: 206 ↛ 207line 206 didn't jump to line 207, because the condition on line 206 was never true

207 _error( 

208 f'The Dependent Link path and Alternative must not have the same value ["{dlink_path}"]' 

209 f" ({error_context} in {provided_alternatives_file})" 

210 ) 

211 return dlink_path, dlink_name, dimpl_path 

212 

213 

214def _mandatory_key( 

215 key: str, 

216 alternative_deb822: Mapping[str, str], 

217 provided_alternatives_file: str, 

218 error_context: str, 

219) -> str: 

220 try: 

221 return alternative_deb822[key] 

222 except KeyError: 

223 _error( 

224 f'Missing mandatory key "{key}" in {provided_alternatives_file} ({error_context})' 

225 )