Coverage for src/debputy/analysis/debian_dir.py: 30%
343 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-09-07 09:27 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-09-07 09:27 +0000
1import dataclasses
2import json
3import os
4import stat
5import subprocess
6from typing import (
7 AbstractSet,
8 List,
9 Mapping,
10 Iterable,
11 Tuple,
12 Optional,
13 Sequence,
14 Dict,
15 Any,
16 Union,
17 Iterator,
18 TypedDict,
19 NotRequired,
20 Container,
21)
23from debputy.analysis import REFERENCE_DATA_TABLE
24from debputy.analysis.analysis_util import flatten_ppfs
25from debputy.dh.dh_assistant import (
26 resolve_active_and_inactive_dh_commands,
27 read_dh_addon_sequences,
28 extract_dh_compat_level,
29)
30from debputy.integration_detection import determine_debputy_integration_mode
31from debputy.packager_provided_files import (
32 PackagerProvidedFile,
33 detect_all_packager_provided_files,
34)
35from debputy.packages import BinaryPackage, SourcePackage
36from debputy.plugin.api import (
37 VirtualPath,
38 packager_provided_file_reference_documentation,
39)
40from debputy.plugin.api.feature_set import PluginProvidedFeatureSet
41from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin
42from debputy.plugin.api.impl_types import (
43 PluginProvidedKnownPackagingFile,
44 DebputyPluginMetadata,
45 KnownPackagingFileInfo,
46 DHCompatibilityBasedRule,
47 PackagerProvidedFileClassSpec,
48 expand_known_packaging_config_features,
49)
50from debputy.plugin.api.spec import DebputyIntegrationMode
51from debputy.util import (
52 assume_not_none,
53 escape_shell,
54 _trace_log,
55 _is_trace_log_enabled,
56 render_command,
57)
59PackagingFileInfo = TypedDict(
60 "PackagingFileInfo",
61 {
62 "path": str,
63 "binary-package": NotRequired[str],
64 "install-path": NotRequired[str],
65 "install-pattern": NotRequired[str],
66 "file-categories": NotRequired[List[str]],
67 "config-features": NotRequired[List[str]],
68 "pkgfile-is-active-in-build": NotRequired[bool],
69 "pkgfile-stem": NotRequired[str],
70 "pkgfile-explicit-package-name": NotRequired[bool],
71 "pkgfile-name-segment": NotRequired[str],
72 "pkgfile-architecture-restriction": NotRequired[str],
73 "likely-typo-of": NotRequired[str],
74 "likely-generated-from": NotRequired[List[str]],
75 "related-tools": NotRequired[List[str]],
76 "documentation-uris": NotRequired[List[str]],
77 "debputy-cmd-templates": NotRequired[List[List[str]]],
78 "generates": NotRequired[str],
79 "generated-from": NotRequired[str],
80 },
81)
84def _perl_json_bool(base: Optional[Any]) -> Optional[Any]:
85 if base is None: 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true
86 return None
88 if isinstance(base, bool): 88 ↛ 95line 88 didn't jump to line 95 because the condition on line 88 was always true
89 return base
91 # Account for different ways that Perl will or could return a JSON bool,
92 # when not using `JSON::PP::{true,false}`.
93 #
94 # https://salsa.debian.org/debian/debputy/-/issues/119#note_627057
95 if isinstance(base, int):
96 if base == 0:
97 return False
98 return True if base == 1 else base
99 if isinstance(base, str):
100 return False if base == "" else base
101 return base
104def scan_debian_dir(
105 feature_set: PluginProvidedFeatureSet,
106 source_package: SourcePackage,
107 binary_packages: Mapping[str, BinaryPackage],
108 debian_dir: VirtualPath,
109 *,
110 uses_dh_sequencer: bool = True,
111 dh_sequences: Optional[AbstractSet[str]] = None,
112) -> Tuple[List[PackagingFileInfo], List[str], int, Optional[object]]:
113 known_packaging_files = feature_set.known_packaging_files
114 debputy_plugin_metadata = plugin_metadata_for_debputys_own_plugin()
115 reference_data_set_names = [
116 "config-features",
117 "file-categories",
118 ]
119 for n in reference_data_set_names:
120 assert n in REFERENCE_DATA_TABLE
122 annotated: List[PackagingFileInfo] = []
123 seen_paths: Dict[str, PackagingFileInfo] = {}
125 if dh_sequences is None:
126 r = read_dh_addon_sequences(debian_dir)
127 if r is not None:
128 bd_sequences, dr_sequences, uses_dh_sequencer = r
129 dh_sequences = bd_sequences | dr_sequences
130 else:
131 dh_sequences = set()
132 uses_dh_sequencer = False
133 debputy_integration_mode = determine_debputy_integration_mode(
134 source_package.fields,
135 dh_sequences,
136 )
137 is_debputy_package = debputy_integration_mode is not None
138 dh_compat_level, dh_assistant_exit_code = extract_dh_compat_level()
139 dh_issues = []
141 static_packaging_files = {
142 kpf.detection_value: kpf
143 for kpf in known_packaging_files.values()
144 if kpf.detection_method == "path"
145 }
146 dh_pkgfile_docs = {
147 kpf.detection_value: kpf
148 for kpf in known_packaging_files.values()
149 if kpf.detection_method == "dh.pkgfile"
150 }
151 if is_debputy_package:
152 all_debputy_ppfs = list(
153 flatten_ppfs(
154 detect_all_packager_provided_files(
155 feature_set,
156 debian_dir,
157 binary_packages,
158 allow_fuzzy_matches=True,
159 detect_typos=True,
160 ignore_paths=static_packaging_files,
161 )
162 )
163 )
164 else:
165 all_debputy_ppfs = []
167 if dh_compat_level is not None:
168 (
169 all_dh_ppfs,
170 _,
171 dh_issues,
172 dh_assistant_exit_code,
173 ) = resolve_debhelper_config_files(
174 debian_dir,
175 binary_packages,
176 debputy_plugin_metadata,
177 feature_set,
178 dh_sequences,
179 dh_compat_level,
180 uses_dh_sequencer,
181 debputy_integration_mode=debputy_integration_mode,
182 ignore_paths=static_packaging_files,
183 )
185 else:
186 all_dh_ppfs = []
188 for ppf in all_debputy_ppfs:
189 key = ppf.path.path
190 ref_doc = ppf.definition.reference_documentation
191 documentation_uris = (
192 ref_doc.format_documentation_uris if ref_doc is not None else None
193 )
194 details: PackagingFileInfo = {
195 "path": key,
196 "binary-package": ppf.package_name,
197 "pkgfile-stem": ppf.definition.stem,
198 "pkgfile-explicit-package-name": ppf.uses_explicit_package_name,
199 "pkgfile-is-active-in-build": ppf.definition.has_active_command,
200 "debputy-cmd-templates": [
201 ["debputy", "plugin", "show", "p-p-f", ppf.definition.stem]
202 ],
203 }
204 if ppf.fuzzy_match and key.endswith(".in"):
205 _merge_list(details, "file-categories", ["generic-template"])
206 details["generates"] = key[:-3]
207 elif assume_not_none(ppf.path.parent_dir).get(ppf.path.name + ".in"):
208 _merge_list(details, "file-categories", ["generated"])
209 details["generated-from"] = key + ".in"
210 name_segment = ppf.name_segment
211 arch_restriction = ppf.architecture_restriction
212 if name_segment is not None:
213 details["pkgfile-name-segment"] = name_segment
214 if arch_restriction:
215 details["pkgfile-architecture-restriction"] = arch_restriction
216 seen_paths[key] = details
217 annotated.append(details)
218 static_details = static_packaging_files.get(key)
219 if static_details is not None:
220 # debhelper compat rules does not apply to debputy files
221 _add_known_packaging_data(details, static_details, None)
222 if documentation_uris:
223 details["documentation-uris"] = list(documentation_uris)
225 _merge_ppfs(annotated, seen_paths, all_dh_ppfs, dh_pkgfile_docs, dh_compat_level)
227 for virtual_path in _scan_debian_dir(debian_dir):
228 key = virtual_path.path
229 if key in seen_paths or _skip_path(virtual_path):
230 continue
232 static_match = static_packaging_files.get(virtual_path.path)
233 if static_match is not None:
234 details: PackagingFileInfo = {
235 "path": key,
236 }
237 annotated.append(details)
238 if assume_not_none(virtual_path.parent_dir).get(virtual_path.name + ".in"):
239 details["generated-from"] = key + ".in"
240 _merge_list(details, "file-categories", ["generated"])
241 _add_known_packaging_data(details, static_match, dh_compat_level)
243 return annotated, reference_data_set_names, dh_assistant_exit_code, dh_issues
246def _skip_path(virtual_path: VirtualPath) -> bool:
247 if virtual_path.is_symlink:
248 try:
249 st = os.stat(virtual_path.fs_path)
250 except FileNotFoundError:
251 return True
252 else:
253 if not stat.S_ISREG(st.st_mode):
254 return True
255 elif not virtual_path.is_file:
256 return True
257 return False
260def _fake_PPFClassSpec(
261 debputy_plugin_metadata: DebputyPluginMetadata,
262 stem: str,
263 doc_uris: Optional[Sequence[str]],
264 install_pattern: Optional[str],
265 *,
266 default_priority: Optional[int] = None,
267 packageless_is_fallback_for_all_packages: bool = False,
268 post_formatting_rewrite: Optional[str] = None,
269 bug_950723: bool = False,
270 has_active_command: bool = False,
271) -> PackagerProvidedFileClassSpec:
272 if install_pattern is None: 272 ↛ 274line 272 didn't jump to line 274 because the condition on line 272 was always true
273 install_pattern = "not-a-real-ppf"
274 if post_formatting_rewrite is not None: 274 ↛ 275line 274 didn't jump to line 275 because the condition on line 274 was never true
275 formatting_hook = _POST_FORMATTING_REWRITE[post_formatting_rewrite]
276 else:
277 formatting_hook = None
278 return PackagerProvidedFileClassSpec(
279 debputy_plugin_metadata,
280 stem,
281 install_pattern,
282 allow_architecture_segment=True,
283 allow_name_segment=True,
284 default_priority=default_priority,
285 default_mode=0o644,
286 post_formatting_rewrite=formatting_hook,
287 packageless_is_fallback_for_all_packages=packageless_is_fallback_for_all_packages,
288 reservation_only=False,
289 formatting_callback=None,
290 bug_950723=bug_950723,
291 has_active_command=has_active_command,
292 reference_documentation=packager_provided_file_reference_documentation(
293 format_documentation_uris=doc_uris,
294 ),
295 )
298def _relevant_dh_compat_rules(
299 compat_level: Optional[int],
300 info: KnownPackagingFileInfo,
301) -> Iterable[DHCompatibilityBasedRule]:
302 if compat_level is None:
303 return
304 dh_compat_rules = info.get("dh_compat_rules")
305 if not dh_compat_rules:
306 return
307 for dh_compat_rule in dh_compat_rules:
308 rule_compat_level = dh_compat_rule.get("starting_with_compat_level")
309 if rule_compat_level is not None and compat_level < rule_compat_level:
310 continue
311 yield dh_compat_rule
314def _kpf_install_pattern(
315 compat_level: Optional[int],
316 ppkpf: PluginProvidedKnownPackagingFile,
317) -> Optional[str]:
318 for compat_rule in _relevant_dh_compat_rules(compat_level, ppkpf.info):
319 install_pattern = compat_rule.get("install_pattern")
320 if install_pattern is not None:
321 return install_pattern
322 return ppkpf.info.get("install_pattern")
325def resolve_debhelper_config_files(
326 debian_dir: VirtualPath,
327 binary_packages: Mapping[str, BinaryPackage],
328 debputy_plugin_metadata: DebputyPluginMetadata,
329 plugin_feature_set: PluginProvidedFeatureSet,
330 dh_rules_addons: AbstractSet[str],
331 dh_compat_level: int,
332 saw_dh: bool,
333 ignore_paths: Container[str] = frozenset(),
334 *,
335 debputy_integration_mode: Optional[DebputyIntegrationMode] = None,
336 cwd: Optional[str] = None,
337) -> Tuple[List[PackagerProvidedFile], List[str], Optional[object], int]:
339 dh_ppf_docs = {
340 kpf.detection_value: kpf
341 for kpf in plugin_feature_set.known_packaging_files.values()
342 if kpf.detection_method == "dh.pkgfile"
343 }
345 dh_ppfs = {}
346 commands, exit_code = _relevant_dh_commands(dh_rules_addons, cwd=cwd)
348 cmd = ["dh_assistant", "list-guessed-dh-config-files"]
349 if dh_rules_addons:
350 addons = ",".join(dh_rules_addons)
351 cmd.append(f"--with={addons}")
352 try:
353 output = subprocess.check_output(
354 cmd,
355 stderr=subprocess.DEVNULL,
356 cwd=cwd,
357 )
358 except (subprocess.CalledProcessError, FileNotFoundError) as e:
359 config_files = []
360 issues = None
361 if isinstance(e, subprocess.CalledProcessError):
362 exit_code = e.returncode
363 else:
364 exit_code = 127
365 if _is_trace_log_enabled():
366 _trace_log(
367 f"Command {render_command(*cmd, cwd=cwd)} failed with {exit_code} ({cwd=})"
368 )
369 else:
370 result = json.loads(output)
371 config_files: List[Union[Mapping[str, Any], object]] = result.get(
372 "config-files", []
373 )
374 missing_introspection: Optional[List[str]] = result.get(
375 "commands-not-introspectable", []
376 )
377 issues = result.get("issues")
378 if _is_trace_log_enabled(): 378 ↛ 379line 378 didn't jump to line 379 because the condition on line 378 was never true
379 _trace_log(
380 f"Command {render_command(*cmd, cwd=cwd)} returned successfully: {output}"
381 )
382 dh_commands = resolve_active_and_inactive_dh_commands(dh_rules_addons)
383 for config_file in config_files:
384 if not isinstance(config_file, dict): 384 ↛ 385line 384 didn't jump to line 385 because the condition on line 384 was never true
385 continue
386 if config_file.get("file-type") != "pkgfile":
387 continue
388 stem = config_file.get("pkgfile")
389 if stem is None: 389 ↛ 390line 389 didn't jump to line 390 because the condition on line 389 was never true
390 continue
391 internal = config_file.get("internal")
392 if isinstance(internal, dict):
393 bug_950723 = internal.get("bug#950723", False) is True
394 else:
395 bug_950723 = False
396 commands = config_file.get("commands")
397 documentation_uris = []
398 related_tools = []
399 seen_commands = set()
400 seen_docs = set()
401 ppkpf = dh_ppf_docs.get(stem)
403 if ppkpf: 403 ↛ 404line 403 didn't jump to line 404 because the condition on line 403 was never true
404 dh_cmds = ppkpf.info.get("debhelper_commands")
405 doc_uris = ppkpf.info.get("documentation_uris")
406 default_priority = ppkpf.info.get("default_priority")
407 if doc_uris is not None:
408 seen_docs.update(doc_uris)
409 documentation_uris.extend(doc_uris)
410 if dh_cmds is not None:
411 seen_commands.update(dh_cmds)
412 related_tools.extend(dh_cmds)
413 install_pattern = _kpf_install_pattern(dh_compat_level, ppkpf)
414 post_formatting_rewrite = ppkpf.info.get("post_formatting_rewrite")
415 packageless_is_fallback_for_all_packages = ppkpf.info.get(
416 "packageless_is_fallback_for_all_packages",
417 False,
418 )
419 # If it is a debhelper PPF, then `has_active_command` is false by default.
420 has_active_command = ppkpf.info.get("has_active_command", False)
421 else:
422 install_pattern = None
423 default_priority = None
424 post_formatting_rewrite = None
425 packageless_is_fallback_for_all_packages = False
426 has_active_command = False
427 for command in commands:
428 if isinstance(command, dict): 428 ↛ 427line 428 didn't jump to line 427 because the condition on line 428 was always true
429 command_name = command.get("command")
430 if isinstance(command_name, str) and command_name: 430 ↛ 439line 430 didn't jump to line 439 because the condition on line 430 was always true
431 if command_name not in seen_commands: 431 ↛ 434line 431 didn't jump to line 434 because the condition on line 431 was always true
432 related_tools.append(command_name)
433 seen_commands.add(command_name)
434 manpage = f"man:{command_name}(1)"
435 if manpage not in seen_docs: 435 ↛ 440line 435 didn't jump to line 440 because the condition on line 435 was always true
436 documentation_uris.append(manpage)
437 seen_docs.add(manpage)
438 else:
439 continue
440 is_active = _perl_json_bool(command.get("is-active", True))
441 if is_active is None and command_name in dh_commands.active_commands: 441 ↛ 442line 441 didn't jump to line 442 because the condition on line 441 was never true
442 is_active = True
443 if not isinstance(is_active, bool): 443 ↛ 444line 443 didn't jump to line 444 because the condition on line 443 was never true
444 continue
445 if is_active:
446 has_active_command = True
448 if debputy_integration_mode == "full": 448 ↛ 450line 448 didn't jump to line 450 because the condition on line 448 was never true
449 # dh commands are never active in full integration mode.
450 has_active_command = False
451 elif not saw_dh: 451 ↛ 455line 451 didn't jump to line 455 because the condition on line 451 was always true
452 # If we did not see `dh`, we assume classic `debhelper` where we have no way of knowing
453 # which commands are active.
454 has_active_command = True
455 dh_ppfs[stem] = _fake_PPFClassSpec(
456 debputy_plugin_metadata,
457 stem,
458 documentation_uris,
459 install_pattern,
460 default_priority=default_priority,
461 post_formatting_rewrite=post_formatting_rewrite,
462 packageless_is_fallback_for_all_packages=packageless_is_fallback_for_all_packages,
463 bug_950723=bug_950723,
464 has_active_command=has_active_command,
465 )
466 for ppkpf in dh_ppf_docs.values(): 466 ↛ 467line 466 didn't jump to line 467 because the loop on line 466 never started
467 stem = ppkpf.detection_value
468 if stem in dh_ppfs:
469 continue
471 default_priority = ppkpf.info.get("default_priority")
472 install_pattern = _kpf_install_pattern(dh_compat_level, ppkpf)
473 post_formatting_rewrite = ppkpf.info.get("post_formatting_rewrite")
474 packageless_is_fallback_for_all_packages = ppkpf.info.get(
475 "packageless_is_fallback_for_all_packages",
476 False,
477 )
478 has_active_command = (
479 ppkpf.info.get("has_active_command", False) if saw_dh else False
480 )
481 if not has_active_command:
482 dh_cmds = ppkpf.info.get("debhelper_commands")
483 if dh_cmds:
484 has_active_command = any(
485 c in dh_commands.active_commands for c in dh_cmds
486 )
487 dh_ppfs[stem] = _fake_PPFClassSpec(
488 debputy_plugin_metadata,
489 stem,
490 ppkpf.info.get("documentation_uris"),
491 install_pattern,
492 default_priority=default_priority,
493 post_formatting_rewrite=post_formatting_rewrite,
494 packageless_is_fallback_for_all_packages=packageless_is_fallback_for_all_packages,
495 has_active_command=has_active_command,
496 )
497 dh_ppf_feature_set = _fake_plugin_feature_set(plugin_feature_set, dh_ppfs)
498 all_dh_ppfs = list(
499 flatten_ppfs(
500 detect_all_packager_provided_files(
501 dh_ppf_feature_set,
502 debian_dir,
503 binary_packages,
504 allow_fuzzy_matches=True,
505 detect_typos=True,
506 ignore_paths=ignore_paths,
507 )
508 )
509 )
510 return all_dh_ppfs, missing_introspection, issues, exit_code
513def _fake_plugin_feature_set(
514 plugin_feature_set: PluginProvidedFeatureSet,
515 fake_defs: Mapping[str, PackagerProvidedFileClassSpec],
516) -> PluginProvidedFeatureSet:
517 return dataclasses.replace(plugin_feature_set, packager_provided_files=fake_defs)
520def _merge_list(
521 existing_table: Dict[str, Any],
522 key: str,
523 new_data: Optional[Sequence[str]],
524) -> None:
525 if not new_data:
526 return
527 existing_values = existing_table.get(key, [])
528 if isinstance(existing_values, tuple):
529 existing_values = list(existing_values)
530 assert isinstance(existing_values, list)
531 seen = set(existing_values)
532 existing_values.extend(x for x in new_data if x not in seen)
533 existing_table[key] = existing_values
536def _merge_ppfs(
537 identified: List[PackagingFileInfo],
538 seen_paths: Dict[str, PackagingFileInfo],
539 ppfs: List[PackagerProvidedFile],
540 context: Mapping[str, PluginProvidedKnownPackagingFile],
541 dh_compat_level: Optional[int],
542) -> None:
543 for ppf in ppfs:
544 key = ppf.path.path
545 ref_doc = ppf.definition.reference_documentation
546 documentation_uris = (
547 ref_doc.format_documentation_uris if ref_doc is not None else None
548 )
549 if not ppf.definition.installed_as_format.startswith("not-a-real-ppf"):
550 try:
551 parts = ppf.compute_dest()
552 except RuntimeError:
553 dest = None
554 else:
555 dest = "/".join(parts).lstrip(".")
556 else:
557 dest = None
558 orig_details = seen_paths.get(key)
559 if orig_details is None:
560 details: PackagingFileInfo = {
561 "path": key,
562 "pkgfile-stem": ppf.definition.stem,
563 "pkgfile-is-active-in-build": ppf.definition.has_active_command,
564 "pkgfile-explicit-package-name": ppf.uses_explicit_package_name,
565 "binary-package": ppf.package_name,
566 }
567 if ppf.expected_path is not None:
568 details["likely-typo-of"] = ppf.expected_path
569 identified.append(details)
570 else:
571 details = orig_details
572 # We do not merge the "is typo" field; if the original
573 for k, v in [
574 ("pkgfile-stem", ppf.definition.stem),
575 ("pkgfile-explicit-package-name", ppf.definition.has_active_command),
576 ("binary-package", ppf.package_name),
577 ]:
578 if k not in details:
579 details[k] = v
580 if ppf.definition.has_active_command and details.get(
581 "pkgfile-is-active-in-build", False
582 ):
583 details["pkgfile-is-active-in-build"] = True
584 if ppf.expected_path is None and "likely-typo-of" in details:
585 del details["likely-typo-of"]
587 name_segment = ppf.name_segment
588 arch_restriction = ppf.architecture_restriction
589 if name_segment is not None and "pkgfile-name-segment" not in details:
590 details["pkgfile-name-segment"] = name_segment
591 if (
592 arch_restriction is not None
593 and "pkgfile-architecture-restriction" not in details
594 ):
595 details["pkgfile-architecture-restriction"] = arch_restriction
596 if ppf.fuzzy_match and key.endswith(".in"):
597 _merge_list(details, "file-categories", ["generic-template"])
598 details["generates"] = key[:-3]
599 elif assume_not_none(ppf.path.parent_dir).get(ppf.path.name + ".in"):
600 _merge_list(details, "file-categories", ["generated"])
601 details["generated-from"] = key + ".in"
602 if dest is not None and "install-path" not in details:
603 details["install-path"] = dest
605 extra_details = context.get(ppf.definition.stem)
606 if extra_details is not None:
607 _add_known_packaging_data(details, extra_details, dh_compat_level)
609 _merge_list(details, "documentation-uris", documentation_uris)
612def _relevant_dh_commands(
613 dh_rules_addons: Iterable[str],
614 cwd: Optional[str] = None,
615) -> Tuple[List[str], int]:
616 cmd = ["dh_assistant", "list-commands", "--output-format=json"]
617 if dh_rules_addons:
618 addons = ",".join(dh_rules_addons)
619 cmd.append(f"--with={addons}")
620 try:
621 output = subprocess.check_output(
622 cmd,
623 stderr=subprocess.DEVNULL,
624 cwd=cwd,
625 )
626 except (FileNotFoundError, subprocess.CalledProcessError) as e:
627 exit_code = 127
628 if isinstance(e, subprocess.CalledProcessError):
629 exit_code = e.returncode
630 if _is_trace_log_enabled():
631 _trace_log(
632 f"Command {render_command(*cmd, cwd=cwd)} failed with {exit_code} ({cwd=})"
633 )
634 return [], exit_code
635 else:
636 data = json.loads(output)
637 commands_json = data.get("commands")
638 commands = []
639 if _is_trace_log_enabled(): 639 ↛ 640line 639 didn't jump to line 640 because the condition on line 639 was never true
640 _trace_log(
641 f"Command {render_command(*cmd, cwd=cwd)} returned successfully: {output}"
642 )
643 for command in commands_json:
644 if isinstance(command, dict): 644 ↛ 643line 644 didn't jump to line 643 because the condition on line 644 was always true
645 command_name = command.get("command")
646 if isinstance(command_name, str) and command_name: 646 ↛ 643line 646 didn't jump to line 643 because the condition on line 646 was always true
647 commands.append(command_name)
648 return commands, 0
651def _add_known_packaging_data(
652 details: PackagingFileInfo,
653 plugin_data: PluginProvidedKnownPackagingFile,
654 dh_compat_level: Optional[int],
655):
656 install_pattern = _kpf_install_pattern(
657 dh_compat_level,
658 plugin_data,
659 )
660 config_features = plugin_data.info.get("config_features")
661 if config_features:
662 config_features = expand_known_packaging_config_features(
663 dh_compat_level or 0,
664 config_features,
665 )
666 _merge_list(details, "config-features", config_features)
668 if dh_compat_level is not None:
669 extra_config_features = []
670 for dh_compat_rule in _relevant_dh_compat_rules(
671 dh_compat_level, plugin_data.info
672 ):
673 cf = dh_compat_rule.get("add_config_features")
674 if cf:
675 extra_config_features.extend(cf)
676 if extra_config_features:
677 extra_config_features = expand_known_packaging_config_features(
678 dh_compat_level,
679 extra_config_features,
680 )
681 _merge_list(details, "config-features", extra_config_features)
682 if "install-pattern" not in details and install_pattern is not None:
683 details["install-pattern"] = install_pattern
684 for mk, ok in [
685 ("file_categories", "file-categories"),
686 ("documentation_uris", "documentation-uris"),
687 ("debputy_cmd_templates", "debputy-cmd-templates"),
688 ]:
689 value = plugin_data.info.get(mk)
690 if value and ok == "debputy-cmd-templates":
691 value = [escape_shell(*c) for c in value]
692 _merge_list(details, ok, value)
695def _scan_debian_dir(debian_dir: VirtualPath) -> Iterator[VirtualPath]:
696 for p in debian_dir.iterdir:
697 yield p
698 if p.is_dir and p.path in ("debian/source", "debian/tests"):
699 yield from p.iterdir
702_POST_FORMATTING_REWRITE = {
703 "period-to-underscore": lambda n: n.replace(".", "_"),
704}