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