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
« 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
4from debian.deb822 import Deb822
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
13# Match debhelper (minus one space in each end, which comes
14# via join).
15LINE_PREFIX = "\\\n "
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
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
34 with provided_alternatives_file.open() as fd:
35 alternatives = list(Deb822.iter_paragraphs(fd))
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 )
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 )
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 )
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 )
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
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 )