Coverage for src/debputy/dh_migration/migrators_impl.py: 81%
761 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-10-19 09:24 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-10-19 09:24 +0000
1import collections
2import dataclasses
3import functools
4import json
5import os
6import re
7import subprocess
8from collections.abc import Iterable, Mapping, Callable, Container
9from itertools import product, chain
10from typing import (
11 Any,
12 TypeVar,
13)
15from debian.deb822 import Deb822
17from debputy import DEBPUTY_DOC_ROOT_DIR
18from debputy.architecture_support import dpkg_architecture_table
19from debputy.deb_packaging_support import dpkg_field_list_pkg_dep
20from debputy.dh.debhelper_emulation import (
21 dhe_filedoublearray,
22 DHConfigFileLine,
23 dhe_pkgfile,
24)
25from debputy.dh.dh_assistant import (
26 read_dh_addon_sequences,
27)
28from debputy.dh_migration.models import (
29 ConflictingChange,
30 FeatureMigration,
31 UnsupportedFeature,
32 AcceptableMigrationIssues,
33 DHMigrationSubstitution,
34 MigrationRequest,
35)
36from debputy.highlevel_manifest import (
37 MutableYAMLSymlink,
38 HighLevelManifest,
39 MutableYAMLConffileManagementItem,
40 AbstractMutableYAMLInstallRule,
41)
42from debputy.installations import MAN_GUESS_FROM_BASENAME, MAN_GUESS_LANG_FROM_PATH
43from debputy.packages import BinaryPackage
44from debputy.plugin.api import VirtualPath
45from debputy.plugin.api.spec import (
46 INTEGRATION_MODE_DH_DEBPUTY_RRR,
47 INTEGRATION_MODE_DH_DEBPUTY,
48 DebputyIntegrationMode,
49 INTEGRATION_MODE_FULL,
50)
51from debputy.util import (
52 _error,
53 PKGVERSION_REGEX,
54 PKGNAME_REGEX,
55 _normalize_path,
56 assume_not_none,
57 has_glob_magic,
58)
61class ContainsEverything:
63 def __contains__(self, item: str) -> bool:
64 return True
67# Align with debputy.py
68DH_COMMANDS_REPLACED: Mapping[DebputyIntegrationMode, Container[str]] = {
69 INTEGRATION_MODE_DH_DEBPUTY_RRR: frozenset(
70 {
71 "dh_fixperms",
72 "dh_shlibdeps",
73 "dh_gencontrol",
74 "dh_md5sums",
75 "dh_builddeb",
76 }
77 ),
78 INTEGRATION_MODE_DH_DEBPUTY: frozenset(
79 {
80 "dh_install",
81 "dh_installdocs",
82 "dh_installchangelogs",
83 "dh_installexamples",
84 "dh_installman",
85 "dh_installcatalogs",
86 "dh_installcron",
87 "dh_installdebconf",
88 "dh_installemacsen",
89 "dh_installifupdown",
90 "dh_installinfo",
91 "dh_installinit",
92 "dh_installsysusers",
93 "dh_installtmpfiles",
94 "dh_installsystemd",
95 "dh_installsystemduser",
96 "dh_installmenu",
97 "dh_installmime",
98 "dh_installmodules",
99 "dh_installlogcheck",
100 "dh_installlogrotate",
101 "dh_installpam",
102 "dh_installppp",
103 "dh_installudev",
104 "dh_installgsettings",
105 "dh_installinitramfs",
106 "dh_installalternatives",
107 "dh_bugfiles",
108 "dh_ucf",
109 "dh_lintian",
110 "dh_icons",
111 "dh_usrlocal",
112 "dh_perl",
113 "dh_link",
114 "dh_installwm",
115 "dh_installxfonts",
116 "dh_strip_nondeterminism",
117 "dh_compress",
118 "dh_fixperms",
119 "dh_dwz",
120 "dh_strip",
121 "dh_makeshlibs",
122 "dh_shlibdeps",
123 "dh_missing",
124 "dh_installdeb",
125 "dh_gencontrol",
126 "dh_md5sums",
127 "dh_builddeb",
128 }
129 ),
130 INTEGRATION_MODE_FULL: ContainsEverything(),
131}
133_GS_DOC = f"{DEBPUTY_DOC_ROOT_DIR}/GETTING-STARTED-WITH-dh-debputy.md"
134MIGRATION_AID_FOR_OVERRIDDEN_COMMANDS = {
135 "dh_installinit": f"{_GS_DOC}#covert-your-overrides-for-dh_installsystemd-dh_installinit-if-any",
136 "dh_installsystemd": f"{_GS_DOC}#covert-your-overrides-for-dh_installsystemd-dh_installinit-if-any",
137 "dh_fixperms": f"{_GS_DOC}#convert-your-overrides-or-excludes-for-dh_fixperms-if-any",
138 "dh_gencontrol": f"{_GS_DOC}#convert-your-overrides-for-dh_gencontrol-if-any",
139}
142@dataclasses.dataclass(frozen=True, slots=True)
143class UnsupportedDHConfig:
144 dh_config_basename: str
145 dh_tool: str
146 bug_950723_prefix_matching: bool = False
147 is_missing_migration: bool = False
150@dataclasses.dataclass(frozen=True, slots=True)
151class DHSequenceMigration:
152 debputy_plugin: str
153 remove_dh_sequence: bool = True
154 must_use_zz_debputy: bool = False
157UNSUPPORTED_DH_CONFIGS_AND_TOOLS_FOR_ZZ_DEBPUTY = [
158 UnsupportedDHConfig("config", "dh_installdebconf"),
159 UnsupportedDHConfig("templates", "dh_installdebconf"),
160 UnsupportedDHConfig("emacsen-compat", "dh_installemacsen"),
161 UnsupportedDHConfig("emacsen-install", "dh_installemacsen"),
162 UnsupportedDHConfig("emacsen-remove", "dh_installemacsen"),
163 UnsupportedDHConfig("emacsen-startup", "dh_installemacsen"),
164 # The `upstart` file should be long dead, but we might as well detect it.
165 UnsupportedDHConfig("upstart", "dh_installinit"),
166 # dh_installsystemduser
167 UnsupportedDHConfig(
168 "user.path", "dh_installsystemduser", bug_950723_prefix_matching=False
169 ),
170 UnsupportedDHConfig(
171 "user.path", "dh_installsystemduser", bug_950723_prefix_matching=True
172 ),
173 UnsupportedDHConfig(
174 "user.service", "dh_installsystemduser", bug_950723_prefix_matching=False
175 ),
176 UnsupportedDHConfig(
177 "user.service", "dh_installsystemduser", bug_950723_prefix_matching=True
178 ),
179 UnsupportedDHConfig(
180 "user.socket", "dh_installsystemduser", bug_950723_prefix_matching=False
181 ),
182 UnsupportedDHConfig(
183 "user.socket", "dh_installsystemduser", bug_950723_prefix_matching=True
184 ),
185 UnsupportedDHConfig(
186 "user.target", "dh_installsystemduser", bug_950723_prefix_matching=False
187 ),
188 UnsupportedDHConfig(
189 "user.target", "dh_installsystemduser", bug_950723_prefix_matching=True
190 ),
191 UnsupportedDHConfig(
192 "user.timer", "dh_installsystemduser", bug_950723_prefix_matching=False
193 ),
194 UnsupportedDHConfig(
195 "user.timer", "dh_installsystemduser", bug_950723_prefix_matching=True
196 ),
197 UnsupportedDHConfig("menu", "dh_installmenu"),
198 UnsupportedDHConfig("menu-method", "dh_installmenu"),
199 UnsupportedDHConfig("ucf", "dh_ucf"),
200 UnsupportedDHConfig("wm", "dh_installwm"),
201 UnsupportedDHConfig("triggers", "dh_installdeb"),
202 UnsupportedDHConfig("postinst", "dh_installdeb"),
203 UnsupportedDHConfig("postrm", "dh_installdeb"),
204 UnsupportedDHConfig("preinst", "dh_installdeb"),
205 UnsupportedDHConfig("prerm", "dh_installdeb"),
206 UnsupportedDHConfig("menutest", "dh_installdeb"),
207 UnsupportedDHConfig("isinstallable", "dh_installdeb"),
208]
209SUPPORTED_DH_ADDONS_WITH_ZZ_DEBPUTY = frozenset(
210 {
211 # debputy's own
212 "debputy",
213 "zz-debputy",
214 # debhelper provided sequences that should work.
215 "single-binary",
216 }
217)
218DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY = frozenset(
219 [
220 # The `zz-debputy` add-on replaces the `zz-debputy-rrr` plugin.
221 "zz-debputy-rrr",
222 # Sequences debputy directly replaces
223 "dwz",
224 "elf-tools",
225 "installinitramfs",
226 "installsysusers",
227 "doxygen",
228 # Sequences that are embedded fully into debputy
229 "bash-completion",
230 "shell-completions",
231 "sodeps",
232 ]
233)
234DH_ADDONS_TO_PLUGINS = {
235 "gnome": DHSequenceMigration(
236 "gnome",
237 # The sequence still provides a command for the clean sequence
238 remove_dh_sequence=False,
239 must_use_zz_debputy=True,
240 ),
241 "grantlee": DHSequenceMigration(
242 "grantlee",
243 remove_dh_sequence=True,
244 must_use_zz_debputy=True,
245 ),
246 "numpy3": DHSequenceMigration(
247 "numpy3",
248 # The sequence provides (build-time) dependencies that we cannot provide
249 remove_dh_sequence=False,
250 must_use_zz_debputy=True,
251 ),
252 "perl-openssl": DHSequenceMigration(
253 "perl-openssl",
254 # The sequence provides (build-time) dependencies that we cannot provide
255 remove_dh_sequence=False,
256 must_use_zz_debputy=True,
257 ),
258}
261def _dh_config_file(
262 debian_dir: VirtualPath,
263 dctrl_bin: BinaryPackage,
264 basename: str,
265 helper_name: str,
266 acceptable_migration_issues: AcceptableMigrationIssues,
267 feature_migration: FeatureMigration,
268 manifest: HighLevelManifest,
269 support_executable_files: bool = False,
270 allow_dh_exec_rename: bool = False,
271 pkgfile_lookup: bool = True,
272 remove_on_migration: bool = True,
273) -> tuple[None, None] | tuple[VirtualPath, Iterable[DHConfigFileLine]]:
274 mutable_manifest = assume_not_none(manifest.mutable_manifest)
275 dh_config_file = (
276 dhe_pkgfile(debian_dir, dctrl_bin, basename)
277 if pkgfile_lookup
278 else debian_dir.get(basename)
279 )
280 if dh_config_file is None or dh_config_file.is_dir:
281 return None, None
282 if dh_config_file.is_executable and not support_executable_files:
283 primary_key = f"executable-{helper_name}-config"
284 if (
285 primary_key in acceptable_migration_issues
286 or "any-executable-dh-configs" in acceptable_migration_issues
287 ):
288 feature_migration.warn(
289 f'TODO: MANUAL MIGRATION of executable dh config "{dh_config_file}" is required.'
290 )
291 return None, None
292 raise UnsupportedFeature(
293 f"Executable configuration files not supported (found: {dh_config_file}).",
294 [primary_key, "any-executable-dh-configs"],
295 )
297 if remove_on_migration:
298 feature_migration.remove_on_success(dh_config_file.fs_path)
299 substitution = DHMigrationSubstitution(
300 dpkg_architecture_table(),
301 acceptable_migration_issues,
302 feature_migration,
303 mutable_manifest,
304 )
305 content = dhe_filedoublearray(
306 dh_config_file,
307 substitution,
308 allow_dh_exec_rename=allow_dh_exec_rename,
309 )
310 return dh_config_file, content
313def _validate_rm_mv_conffile(
314 package: str,
315 config_line: DHConfigFileLine,
316) -> tuple[str, str, str | None, str | None, str | None]:
317 cmd, *args = config_line.tokens
318 if "--" in config_line.tokens: 318 ↛ 319line 318 didn't jump to line 319 because the condition on line 318 was never true
319 raise ValueError(
320 f'The maintscripts file "{config_line.config_file.path}" for {package} includes a "--" in line'
321 f" {config_line.line_no}. The offending line is: {config_line.original_line}"
322 )
323 if cmd == "rm_conffile":
324 min_args = 1
325 max_args = 3
326 else:
327 min_args = 2
328 max_args = 4
329 if len(args) > max_args or len(args) < min_args: 329 ↛ 330line 329 didn't jump to line 330 because the condition on line 329 was never true
330 raise ValueError(
331 f'The "{cmd}" command takes at least {min_args} and at most {max_args} arguments. However,'
332 f' in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), there'
333 f" are {len(args)} arguments. The offending line is: {config_line.original_line}"
334 )
336 obsolete_conffile = args[0]
337 new_conffile = args[1] if cmd == "mv_conffile" else None
338 prior_version = args[min_args] if len(args) > min_args else None
339 owning_package = args[min_args + 1] if len(args) > min_args + 1 else None
340 if not obsolete_conffile.startswith("/"): 340 ↛ 341line 340 didn't jump to line 341 because the condition on line 340 was never true
341 raise ValueError(
342 f'The (old-)conffile parameter for {cmd} must be absolute (i.e., start with "/"). However,'
343 f' in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), it was specified'
344 f' as "{obsolete_conffile}". The offending line is: {config_line.original_line}'
345 )
346 if new_conffile is not None and not new_conffile.startswith("/"): 346 ↛ 347line 346 didn't jump to line 347 because the condition on line 346 was never true
347 raise ValueError(
348 f'The new-conffile parameter for {cmd} must be absolute (i.e., start with "/"). However,'
349 f' in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), it was specified'
350 f' as "{new_conffile}". The offending line is: {config_line.original_line}'
351 )
352 if prior_version is not None and not PKGVERSION_REGEX.fullmatch(prior_version): 352 ↛ 353line 352 didn't jump to line 353 because the condition on line 352 was never true
353 raise ValueError(
354 f"The prior-version parameter for {cmd} must be a valid package version (i.e., match"
355 f' {PKGVERSION_REGEX}). However, in "{config_line.config_file.path}" line {config_line.line_no}'
356 f' (for {package}), it was specified as "{prior_version}". The offending line is:'
357 f" {config_line.original_line}"
358 )
359 if owning_package is not None and not PKGNAME_REGEX.fullmatch(owning_package): 359 ↛ 360line 359 didn't jump to line 360 because the condition on line 359 was never true
360 raise ValueError(
361 f"The package parameter for {cmd} must be a valid package name (i.e., match {PKGNAME_REGEX})."
362 f' However, in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), it'
363 f' was specified as "{owning_package}". The offending line is: {config_line.original_line}'
364 )
365 return cmd, obsolete_conffile, new_conffile, prior_version, owning_package
368_BASH_COMPLETION_RE = re.compile(
369 r"""
370 (^|[|&;])\s*complete.*-[A-Za-z].*
371 | \$\(.*\)
372 | \s*compgen.*-[A-Za-z].*
373 | \s*if.*;.*then/
374""",
375 re.VERBOSE,
376)
379def migrate_bash_completion(
380 migration_request: MigrationRequest,
381 feature_migration: FeatureMigration,
382) -> None:
383 feature_migration.tagline = "dh_bash-completion files"
384 is_single_binary = migration_request.is_single_binary_package
385 manifest = migration_request.manifest
386 debian_dir = migration_request.debian_dir
387 mutable_manifest = assume_not_none(manifest.mutable_manifest)
388 installations = mutable_manifest.installations(create_if_absent=False)
390 for dctrl_bin in migration_request.all_packages:
391 dh_file = dhe_pkgfile(debian_dir, dctrl_bin, "bash-completion")
392 if dh_file is None:
393 continue
394 is_bash_completion_file = False
395 with dh_file.open() as fd:
396 for line in fd:
397 line = line.strip()
398 if not line or line[0] == "#": 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true
399 continue
400 if _BASH_COMPLETION_RE.search(line):
401 is_bash_completion_file = True
402 break
403 if not is_bash_completion_file:
404 _, content = _dh_config_file(
405 debian_dir,
406 dctrl_bin,
407 "bash-completion",
408 "dh_bash-completion",
409 migration_request.acceptable_migration_issues,
410 feature_migration,
411 manifest,
412 support_executable_files=True,
413 )
414 else:
415 content = None
417 if content:
418 install_dest_sources: list[str] = []
419 install_as_rules: list[tuple[str, str]] = []
420 for dhe_line in content:
421 if len(dhe_line.tokens) > 2: 421 ↛ 422line 421 didn't jump to line 422 because the condition on line 421 was never true
422 raise UnsupportedFeature(
423 f"The dh_bash-completion file {dh_file.path} more than two words on"
424 f' line {dhe_line.line_no} (line: "{dhe_line.original_line}").'
425 )
426 source = dhe_line.tokens[0]
427 dest_basename = (
428 dhe_line.tokens[1]
429 if len(dhe_line.tokens) > 1
430 else os.path.basename(source)
431 )
432 if source.startswith("debian/") and not has_glob_magic(source):
433 if dctrl_bin.name != dest_basename:
434 dest_path = (
435 f"debian/{dctrl_bin.name}.{dest_basename}.bash-completion"
436 )
437 else:
438 dest_path = f"debian/{dest_basename}.bash-completion"
439 feature_migration.rename_on_success(source, dest_path)
440 elif len(dhe_line.tokens) == 1:
441 install_dest_sources.append(source)
442 else:
443 install_as_rules.append((source, dest_basename))
445 if install_dest_sources: 445 ↛ 459line 445 didn't jump to line 459 because the condition on line 445 was always true
446 sources: list[str] | str = (
447 install_dest_sources
448 if len(install_dest_sources) > 1
449 else install_dest_sources[0]
450 )
451 installations.append(
452 AbstractMutableYAMLInstallRule.install_dest(
453 sources=sources,
454 dest_dir="{{path:BASH_COMPLETION_DIR}}",
455 into=dctrl_bin.name if not is_single_binary else None,
456 )
457 )
459 for source, dest_basename in install_as_rules:
460 installations.append(
461 AbstractMutableYAMLInstallRule.install_as(
462 source=source,
463 install_as="{{path:BASH_COMPLETION_DIR}}/" + dest_basename,
464 into=dctrl_bin.name if not is_single_binary else None,
465 )
466 )
469_SHELL_COMPLETIONS_RE = re.compile(r"^\s*\S+\s+\S+\s+\S")
472def migrate_shell_completions(
473 migration_request: MigrationRequest,
474 feature_migration: FeatureMigration,
475) -> None:
476 feature_migration.tagline = "dh_shell_completions files"
477 manifest = migration_request.manifest
478 debian_dir = migration_request.debian_dir
479 is_single_binary = migration_request.is_single_binary_package
480 mutable_manifest = assume_not_none(manifest.mutable_manifest)
481 installations = mutable_manifest.installations(create_if_absent=False)
482 # Note: The bash completion script used `bash-completion` whereas `dh_shell_completions` uses
483 # `...-completions` (note the trailing `s`). In `debputy`, we always use the singular notation
484 # because we use "one file, one completion (ruleset)".
485 completions = ["bash", "fish", "zsh"]
487 for completion, dctrl_bin in product(completions, migration_request.all_packages):
488 dh_file = dhe_pkgfile(debian_dir, dctrl_bin, f"{completion}-completions")
489 if dh_file is None:
490 continue
491 is_completion_file = False
492 with dh_file.open() as fd:
493 for line in fd:
494 line = line.strip()
495 if not line or line[0] == "#":
496 continue
497 if _SHELL_COMPLETIONS_RE.search(line):
498 is_completion_file = True
499 break
500 if is_completion_file:
501 dest_path = f"debian/{dctrl_bin.name}.{completion}-completion"
502 feature_migration.rename_on_success(dh_file.fs_path, dest_path)
503 continue
505 _, content = _dh_config_file(
506 debian_dir,
507 dctrl_bin,
508 f"{completion}-completions",
509 "dh_shell_completions",
510 migration_request.acceptable_migration_issues,
511 feature_migration,
512 manifest,
513 remove_on_migration=True,
514 )
516 if content: 516 ↛ 487line 516 didn't jump to line 487 because the condition on line 516 was always true
517 install_dest_sources: list[str] = []
518 install_as_rules: list[tuple[str, str]] = []
519 for dhe_line in content:
520 if len(dhe_line.tokens) > 2: 520 ↛ 521line 520 didn't jump to line 521 because the condition on line 520 was never true
521 raise UnsupportedFeature(
522 f"The dh_shell_completions file {dh_file.path} more than two words on"
523 f' line {dhe_line.line_no} (line: "{dhe_line.original_line}").'
524 )
525 source = dhe_line.tokens[0]
526 dest_basename = (
527 dhe_line.tokens[1]
528 if len(dhe_line.tokens) > 1
529 else os.path.basename(source)
530 )
531 if source.startswith("debian/") and not has_glob_magic(source):
532 if dctrl_bin.name != dest_basename:
533 dest_path = f"debian/{dctrl_bin.name}.{dest_basename}.{completion}-completion"
534 else:
535 dest_path = f"debian/{dest_basename}.{completion}-completion"
536 feature_migration.rename_on_success(source, dest_path)
537 elif len(dhe_line.tokens) == 1:
538 install_dest_sources.append(source)
539 else:
540 install_as_rules.append((source, dest_basename))
542 completion_dir_variable = (
543 "{{path:" + f"{completion.upper()}_COMPLETION_DIR" + "}}"
544 )
546 if install_dest_sources: 546 ↛ 560line 546 didn't jump to line 560 because the condition on line 546 was always true
547 sources: list[str] | str = (
548 install_dest_sources
549 if len(install_dest_sources) > 1
550 else install_dest_sources[0]
551 )
552 installations.append(
553 AbstractMutableYAMLInstallRule.install_dest(
554 sources=sources,
555 dest_dir=completion_dir_variable,
556 into=dctrl_bin.name if not is_single_binary else None,
557 )
558 )
560 for source, dest_basename in install_as_rules:
561 installations.append(
562 AbstractMutableYAMLInstallRule.install_as(
563 source=source,
564 install_as=f"{completion_dir_variable}/{dest_basename}",
565 into=dctrl_bin.name if not is_single_binary else None,
566 )
567 )
570def migrate_dh_installsystemd_files(
571 migration_request: MigrationRequest,
572 feature_migration: FeatureMigration,
573) -> None:
574 debian_dir = migration_request.debian_dir
575 feature_migration.tagline = "dh_installsystemd files"
576 for dctrl_bin in migration_request.all_packages:
577 for stem in [
578 "path",
579 "service",
580 "socket",
581 "target",
582 "timer",
583 ]:
584 pkgfile = dhe_pkgfile(
585 debian_dir, dctrl_bin, stem, bug_950723_prefix_matching=True
586 )
587 if not pkgfile:
588 continue
589 if not pkgfile.name.endswith(f".{stem}") or "@." not in pkgfile.name: 589 ↛ 590line 589 didn't jump to line 590 because the condition on line 589 was never true
590 raise UnsupportedFeature(
591 f'Unable to determine the correct name for {pkgfile.fs_path}. It should be a ".@{stem}"'
592 f" file now (foo@.service => foo.@service)"
593 )
594 newname = pkgfile.name.replace("@.", ".")
595 newname = newname[: -len(stem)] + f"@{stem}"
596 feature_migration.rename_on_success(
597 pkgfile.fs_path, os.path.join(debian_dir.fs_path, newname)
598 )
601def migrate_clean_file(
602 migration_request: MigrationRequest,
603 feature_migration: FeatureMigration,
604) -> None:
605 feature_migration.tagline = "debian/clean"
606 clean_file = migration_request.debian_dir.get("clean")
607 if clean_file is None:
608 return
610 mutable_manifest = assume_not_none(migration_request.manifest.mutable_manifest)
612 substitution = DHMigrationSubstitution(
613 dpkg_architecture_table(),
614 migration_request.acceptable_migration_issues,
615 feature_migration,
616 mutable_manifest,
617 )
618 content = dhe_filedoublearray(
619 clean_file,
620 substitution,
621 )
623 remove_during_clean_rules = mutable_manifest.remove_during_clean(
624 create_if_absent=False
625 )
626 tokens = chain.from_iterable(c.tokens for c in content)
627 rules_before = len(remove_during_clean_rules)
628 remove_during_clean_rules.extend(tokens)
629 rules_after = len(remove_during_clean_rules)
630 feature_migration.successful_manifest_changes += rules_after - rules_before
631 feature_migration.remove_on_success(clean_file.fs_path)
634def migrate_maintscript(
635 migration_request: MigrationRequest,
636 feature_migration: FeatureMigration,
637) -> None:
638 feature_migration.tagline = "dh_installdeb files"
639 manifest = migration_request.manifest
640 mutable_manifest = assume_not_none(manifest.mutable_manifest)
641 for dctrl_bin in migration_request.all_packages:
642 mainscript_file, content = _dh_config_file(
643 migration_request.debian_dir,
644 dctrl_bin,
645 "maintscript",
646 "dh_installdeb",
647 migration_request.acceptable_migration_issues,
648 feature_migration,
649 manifest,
650 )
652 if mainscript_file is None:
653 continue
654 assert content is not None
656 package_definition = mutable_manifest.package(dctrl_bin.name)
657 conffiles = {
658 it.obsolete_conffile: it
659 for it in package_definition.conffile_management_items()
660 }
661 seen_conffiles = set()
663 for dhe_line in content:
664 cmd = dhe_line.tokens[0]
665 if cmd not in {"rm_conffile", "mv_conffile"}: 665 ↛ 666line 665 didn't jump to line 666 because the condition on line 665 was never true
666 raise UnsupportedFeature(
667 f"The dh_installdeb file {mainscript_file.path} contains the (currently)"
668 f' unsupported command "{cmd}" on line {dhe_line.line_no}'
669 f' (line: "{dhe_line.original_line}")'
670 )
672 try:
673 (
674 _,
675 obsolete_conffile,
676 new_conffile,
677 prior_to_version,
678 owning_package,
679 ) = _validate_rm_mv_conffile(dctrl_bin.name, dhe_line)
680 except ValueError as e:
681 _error(
682 f"Validation error in {mainscript_file} on line {dhe_line.line_no}. The error was: {e.args[0]}."
683 )
685 if obsolete_conffile in seen_conffiles: 685 ↛ 686line 685 didn't jump to line 686 because the condition on line 685 was never true
686 raise ConflictingChange(
687 f'The {mainscript_file} file defines actions for "{obsolete_conffile}" twice!'
688 f" Please ensure that it is defined at most once in that file."
689 )
690 seen_conffiles.add(obsolete_conffile)
692 if cmd == "rm_conffile":
693 item = MutableYAMLConffileManagementItem.rm_conffile(
694 obsolete_conffile,
695 prior_to_version,
696 owning_package,
697 )
698 else:
699 assert cmd == "mv_conffile"
700 item = MutableYAMLConffileManagementItem.mv_conffile(
701 obsolete_conffile,
702 assume_not_none(new_conffile),
703 prior_to_version,
704 owning_package,
705 )
707 existing_def = conffiles.get(item.obsolete_conffile)
708 if existing_def is not None: 708 ↛ 709line 708 didn't jump to line 709 because the condition on line 708 was never true
709 if not (
710 item.command == existing_def.command
711 and item.new_conffile == existing_def.new_conffile
712 and item.prior_to_version == existing_def.prior_to_version
713 and item.owning_package == existing_def.owning_package
714 ):
715 raise ConflictingChange(
716 f"The maintscript defines the action {item.command} for"
717 f' "{obsolete_conffile}" in {mainscript_file}, but there is another'
718 f" conffile management definition for same path defined already (in the"
719 f" existing manifest or an migration e.g., inside {mainscript_file})"
720 )
721 continue
723 package_definition.add_conffile_management(item)
724 feature_migration.successful_manifest_changes += 1
727@dataclasses.dataclass(slots=True)
728class SourcesAndConditional:
729 dest_dir: str | None = None
730 sources: list[str] = dataclasses.field(default_factory=list)
731 conditional: str | Mapping[str, Any] | None = None
734def _raise_on_unsupported_path(
735 p: str,
736 path: VirtualPath,
737 line_no: int,
738) -> str:
739 if p.startswith("../") or any(s == ".." for s in p.split("/")): 739 ↛ 740line 739 didn't jump to line 740 because the condition on line 739 was never true
740 _error(
741 f"Sorry, the path name {p!r} provided in {path.fs_path} on line {line_no} is not supported. Please rewrite it to a path relative to the package root without upward segments"
742 )
743 return p
746def _strip_d_tmp(p: str, path: VirtualPath, line_no: int) -> str:
747 if p.startswith("debian/tmp/") and len(p) > 11:
748 return p[11:]
749 if p.startswith("../"):
750 pruned = p[3:]
751 if pruned.startswith("../"): 751 ↛ 752line 751 didn't jump to line 752 because the condition on line 751 was never true
752 _raise_on_unsupported_path(p, path, line_no)
753 _error(
754 f"Internal error: _raise_on_unsupported_path should have rejected the path at {p!r} ({path.fs_path}:{line_no})"
755 )
756 return f"debian/{pruned}"
758 return p
761def migrate_install_file(
762 migration_request: MigrationRequest,
763 feature_migration: FeatureMigration,
764) -> None:
765 feature_migration.tagline = "dh_install config files"
766 manifest = migration_request.manifest
767 mutable_manifest = assume_not_none(manifest.mutable_manifest)
768 installations = mutable_manifest.installations(create_if_absent=False)
769 priority_lines = []
770 remaining_install_lines = []
771 warn_about_fixmes_in_dest_dir = False
773 is_single_binary = migration_request.is_single_binary_package
775 for dctrl_bin in migration_request.all_packages:
776 install_file, content = _dh_config_file(
777 migration_request.debian_dir,
778 dctrl_bin,
779 "install",
780 "dh_install",
781 migration_request.acceptable_migration_issues,
782 feature_migration,
783 manifest,
784 support_executable_files=True,
785 allow_dh_exec_rename=True,
786 )
787 if not install_file or not content:
788 continue
789 current_sources = []
790 sources_by_destdir: dict[tuple[str, tuple[str, ...]], SourcesAndConditional] = (
791 {}
792 )
793 install_as_rules = []
794 multi_dest = collections.defaultdict(list)
795 seen_sources = set()
796 multi_dest_sources: set[str] = set()
798 for dhe_line in content:
799 special_rule = None
800 if "=>" in dhe_line.tokens:
801 if dhe_line.tokens[0] == "=>" and len(dhe_line.tokens) == 2:
802 # This rule must be as early as possible to retain the semantics
803 path = _strip_d_tmp(
804 _normalize_path(
805 dhe_line.tokens[1],
806 with_prefix=False,
807 allow_and_keep_upward_segments=True,
808 ),
809 dhe_line.config_file,
810 dhe_line.line_no,
811 )
812 special_rule = AbstractMutableYAMLInstallRule.install_dest(
813 path,
814 dctrl_bin.name if not is_single_binary else None,
815 dest_dir=None,
816 when=dhe_line.conditional(),
817 )
818 elif len(dhe_line.tokens) != 3: 818 ↛ 819line 818 didn't jump to line 819 because the condition on line 818 was never true
819 _error(
820 f"Validation error in {install_file.path} on line {dhe_line.line_no}. Cannot migrate dh-exec"
821 ' renames that is not exactly "SOURCE => TARGET" or "=> TARGET".'
822 )
823 else:
824 install_rule = AbstractMutableYAMLInstallRule.install_as(
825 _strip_d_tmp(
826 _normalize_path(
827 dhe_line.tokens[0],
828 with_prefix=False,
829 allow_and_keep_upward_segments=True,
830 ),
831 dhe_line.config_file,
832 dhe_line.line_no,
833 ),
834 _raise_on_unsupported_path(
835 _normalize_path(
836 dhe_line.tokens[2],
837 with_prefix=False,
838 allow_and_keep_upward_segments=True,
839 ),
840 dhe_line.config_file,
841 dhe_line.line_no,
842 ),
843 dctrl_bin.name if not is_single_binary else None,
844 when=dhe_line.conditional(),
845 )
846 install_as_rules.append(install_rule)
847 else:
848 if len(dhe_line.tokens) > 1:
849 sources = list(
850 _strip_d_tmp(
851 _normalize_path(
852 w,
853 with_prefix=False,
854 allow_and_keep_upward_segments=True,
855 ),
856 dhe_line.config_file,
857 dhe_line.line_no,
858 )
859 for w in dhe_line.tokens[:-1]
860 )
861 dest_dir = _raise_on_unsupported_path(
862 _normalize_path(
863 dhe_line.tokens[-1],
864 with_prefix=False,
865 allow_and_keep_upward_segments=True,
866 ),
867 dhe_line.config_file,
868 dhe_line.line_no,
869 )
870 else:
871 sources = list(
872 _strip_d_tmp(
873 _normalize_path(
874 w,
875 with_prefix=False,
876 allow_and_keep_upward_segments=True,
877 ),
878 dhe_line.config_file,
879 dhe_line.line_no,
880 )
881 for w in dhe_line.tokens
882 )
883 dest_dir = None
885 multi_dest_sources.update(s for s in sources if s in seen_sources)
886 seen_sources.update(sources)
888 if dest_dir is None and dhe_line.conditional() is None:
889 current_sources.extend(sources)
890 continue
891 key = (dest_dir, dhe_line.conditional_key())
892 ctor = functools.partial(
893 SourcesAndConditional,
894 dest_dir=dest_dir,
895 conditional=dhe_line.conditional(),
896 )
897 md = _fetch_or_create(
898 sources_by_destdir,
899 key,
900 ctor,
901 )
902 md.sources.extend(sources)
904 if special_rule:
905 priority_lines.append(special_rule)
907 remaining_install_lines.extend(install_as_rules)
909 for md in sources_by_destdir.values():
910 if multi_dest_sources:
911 sources = [s for s in md.sources if s not in multi_dest_sources]
912 already_installed = (s for s in md.sources if s in multi_dest_sources)
913 for s in already_installed:
914 # The sources are ignored, so we can reuse the object as-is
915 multi_dest[s].append(md)
916 if not sources:
917 continue
918 else:
919 sources = md.sources
920 install_rule = AbstractMutableYAMLInstallRule.install_dest(
921 sources[0] if len(sources) == 1 else sources,
922 dctrl_bin.name if not is_single_binary else None,
923 dest_dir=md.dest_dir,
924 when=md.conditional,
925 )
926 remaining_install_lines.append(install_rule)
928 if current_sources:
929 if multi_dest_sources:
930 sources = [s for s in current_sources if s not in multi_dest_sources]
931 already_installed = (
932 s for s in current_sources if s in multi_dest_sources
933 )
934 for s in already_installed:
935 # The sources are ignored, so we can reuse the object as-is
936 dest_dir = os.path.dirname(s)
937 if has_glob_magic(dest_dir):
938 warn_about_fixmes_in_dest_dir = True
939 dest_dir = f"FIXME: {dest_dir} (could not reliably compute the dest dir)"
940 multi_dest[s].append(
941 SourcesAndConditional(
942 dest_dir=dest_dir,
943 conditional=None,
944 )
945 )
946 else:
947 sources = current_sources
949 if sources:
950 install_rule = AbstractMutableYAMLInstallRule.install_dest(
951 sources[0] if len(sources) == 1 else sources,
952 dctrl_bin.name if not is_single_binary else None,
953 dest_dir=None,
954 )
955 remaining_install_lines.append(install_rule)
957 if multi_dest:
958 for source, dest_and_conditionals in multi_dest.items():
959 dest_dirs = [dac.dest_dir for dac in dest_and_conditionals]
960 # We assume the conditional is the same.
961 conditional = next(
962 iter(
963 dac.conditional
964 for dac in dest_and_conditionals
965 if dac.conditional is not None
966 ),
967 None,
968 )
969 remaining_install_lines.append(
970 AbstractMutableYAMLInstallRule.multi_dest_install(
971 source,
972 dest_dirs,
973 dctrl_bin.name if not is_single_binary else None,
974 when=conditional,
975 )
976 )
978 if priority_lines:
979 installations.extend(priority_lines)
981 if remaining_install_lines:
982 installations.extend(remaining_install_lines)
984 feature_migration.successful_manifest_changes += len(priority_lines) + len(
985 remaining_install_lines
986 )
987 if warn_about_fixmes_in_dest_dir:
988 feature_migration.warn(
989 "TODO: FIXME left in dest-dir(s) of some installation rules."
990 " Please review these and remove the FIXME (plus correct as necessary)"
991 )
994def migrate_installdocs_file(
995 migration_request: MigrationRequest,
996 feature_migration: FeatureMigration,
997) -> None:
998 feature_migration.tagline = "dh_installdocs config files"
999 manifest = migration_request.manifest
1000 mutable_manifest = assume_not_none(manifest.mutable_manifest)
1001 installations = mutable_manifest.installations(create_if_absent=False)
1003 is_single_binary = migration_request.is_single_binary_package
1005 for dctrl_bin in migration_request.all_packages:
1006 install_file, content = _dh_config_file(
1007 migration_request.debian_dir,
1008 dctrl_bin,
1009 "docs",
1010 "dh_installdocs",
1011 migration_request.acceptable_migration_issues,
1012 feature_migration,
1013 manifest,
1014 support_executable_files=True,
1015 )
1016 if not install_file:
1017 continue
1018 assert content is not None
1019 docs: list[str] = []
1020 for dhe_line in content:
1021 if dhe_line.arch_filter or dhe_line.build_profile_filter: 1021 ↛ 1022line 1021 didn't jump to line 1022 because the condition on line 1021 was never true
1022 _error(
1023 f"Unable to migrate line {dhe_line.line_no} of {install_file.path}."
1024 " Missing support for conditions."
1025 )
1026 docs.extend(
1027 _raise_on_unsupported_path(
1028 _normalize_path(
1029 w, with_prefix=False, allow_and_keep_upward_segments=True
1030 ),
1031 dhe_line.config_file,
1032 dhe_line.line_no,
1033 )
1034 for w in dhe_line.tokens
1035 )
1037 if not docs: 1037 ↛ 1038line 1037 didn't jump to line 1038 because the condition on line 1037 was never true
1038 continue
1039 feature_migration.successful_manifest_changes += 1
1040 install_rule = AbstractMutableYAMLInstallRule.install_docs(
1041 docs if len(docs) > 1 else docs[0],
1042 dctrl_bin.name if not is_single_binary else None,
1043 )
1044 installations.create_definition_if_missing()
1045 installations.append(install_rule)
1048def migrate_installexamples_file(
1049 migration_request: MigrationRequest,
1050 feature_migration: FeatureMigration,
1051) -> None:
1052 feature_migration.tagline = "dh_installexamples config files"
1053 manifest = migration_request.manifest
1054 mutable_manifest = assume_not_none(manifest.mutable_manifest)
1055 installations = mutable_manifest.installations(create_if_absent=False)
1056 is_single_binary = migration_request.is_single_binary_package
1058 for dctrl_bin in manifest.all_packages:
1059 install_file, content = _dh_config_file(
1060 migration_request.debian_dir,
1061 dctrl_bin,
1062 "examples",
1063 "dh_installexamples",
1064 migration_request.acceptable_migration_issues,
1065 feature_migration,
1066 manifest,
1067 support_executable_files=True,
1068 )
1069 if not install_file:
1070 continue
1071 assert content is not None
1072 examples: list[str] = []
1073 for dhe_line in content:
1074 if dhe_line.arch_filter or dhe_line.build_profile_filter: 1074 ↛ 1075line 1074 didn't jump to line 1075 because the condition on line 1074 was never true
1075 _error(
1076 f"Unable to migrate line {dhe_line.line_no} of {install_file.path}."
1077 " Missing support for conditions."
1078 )
1079 examples.extend(
1080 _raise_on_unsupported_path(
1081 _normalize_path(
1082 w, with_prefix=False, allow_and_keep_upward_segments=True
1083 ),
1084 dhe_line.config_file,
1085 dhe_line.line_no,
1086 )
1087 for w in dhe_line.tokens
1088 )
1090 if not examples: 1090 ↛ 1091line 1090 didn't jump to line 1091 because the condition on line 1090 was never true
1091 continue
1092 feature_migration.successful_manifest_changes += 1
1093 install_rule = AbstractMutableYAMLInstallRule.install_examples(
1094 examples if len(examples) > 1 else examples[0],
1095 dctrl_bin.name if not is_single_binary else None,
1096 )
1097 installations.create_definition_if_missing()
1098 installations.append(install_rule)
1101@dataclasses.dataclass(slots=True)
1102class InfoFilesDefinition:
1103 sources: list[str] = dataclasses.field(default_factory=list)
1104 conditional: str | Mapping[str, Any] | None = None
1107def migrate_installinfo_file(
1108 migration_request: MigrationRequest,
1109 feature_migration: FeatureMigration,
1110) -> None:
1111 feature_migration.tagline = "dh_installinfo config files"
1112 manifest = migration_request.manifest
1113 mutable_manifest = assume_not_none(manifest.mutable_manifest)
1114 installations = mutable_manifest.installations(create_if_absent=False)
1115 is_single_binary = migration_request.is_single_binary_package
1117 for dctrl_bin in manifest.all_packages:
1118 info_file, content = _dh_config_file(
1119 migration_request.debian_dir,
1120 dctrl_bin,
1121 "info",
1122 "dh_installinfo",
1123 migration_request.acceptable_migration_issues,
1124 feature_migration,
1125 manifest,
1126 support_executable_files=True,
1127 )
1128 if not info_file:
1129 continue
1130 assert content is not None
1131 info_files_by_condition: dict[tuple[str, ...], InfoFilesDefinition] = {}
1132 for dhe_line in content:
1133 key = dhe_line.conditional_key()
1134 ctr = functools.partial(
1135 InfoFilesDefinition, conditional=dhe_line.conditional()
1136 )
1137 info_def = _fetch_or_create(
1138 info_files_by_condition,
1139 key,
1140 ctr,
1141 )
1142 info_def.sources.extend(
1143 _raise_on_unsupported_path(
1144 _normalize_path(
1145 w, with_prefix=False, allow_and_keep_upward_segments=True
1146 ),
1147 dhe_line.config_file,
1148 dhe_line.line_no,
1149 )
1150 for w in dhe_line.tokens
1151 )
1153 if not info_files_by_condition: 1153 ↛ 1154line 1153 didn't jump to line 1154 because the condition on line 1153 was never true
1154 continue
1155 feature_migration.successful_manifest_changes += 1
1156 installations.create_definition_if_missing()
1157 for info_def in info_files_by_condition.values():
1158 info_files = info_def.sources
1159 install_rule = AbstractMutableYAMLInstallRule.install_docs(
1160 info_files if len(info_files) > 1 else info_files[0],
1161 dctrl_bin.name if not is_single_binary else None,
1162 dest_dir="{{path:GNU_INFO_DIR}}",
1163 when=info_def.conditional,
1164 )
1165 installations.append(install_rule)
1168@dataclasses.dataclass(slots=True)
1169class ManpageDefinition:
1170 sources: list[str] = dataclasses.field(default_factory=list)
1171 language: str | None = None
1172 conditional: str | Mapping[str, Any] | None = None
1175DK = TypeVar("DK")
1176DV = TypeVar("DV")
1179def _fetch_or_create(d: dict[DK, DV], key: DK, factory: Callable[[], DV]) -> DV:
1180 v = d.get(key)
1181 if v is None:
1182 v = factory()
1183 d[key] = v
1184 return v
1187def migrate_installman_file(
1188 migration_request: MigrationRequest,
1189 feature_migration: FeatureMigration,
1190) -> None:
1191 feature_migration.tagline = "dh_installman config files"
1192 manifest = migration_request.manifest
1193 mutable_manifest = assume_not_none(manifest.mutable_manifest)
1194 installations = mutable_manifest.installations(create_if_absent=False)
1195 is_single_binary = migration_request.is_single_binary_package
1196 warn_about_basename = False
1198 for dctrl_bin in migration_request.all_packages:
1199 manpages_file, content = _dh_config_file(
1200 migration_request.debian_dir,
1201 dctrl_bin,
1202 "manpages",
1203 "dh_installman",
1204 migration_request.acceptable_migration_issues,
1205 feature_migration,
1206 manifest,
1207 support_executable_files=True,
1208 allow_dh_exec_rename=True,
1209 )
1210 if not manpages_file:
1211 continue
1212 assert content is not None
1214 vanilla_definitions = []
1215 install_as_rules = []
1216 complex_definitions: dict[
1217 tuple[str | None, tuple[str, ...]], ManpageDefinition
1218 ] = {}
1219 install_rule: AbstractMutableYAMLInstallRule
1220 for dhe_line in content:
1221 if "=>" in dhe_line.tokens: 1221 ↛ 1224line 1221 didn't jump to line 1224 because the condition on line 1221 was never true
1222 # dh-exec allows renaming features. For `debputy`, we degenerate it into an `install` (w. `as`) feature
1223 # without any of the `install-man` features.
1224 if dhe_line.tokens[0] == "=>" and len(dhe_line.tokens) == 2:
1225 _error(
1226 f'Unsupported "=> DEST" rule for error in {manpages_file.path} on line {dhe_line.line_no}."'
1227 f' Cannot migrate dh-exec renames that is not exactly "SOURCE => TARGET" for d/manpages files.'
1228 )
1229 elif len(dhe_line.tokens) != 3:
1230 _error(
1231 f"Validation error in {manpages_file.path} on line {dhe_line.line_no}. Cannot migrate dh-exec"
1232 ' renames that is not exactly "SOURCE => TARGET" or "=> TARGET".'
1233 )
1234 else:
1235 install_rule = AbstractMutableYAMLInstallRule.install_doc_as(
1236 _raise_on_unsupported_path(
1237 _normalize_path(
1238 dhe_line.tokens[0],
1239 with_prefix=False,
1240 allow_and_keep_upward_segments=True,
1241 ),
1242 dhe_line.config_file,
1243 dhe_line.line_no,
1244 ),
1245 _raise_on_unsupported_path(
1246 _normalize_path(
1247 dhe_line.tokens[2],
1248 with_prefix=False,
1249 allow_and_keep_upward_segments=True,
1250 ),
1251 dhe_line.config_file,
1252 dhe_line.line_no,
1253 ),
1254 dctrl_bin.name if not is_single_binary else None,
1255 when=dhe_line.conditional(),
1256 )
1257 install_as_rules.append(install_rule)
1258 continue
1260 sources = [
1261 _raise_on_unsupported_path(
1262 _normalize_path(
1263 w, with_prefix=False, allow_and_keep_upward_segments=True
1264 ),
1265 dhe_line.config_file,
1266 dhe_line.line_no,
1267 )
1268 for w in dhe_line.tokens
1269 ]
1270 needs_basename = any(
1271 MAN_GUESS_FROM_BASENAME.search(x)
1272 and not MAN_GUESS_LANG_FROM_PATH.search(x)
1273 for x in sources
1274 )
1275 if needs_basename or dhe_line.conditional() is not None:
1276 if needs_basename: 1276 ↛ 1280line 1276 didn't jump to line 1280 because the condition on line 1276 was always true
1277 warn_about_basename = True
1278 language = "derive-from-basename"
1279 else:
1280 language = None
1281 key = (language, dhe_line.conditional_key())
1282 ctor = functools.partial(
1283 ManpageDefinition,
1284 language=language,
1285 conditional=dhe_line.conditional(),
1286 )
1287 manpage_def = _fetch_or_create(
1288 complex_definitions,
1289 key,
1290 ctor,
1291 )
1292 manpage_def.sources.extend(sources)
1293 else:
1294 vanilla_definitions.extend(sources)
1296 if not install_as_rules and not vanilla_definitions and not complex_definitions: 1296 ↛ 1297line 1296 didn't jump to line 1297 because the condition on line 1296 was never true
1297 continue
1298 feature_migration.successful_manifest_changes += 1
1299 installations.create_definition_if_missing()
1300 installations.extend(install_as_rules)
1301 if vanilla_definitions: 1301 ↛ 1313line 1301 didn't jump to line 1313 because the condition on line 1301 was always true
1302 man_source = (
1303 vanilla_definitions
1304 if len(vanilla_definitions) > 1
1305 else vanilla_definitions[0]
1306 )
1307 install_rule = AbstractMutableYAMLInstallRule.install_man(
1308 man_source,
1309 dctrl_bin.name if not is_single_binary else None,
1310 None,
1311 )
1312 installations.append(install_rule)
1313 for manpage_def in complex_definitions.values():
1314 sources = manpage_def.sources
1315 install_rule = AbstractMutableYAMLInstallRule.install_man(
1316 sources if len(sources) > 1 else sources[0],
1317 dctrl_bin.name if not is_single_binary else None,
1318 manpage_def.language,
1319 when=manpage_def.conditional,
1320 )
1321 installations.append(install_rule)
1323 if warn_about_basename:
1324 feature_migration.warn(
1325 'Detected man pages that might rely on "derive-from-basename" logic. Please double check'
1326 " that the generated `install-man` rules are correct"
1327 )
1330def migrate_not_installed_file(
1331 migration_request: MigrationRequest,
1332 feature_migration: FeatureMigration,
1333) -> None:
1334 feature_migration.tagline = "dh_missing's not-installed config file"
1335 manifest = migration_request.manifest
1336 mutable_manifest = assume_not_none(manifest.mutable_manifest)
1337 installations = mutable_manifest.installations(create_if_absent=False)
1338 main_binary = migration_request.main_binary
1340 missing_file, content = _dh_config_file(
1341 migration_request.debian_dir,
1342 main_binary,
1343 "not-installed",
1344 "dh_missing",
1345 migration_request.acceptable_migration_issues,
1346 feature_migration,
1347 manifest,
1348 support_executable_files=False,
1349 pkgfile_lookup=False,
1350 )
1351 discard_rules: list[str] = []
1352 if missing_file:
1353 assert content is not None
1354 for dhe_line in content:
1355 discard_rules.extend(
1356 _raise_on_unsupported_path(
1357 _normalize_path(
1358 w, with_prefix=False, allow_and_keep_upward_segments=True
1359 ),
1360 dhe_line.config_file,
1361 dhe_line.line_no,
1362 )
1363 for w in dhe_line.tokens
1364 )
1366 if discard_rules:
1367 feature_migration.successful_manifest_changes += 1
1368 install_rule = AbstractMutableYAMLInstallRule.discard(
1369 discard_rules if len(discard_rules) > 1 else discard_rules[0],
1370 )
1371 installations.create_definition_if_missing()
1372 installations.append(install_rule)
1375def min_dh_compat_check(
1376 migration_request: MigrationRequest,
1377 feature_migration: FeatureMigration,
1378) -> None:
1379 feature_migration.tagline = "min dh compat level check"
1380 # We start on compat 12 for arch:any due to the new dh_makeshlibs and dh_installinit default
1381 # For arch:any, the min is compat 14 due to `dh_dwz` being removed.
1382 min_compat = (
1383 14 if any(not p.is_arch_all for p in migration_request.all_packages) else 12
1384 )
1385 feature_migration.assumed_compat = min_compat
1388def detect_pam_files(
1389 migration_request: MigrationRequest,
1390 feature_migration: FeatureMigration,
1391) -> None:
1392 feature_migration.tagline = "detect dh_installpam files (min dh compat)"
1393 for dctrl_bin in migration_request.all_packages:
1394 dh_config_file = dhe_pkgfile(migration_request.debian_dir, dctrl_bin, "pam")
1395 if dh_config_file is not None:
1396 feature_migration.assumed_compat = 14
1397 break
1400def migrate_tmpfile(
1401 migration_request: MigrationRequest,
1402 feature_migration: FeatureMigration,
1403) -> None:
1404 feature_migration.tagline = "dh_installtmpfiles config files"
1405 debian_dir = migration_request.debian_dir
1406 for dctrl_bin in migration_request.all_packages:
1407 dh_config_file = dhe_pkgfile(debian_dir, dctrl_bin, "tmpfile")
1408 if dh_config_file is not None:
1409 target = (
1410 dh_config_file.name.replace(".tmpfile", ".tmpfiles")
1411 if "." in dh_config_file.name
1412 else "tmpfiles"
1413 )
1414 _rename_file_if_exists(
1415 debian_dir,
1416 dh_config_file.name,
1417 target,
1418 feature_migration,
1419 )
1422def migrate_lintian_overrides_files(
1423 migration_request: MigrationRequest,
1424 feature_migration: FeatureMigration,
1425) -> None:
1426 feature_migration.tagline = "dh_lintian config files"
1427 for dctrl_bin in migration_request.all_packages:
1428 # We do not support executable lintian-overrides and `_dh_config_file` handles all of that.
1429 # Therefore, the return value is irrelevant to us.
1430 _dh_config_file(
1431 migration_request.debian_dir,
1432 dctrl_bin,
1433 "lintian-overrides",
1434 "dh_lintian",
1435 migration_request.acceptable_migration_issues,
1436 feature_migration,
1437 migration_request.manifest,
1438 support_executable_files=False,
1439 remove_on_migration=False,
1440 )
1443def migrate_links_files(
1444 migration_request: MigrationRequest,
1445 feature_migration: FeatureMigration,
1446) -> None:
1447 feature_migration.tagline = "dh_link files"
1448 manifest = migration_request.manifest
1449 mutable_manifest = assume_not_none(manifest.mutable_manifest)
1450 for dctrl_bin in migration_request.all_packages:
1451 links_file, content = _dh_config_file(
1452 migration_request.debian_dir,
1453 dctrl_bin,
1454 "links",
1455 "dh_link",
1456 migration_request.acceptable_migration_issues,
1457 feature_migration,
1458 manifest,
1459 support_executable_files=True,
1460 )
1462 if links_file is None:
1463 continue
1464 assert content is not None
1466 package_definition = mutable_manifest.package(dctrl_bin.name)
1467 defined_symlink = {
1468 symlink.symlink_path: symlink.symlink_target
1469 for symlink in package_definition.symlinks()
1470 }
1472 seen_symlinks: set[str] = set()
1474 for dhe_line in content:
1475 if len(dhe_line.tokens) != 2: 1475 ↛ 1476line 1475 didn't jump to line 1476 because the condition on line 1475 was never true
1476 raise UnsupportedFeature(
1477 f"The dh_link file {links_file.fs_path} did not have exactly two paths on line"
1478 f' {dhe_line.line_no} (line: "{dhe_line.original_line}"'
1479 )
1480 target, source = dhe_line.tokens
1481 source = _raise_on_unsupported_path(
1482 source,
1483 dhe_line.config_file,
1484 dhe_line.line_no,
1485 )
1486 target = _raise_on_unsupported_path(
1487 target,
1488 dhe_line.config_file,
1489 dhe_line.line_no,
1490 )
1491 if source in seen_symlinks: 1491 ↛ 1493line 1491 didn't jump to line 1493 because the condition on line 1491 was never true
1492 # According to #934499, this has happened in the wild already
1493 raise ConflictingChange(
1494 f"The {links_file.fs_path} file defines the link path {source} twice! Please ensure"
1495 " that it is defined at most once in that file"
1496 )
1497 seen_symlinks.add(source)
1498 # Symlinks in .links are always considered absolute, but you were not required to have a leading slash.
1499 # However, in the debputy manifest, you can have relative links, so we should ensure it is explicitly
1500 # absolute.
1501 if not target.startswith("/"): 1501 ↛ 1503line 1501 didn't jump to line 1503 because the condition on line 1501 was always true
1502 target = "/" + target
1503 existing_target = defined_symlink.get(source)
1504 if existing_target is not None: 1504 ↛ 1505line 1504 didn't jump to line 1505 because the condition on line 1504 was never true
1505 if existing_target != target:
1506 raise ConflictingChange(
1507 f'The symlink "{source}" points to "{target}" in {links_file}, but there is'
1508 f' another symlink with same path pointing to "{existing_target}" defined'
1509 " already (in the existing manifest or an migration e.g., inside"
1510 f" {links_file.fs_path})"
1511 )
1512 continue
1513 condition = dhe_line.conditional()
1514 package_definition.add_symlink(
1515 MutableYAMLSymlink.new_symlink(
1516 source,
1517 target,
1518 condition,
1519 )
1520 )
1521 feature_migration.successful_manifest_changes += 1
1524def migrate_misspelled_readme_debian_files(
1525 migration_request: MigrationRequest,
1526 feature_migration: FeatureMigration,
1527) -> None:
1528 feature_migration.tagline = "misspelled README.Debian files"
1529 debian_dir = migration_request.debian_dir
1530 for dctrl_bin in migration_request.all_packages:
1531 readme, _ = _dh_config_file(
1532 debian_dir,
1533 dctrl_bin,
1534 "README.debian",
1535 "dh_installdocs",
1536 migration_request.acceptable_migration_issues,
1537 feature_migration,
1538 migration_request.manifest,
1539 support_executable_files=False,
1540 remove_on_migration=False,
1541 )
1542 if readme is None:
1543 continue
1544 new_name = readme.name.replace("README.debian", "README.Debian")
1545 assert readme.name != new_name
1546 _rename_file_if_exists(
1547 debian_dir,
1548 readme.name,
1549 new_name,
1550 feature_migration,
1551 )
1554def migrate_doc_base_files(
1555 migration_request: MigrationRequest,
1556 feature_migration: FeatureMigration,
1557) -> None:
1558 feature_migration.tagline = "doc-base files"
1559 debian_dir = migration_request.debian_dir
1560 # ignore the dh_make ".EX" file if one should still be present. The dh_installdocs tool ignores it too.
1561 possible_effected_doc_base_files = [
1562 f
1563 for f in debian_dir.iterdir
1564 if (
1565 (".doc-base." in f.name or f.name.startswith("doc-base."))
1566 and not f.name.endswith("doc-base.EX")
1567 )
1568 ]
1569 known_packages = {d.name: d for d in migration_request.all_packages}
1570 main_package = migration_request.main_binary
1571 for doc_base_file in possible_effected_doc_base_files:
1572 parts = doc_base_file.name.split(".")
1573 owning_package = known_packages.get(parts[0])
1574 if owning_package is None: 1574 ↛ 1575line 1574 didn't jump to line 1575 because the condition on line 1574 was never true
1575 owning_package = main_package
1576 package_part = None
1577 else:
1578 package_part = parts[0]
1579 parts = parts[1:]
1581 if not parts or parts[0] != "doc-base": 1581 ↛ 1583line 1581 didn't jump to line 1583 because the condition on line 1581 was never true
1582 # Not a doc-base file after all
1583 continue
1585 if len(parts) > 1: 1585 ↛ 1592line 1585 didn't jump to line 1592 because the condition on line 1585 was always true
1586 name_part = ".".join(parts[1:])
1587 if package_part is None: 1587 ↛ 1589line 1587 didn't jump to line 1589 because the condition on line 1587 was never true
1588 # Named files must have a package prefix
1589 package_part = owning_package.name
1590 else:
1591 # No rename needed
1592 continue
1594 new_basename = ".".join(filter(None, (package_part, name_part, "doc-base")))
1595 _rename_file_if_exists(
1596 debian_dir,
1597 doc_base_file.name,
1598 new_basename,
1599 feature_migration,
1600 )
1603def migrate_dh_hook_targets(
1604 migration_request: MigrationRequest,
1605 feature_migration: FeatureMigration,
1606) -> None:
1607 feature_migration.tagline = "dh hook targets"
1608 source_root = os.path.dirname(migration_request.debian_dir.fs_path)
1609 if source_root == "":
1610 source_root = "."
1611 detected_hook_targets = json.loads(
1612 subprocess.check_output(
1613 ["dh_assistant", "detect-hook-targets"],
1614 cwd=source_root,
1615 ).decode("utf-8")
1616 )
1617 sample_hook_target: str | None = None
1618 debputy_integration_mode = migration_request.migration_target
1619 assert debputy_integration_mode is not None
1620 replaced_commands = DH_COMMANDS_REPLACED[debputy_integration_mode]
1622 for hook_target_def in detected_hook_targets["hook-targets"]:
1623 if hook_target_def["is-empty"]:
1624 continue
1625 command = hook_target_def["command"]
1626 if command not in replaced_commands:
1627 continue
1628 hook_target = hook_target_def["target-name"]
1629 advice = MIGRATION_AID_FOR_OVERRIDDEN_COMMANDS.get(command)
1630 if advice is None:
1631 if sample_hook_target is None:
1632 sample_hook_target = hook_target
1633 feature_migration.warn(
1634 f"TODO: MANUAL MIGRATION required for hook target {hook_target}"
1635 )
1636 else:
1637 feature_migration.warn(
1638 f"TODO: MANUAL MIGRATION required for hook target {hook_target}. Please see {advice}"
1639 f" for migration advice."
1640 )
1641 if (
1642 feature_migration.warnings
1643 and "dh-hook-targets" not in migration_request.acceptable_migration_issues
1644 and sample_hook_target is not None
1645 ):
1646 raise UnsupportedFeature(
1647 f"The debian/rules file contains one or more non empty dh hook targets that will not"
1648 f" be run with the requested debputy dh sequence with no known migration advice. One of these would be"
1649 f" {sample_hook_target}.",
1650 ["dh-hook-targets"],
1651 )
1654def detect_unsupported_zz_debputy_features(
1655 migration_request: MigrationRequest,
1656 feature_migration: FeatureMigration,
1657) -> None:
1658 feature_migration.tagline = "Known unsupported features"
1660 for unsupported_config in UNSUPPORTED_DH_CONFIGS_AND_TOOLS_FOR_ZZ_DEBPUTY:
1661 _unsupported_debhelper_config_file(
1662 migration_request,
1663 unsupported_config,
1664 feature_migration,
1665 )
1668def detect_obsolete_substvars(
1669 migration_request: MigrationRequest,
1670 feature_migration: FeatureMigration,
1671) -> None:
1672 feature_migration.tagline = (
1673 "Check for obsolete ${foo:var} variables in debian/control"
1674 )
1675 ctrl_file = migration_request.debian_dir.get("control")
1676 if not ctrl_file: 1676 ↛ 1677line 1676 didn't jump to line 1677 because the condition on line 1676 was never true
1677 feature_migration.warn(
1678 "Cannot find debian/control. Detection of obsolete substvars could not be performed."
1679 )
1680 return
1681 with ctrl_file.open() as fd:
1682 ctrl = list(Deb822.iter_paragraphs(fd))
1684 relationship_fields = dpkg_field_list_pkg_dep()
1685 relationship_fields_lc = frozenset(x.lower() for x in relationship_fields)
1687 for p in ctrl[1:]:
1688 seen_obsolete_relationship_substvars = set()
1689 obsolete_fields = set()
1690 is_essential = p.get("Essential") == "yes"
1691 for df in relationship_fields:
1692 field: str | None = p.get(df)
1693 if field is None:
1694 continue
1695 df_lc = df.lower()
1696 number_of_relations = 0
1697 obsolete_substvars_in_field = set()
1698 for d in (d.strip() for d in field.strip().split(",")):
1699 if not d:
1700 continue
1701 number_of_relations += 1
1702 if not d.startswith("${"):
1703 continue
1704 try:
1705 end_idx = d.index("}")
1706 except ValueError:
1707 continue
1708 substvar_name = d[2:end_idx]
1709 if ":" not in substvar_name: 1709 ↛ 1710line 1709 didn't jump to line 1710 because the condition on line 1709 was never true
1710 continue
1711 _, field = substvar_name.rsplit(":", 1)
1712 field_lc = field.lower()
1713 if field_lc not in relationship_fields_lc: 1713 ↛ 1714line 1713 didn't jump to line 1714 because the condition on line 1713 was never true
1714 continue
1715 is_obsolete = field_lc == df_lc
1716 if (
1717 not is_obsolete
1718 and is_essential
1719 and substvar_name.lower() == "shlibs:depends"
1720 and df_lc == "pre-depends"
1721 ):
1722 is_obsolete = True
1724 if is_obsolete:
1725 obsolete_substvars_in_field.add(d)
1727 if number_of_relations == len(obsolete_substvars_in_field):
1728 obsolete_fields.add(df)
1729 else:
1730 seen_obsolete_relationship_substvars.update(obsolete_substvars_in_field)
1732 package = p.get("Package", "(Missing package name!?)")
1733 fo = feature_migration.fo
1734 if obsolete_fields:
1735 fields = ", ".join(obsolete_fields)
1736 feature_migration.warn(
1737 f"The following relationship fields can be removed from {package}: {fields}."
1738 f" (The content in them would be applied automatically. Note: {fo.bts('1067653')})"
1739 )
1740 if seen_obsolete_relationship_substvars:
1741 v = ", ".join(sorted(seen_obsolete_relationship_substvars))
1742 feature_migration.warn(
1743 f"The following relationship substitution variables can be removed from {package}: {v}"
1744 f" (Note: {fo.bts('1067653')})"
1745 )
1748def detect_dh_addons_zz_debputy_rrr(
1749 migration_request: MigrationRequest,
1750 feature_migration: FeatureMigration,
1751) -> None:
1752 feature_migration.tagline = "Check for dh-sequence-addons"
1753 r = read_dh_addon_sequences(migration_request.debian_dir)
1754 if r is None:
1755 feature_migration.warn(
1756 "Cannot find debian/control. Detection of unsupported/missing dh-sequence addon"
1757 " could not be performed. Please ensure the package will Build-Depend on dh-sequence-zz-debputy-rrr."
1758 )
1759 return
1761 bd_sequences, dr_sequences, _ = r
1763 remaining_sequences = bd_sequences | dr_sequences
1764 saw_dh_debputy = "zz-debputy-rrr" in remaining_sequences
1766 if not saw_dh_debputy:
1767 feature_migration.warn("Missing Build-Depends on dh-sequence-zz-debputy-rrr")
1770def detect_dh_addons_with_full_integration(
1771 _migration_request: MigrationRequest,
1772 feature_migration: FeatureMigration,
1773) -> None:
1774 feature_migration.tagline = "Check for dh-sequence-addons and Build-Depends"
1775 feature_migration.warn(
1776 "TODO: Not implemented: Please remove any dh-sequence Build-Dependency"
1777 )
1778 feature_migration.warn(
1779 "TODO: Not implemented: Please ensure there is a Build-Dependency on `debputy (>= 0.1.45~)"
1780 )
1781 feature_migration.warn(
1782 "TODO: Not implemented: Please ensure there is a Build-Dependency on `dpkg-dev (>= 1.22.7~)"
1783 )
1786def detect_dh_addons_with_zz_integration(
1787 migration_request: MigrationRequest,
1788 feature_migration: FeatureMigration,
1789) -> None:
1790 feature_migration.tagline = "Check for dh-sequence-addons"
1791 r = read_dh_addon_sequences(migration_request.debian_dir)
1792 acceptable_migration_issues = migration_request.acceptable_migration_issues
1793 if r is None:
1794 feature_migration.warn(
1795 "Cannot find debian/control. Detection of unsupported/missing dh-sequence addon"
1796 " could not be performed. Please ensure the package will Build-Depend on dh-sequence-zz-debputy"
1797 " and not rely on any other debhelper sequence addons except those debputy explicitly supports."
1798 )
1799 return
1801 assert migration_request.migration_target != INTEGRATION_MODE_FULL
1803 bd_sequences, dr_sequences, _ = r
1805 remaining_sequences = bd_sequences | dr_sequences
1806 saw_dh_debputy = (
1807 "debputy" in remaining_sequences or "zz-debputy" in remaining_sequences
1808 )
1809 saw_zz_debputy = "zz-debputy" in remaining_sequences
1810 must_use_zz_debputy = False
1811 remaining_sequences -= SUPPORTED_DH_ADDONS_WITH_ZZ_DEBPUTY
1812 for sequence in remaining_sequences & DH_ADDONS_TO_PLUGINS.keys():
1813 migration = DH_ADDONS_TO_PLUGINS[sequence]
1814 feature_migration.require_plugin(migration.debputy_plugin)
1815 if migration.remove_dh_sequence: 1815 ↛ 1816line 1815 didn't jump to line 1816 because the condition on line 1815 was never true
1816 if migration.must_use_zz_debputy:
1817 must_use_zz_debputy = True
1818 if sequence in bd_sequences:
1819 feature_migration.warn(
1820 f"TODO: MANUAL MIGRATION - Remove build-dependency on dh-sequence-{sequence}"
1821 f" (replaced by debputy-plugin-{migration.debputy_plugin})"
1822 )
1823 else:
1824 feature_migration.warn(
1825 f"TODO: MANUAL MIGRATION - Remove --with {sequence} from dh in d/rules"
1826 f" (replaced by debputy-plugin-{migration.debputy_plugin})"
1827 )
1829 remaining_sequences -= DH_ADDONS_TO_PLUGINS.keys()
1831 alt_key = "unsupported-dh-sequences"
1832 for sequence in remaining_sequences & DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY: 1832 ↛ 1833line 1832 didn't jump to line 1833 because the loop on line 1832 never started
1833 if sequence in bd_sequences:
1834 feature_migration.warn(
1835 f"TODO: MANUAL MIGRATION - Remove build dependency on dh-sequence-{sequence}"
1836 )
1837 else:
1838 feature_migration.warn(
1839 f"TODO: MANUAL MIGRATION - Remove --with {sequence} from dh in d/rules"
1840 )
1842 remaining_sequences -= DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY
1844 for sequence in remaining_sequences:
1845 key = f"unsupported-dh-sequence-{sequence}"
1846 msg = f'The dh addon "{sequence}" is not known to work with dh-debputy and might malfunction'
1847 if (
1848 key not in acceptable_migration_issues
1849 and alt_key not in acceptable_migration_issues
1850 ):
1851 raise UnsupportedFeature(msg, [key, alt_key])
1852 feature_migration.warn(msg)
1854 if not saw_dh_debputy:
1855 feature_migration.warn("Missing Build-Depends on dh-sequence-zz-debputy")
1856 elif must_use_zz_debputy and not saw_zz_debputy: 1856 ↛ 1857line 1856 didn't jump to line 1857 because the condition on line 1856 was never true
1857 feature_migration.warn(
1858 "Please use the zz-debputy sequence rather than the debputy (needed due to dh add-on load order)"
1859 )
1862def _rename_file_if_exists(
1863 debian_dir: VirtualPath,
1864 source: str,
1865 dest: str,
1866 feature_migration: FeatureMigration,
1867) -> None:
1868 source_path = debian_dir.get(source)
1869 dest_path = debian_dir.get(dest)
1870 spath = (
1871 source_path.path
1872 if source_path is not None
1873 else os.path.join(debian_dir.path, source)
1874 )
1875 dpath = (
1876 dest_path.path if dest_path is not None else os.path.join(debian_dir.path, dest)
1877 )
1878 if source_path is not None and source_path.is_file:
1879 if dest_path is not None:
1880 if not dest_path.is_file:
1881 feature_migration.warnings.append(
1882 f'TODO: MANUAL MIGRATION - there is a "{spath}" (file) and "{dpath}" (not a file).'
1883 f' The migration wanted to replace "{spath}" with "{dpath}", but since "{dpath}" is not'
1884 " a file, this step is left as a manual migration."
1885 )
1886 return
1887 if (
1888 subprocess.call(["cmp", "-s", source_path.fs_path, dest_path.fs_path])
1889 != 0
1890 ):
1891 feature_migration.warnings.append(
1892 f'TODO: MANUAL MIGRATION - there is a "{source_path.path}" and "{dest_path.path}"'
1893 f" file. Normally these files are for the same package and there would only be one of"
1894 f" them. In this case, they both exist but their content differs. Be advised that"
1895 f' debputy tool will use the "{dest_path.path}".'
1896 )
1897 else:
1898 feature_migration.remove_on_success(dest_path.fs_path)
1899 else:
1900 feature_migration.rename_on_success(
1901 source_path.fs_path,
1902 os.path.join(debian_dir.fs_path, dest),
1903 )
1904 elif source_path is not None: 1904 ↛ exitline 1904 didn't return from function '_rename_file_if_exists' because the condition on line 1904 was always true
1905 feature_migration.warnings.append(
1906 f'TODO: MANUAL MIGRATION - The migration would normally have renamed "{spath}" to "{dpath}".'
1907 f' However, the migration assumed "{spath}" would be a file and it is not. Therefore, this step'
1908 " as a manual migration."
1909 )
1912def _find_dh_config_file_for_any_pkg(
1913 migration_request: MigrationRequest,
1914 unsupported_config: UnsupportedDHConfig,
1915) -> Iterable[VirtualPath]:
1916 for dctrl_bin in migration_request.all_packages:
1917 dh_config_file = dhe_pkgfile(
1918 migration_request.debian_dir,
1919 dctrl_bin,
1920 unsupported_config.dh_config_basename,
1921 bug_950723_prefix_matching=unsupported_config.bug_950723_prefix_matching,
1922 )
1923 if dh_config_file is not None:
1924 yield dh_config_file
1927def _unsupported_debhelper_config_file(
1928 migration_request: MigrationRequest,
1929 unsupported_config: UnsupportedDHConfig,
1930 feature_migration: FeatureMigration,
1931) -> None:
1932 dh_config_files = list(
1933 _find_dh_config_file_for_any_pkg(migration_request, unsupported_config)
1934 )
1935 if not dh_config_files:
1936 return
1937 dh_tool = unsupported_config.dh_tool
1938 basename = unsupported_config.dh_config_basename
1939 file_stem = (
1940 f"@{basename}" if unsupported_config.bug_950723_prefix_matching else basename
1941 )
1942 dh_config_file = dh_config_files[0]
1943 if unsupported_config.is_missing_migration:
1944 feature_migration.warn(
1945 f'Missing migration support for the "{dh_config_file.path}" debhelper config file'
1946 f" (used by {dh_tool}). Manual migration may be feasible depending on the exact features"
1947 " required."
1948 )
1949 return
1950 primary_key = f"unsupported-dh-config-file-{file_stem}"
1951 secondary_key = "any-unsupported-dh-config-file"
1952 if (
1953 primary_key not in migration_request.acceptable_migration_issues
1954 and secondary_key not in migration_request.acceptable_migration_issues
1955 ):
1956 msg = (
1957 f'The "{dh_config_file.path}" debhelper config file (used by {dh_tool} is currently not'
1958 " supported by debputy."
1959 )
1960 raise UnsupportedFeature(
1961 msg,
1962 [primary_key, secondary_key],
1963 )
1964 for dh_config_file in dh_config_files:
1965 feature_migration.warn(
1966 f'TODO: MANUAL MIGRATION - Use of unsupported "{dh_config_file.path}" file (used by {dh_tool})'
1967 )