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