Coverage for src/debputy/dh_migration/migrators_impl.py: 80%

775 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-09-06 13:40 +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) 

14 

15from debian.deb822 import Deb822 

16 

17from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable 

18from debputy.deb_packaging_support import dpkg_field_list_pkg_dep 

19from debputy.dh.debhelper_emulation import ( 

20 dhe_filedoublearray, 

21 DHConfigFileLine, 

22 dhe_pkgfile, 

23) 

24from debputy.dh.dh_assistant import ( 

25 read_dh_addon_sequences, 

26) 

27from debputy.dh_migration.models import ( 

28 ConflictingChange, 

29 FeatureMigration, 

30 UnsupportedFeature, 

31 AcceptableMigrationIssues, 

32 DHMigrationSubstitution, 

33 MigrationRequest, 

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.maintscript_snippet import SUPPORTED_UDEB_SCRIPTS, DPKG_DEB_CONTROL_SCRIPTS 

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) 

59from debputy.version import debputy_doc_root_dir 

60 

61 

62class ContainsEverything: 

63 

64 def __contains__(self, item: str) -> bool: 

65 return True 

66 

67 

68# Align with debputy.py 

69DH_COMMANDS_REPLACED: Mapping[DebputyIntegrationMode, Container[str]] = { 

70 INTEGRATION_MODE_DH_DEBPUTY_RRR: frozenset( 

71 { 

72 "dh_fixperms", 

73 "dh_shlibdeps", 

74 "dh_gencontrol", 

75 "dh_md5sums", 

76 "dh_builddeb", 

77 } 

78 ), 

79 INTEGRATION_MODE_DH_DEBPUTY: frozenset( 

80 { 

81 "dh_install", 

82 "dh_installdocs", 

83 "dh_installchangelogs", 

84 "dh_installexamples", 

85 "dh_installman", 

86 "dh_installcatalogs", 

87 "dh_installcron", 

88 "dh_installdebconf", 

89 "dh_installemacsen", 

90 "dh_installifupdown", 

91 "dh_installinfo", 

92 "dh_installinit", 

93 "dh_installsysusers", 

94 "dh_installtmpfiles", 

95 "dh_installsystemd", 

96 "dh_installsystemduser", 

97 "dh_installmenu", 

98 "dh_installmime", 

99 "dh_installmodules", 

100 "dh_installlogcheck", 

101 "dh_installlogrotate", 

102 "dh_installpam", 

103 "dh_installppp", 

104 "dh_installudev", 

105 "dh_installgsettings", 

106 "dh_installinitramfs", 

107 "dh_installalternatives", 

108 "dh_bugfiles", 

109 "dh_ucf", 

110 "dh_lintian", 

111 "dh_icons", 

112 "dh_usrlocal", 

113 "dh_perl", 

114 "dh_link", 

115 "dh_installwm", 

116 "dh_installxfonts", 

117 "dh_strip_nondeterminism", 

118 "dh_compress", 

119 "dh_fixperms", 

120 "dh_dwz", 

121 "dh_strip", 

122 "dh_makeshlibs", 

123 "dh_shlibdeps", 

124 "dh_missing", 

125 "dh_installdeb", 

126 "dh_computeautosubstvars", 

127 "dh_gencontrol", 

128 "dh_md5sums", 

129 "dh_builddeb", 

130 } 

131 ), 

132 INTEGRATION_MODE_FULL: ContainsEverything(), 

133} 

134 

135_GS_DOC = f"{debputy_doc_root_dir()}/GETTING-STARTED-WITH-dh-debputy.md" 

136MIGRATION_AID_FOR_OVERRIDDEN_COMMANDS = { 

137 "dh_installinit": f"{_GS_DOC}#covert-your-overrides-for-dh_installsystemd-dh_installinit-if-any", 

138 "dh_installsystemd": f"{_GS_DOC}#covert-your-overrides-for-dh_installsystemd-dh_installinit-if-any", 

139 "dh_fixperms": f"{_GS_DOC}#convert-your-overrides-or-excludes-for-dh_fixperms-if-any", 

140 "dh_gencontrol": f"{_GS_DOC}#convert-your-overrides-for-dh_gencontrol-if-any", 

141} 

142 

143 

144@dataclasses.dataclass(frozen=True, slots=True) 

145class UnsupportedDHConfig: 

146 dh_config_basename: str 

147 dh_tool: str 

148 bug_950723_prefix_matching: bool = False 

149 is_missing_migration: bool = False 

150 

151 

152@dataclasses.dataclass(frozen=True, slots=True) 

153class DHSequenceMigration: 

154 debputy_plugin: str 

155 remove_dh_sequence: bool = True 

156 must_use_zz_debputy: bool = False 

157 

158 

159UNSUPPORTED_DH_CONFIGS_AND_TOOLS_FOR_ZZ_DEBPUTY = [ 

160 UnsupportedDHConfig("config", "dh_installdebconf"), 

161 UnsupportedDHConfig("templates", "dh_installdebconf"), 

162 UnsupportedDHConfig("emacsen-compat", "dh_installemacsen"), 

163 UnsupportedDHConfig("emacsen-install", "dh_installemacsen"), 

164 UnsupportedDHConfig("emacsen-remove", "dh_installemacsen"), 

165 UnsupportedDHConfig("emacsen-startup", "dh_installemacsen"), 

166 # The `upstart` file should be long dead, but we might as well detect it. 

167 UnsupportedDHConfig("upstart", "dh_installinit"), 

168 # dh_installsystemduser 

169 UnsupportedDHConfig( 

170 "user.path", "dh_installsystemduser", bug_950723_prefix_matching=False 

171 ), 

172 UnsupportedDHConfig( 

173 "user.path", "dh_installsystemduser", bug_950723_prefix_matching=True 

174 ), 

175 UnsupportedDHConfig( 

176 "user.service", "dh_installsystemduser", bug_950723_prefix_matching=False 

177 ), 

178 UnsupportedDHConfig( 

179 "user.service", "dh_installsystemduser", bug_950723_prefix_matching=True 

180 ), 

181 UnsupportedDHConfig( 

182 "user.socket", "dh_installsystemduser", bug_950723_prefix_matching=False 

183 ), 

184 UnsupportedDHConfig( 

185 "user.socket", "dh_installsystemduser", bug_950723_prefix_matching=True 

186 ), 

187 UnsupportedDHConfig( 

188 "user.target", "dh_installsystemduser", bug_950723_prefix_matching=False 

189 ), 

190 UnsupportedDHConfig( 

191 "user.target", "dh_installsystemduser", bug_950723_prefix_matching=True 

192 ), 

193 UnsupportedDHConfig( 

194 "user.timer", "dh_installsystemduser", bug_950723_prefix_matching=False 

195 ), 

196 UnsupportedDHConfig( 

197 "user.timer", "dh_installsystemduser", bug_950723_prefix_matching=True 

198 ), 

199 UnsupportedDHConfig("menu", "dh_installmenu"), 

200 UnsupportedDHConfig("menu-method", "dh_installmenu"), 

201 UnsupportedDHConfig("ucf", "dh_ucf"), 

202 UnsupportedDHConfig("wm", "dh_installwm"), 

203 UnsupportedDHConfig("triggers", "dh_installdeb"), 

204 UnsupportedDHConfig("menutest", "dh_installdeb"), 

205 UnsupportedDHConfig("isinstallable", "dh_installdeb"), 

206] 

207SUPPORTED_DH_ADDONS_WITH_ZZ_DEBPUTY = frozenset( 

208 { 

209 # debputy's own 

210 "debputy", 

211 "zz-debputy", 

212 # debhelper provided sequences that should work. 

213 "single-binary", 

214 } 

215) 

216DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY = frozenset( 

217 [ 

218 # The `zz-debputy` add-on replaces the `zz-debputy-rrr` plugin. 

219 "zz-debputy-rrr", 

220 # Sequences debputy directly replaces 

221 "dwz", 

222 "elf-tools", 

223 "installinitramfs", 

224 "installsysusers", 

225 "doxygen", 

226 # Sequences that are embedded fully into debputy 

227 "bash-completion", 

228 "shell-completions", 

229 "sodeps", 

230 "builtusing", 

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} 

258 

259 

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 ) 

295 

296 if remove_on_migration: 

297 feature_migration.remove_on_success(dh_config_file.fs_path) 

298 substitution = DHMigrationSubstitution( 

299 DpkgArchitectureBuildProcessValuesTable(), 

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 

310 

311 

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 ) 

334 

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 

365 

366 

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) 

376 

377 

378def migrate_bash_completion( 

379 migration_request: MigrationRequest, 

380 feature_migration: FeatureMigration, 

381) -> None: 

382 feature_migration.tagline = "dh_bash-completion files" 

383 is_single_binary = migration_request.is_single_binary_package 

384 manifest = migration_request.manifest 

385 debian_dir = migration_request.debian_dir 

386 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

387 installations = mutable_manifest.installations(create_if_absent=False) 

388 

389 for dctrl_bin in migration_request.all_packages: 

390 dh_file = dhe_pkgfile(debian_dir, dctrl_bin, "bash-completion") 

391 if dh_file is None: 

392 continue 

393 is_bash_completion_file = False 

394 with dh_file.open() as fd: 

395 for line in fd: 

396 line = line.strip() 

397 if not line or line[0] == "#": 397 ↛ 398line 397 didn't jump to line 398 because the condition on line 397 was never true

398 continue 

399 if _BASH_COMPLETION_RE.search(line): 

400 is_bash_completion_file = True 

401 break 

402 if not is_bash_completion_file: 

403 _, content = _dh_config_file( 

404 debian_dir, 

405 dctrl_bin, 

406 "bash-completion", 

407 "dh_bash-completion", 

408 migration_request.acceptable_migration_issues, 

409 feature_migration, 

410 manifest, 

411 support_executable_files=True, 

412 ) 

413 else: 

414 content = None 

415 

416 if content: 

417 install_dest_sources: list[str] = [] 

418 install_as_rules: list[tuple[str, str]] = [] 

419 for dhe_line in content: 

420 if len(dhe_line.tokens) > 2: 420 ↛ 421line 420 didn't jump to line 421 because the condition on line 420 was never true

421 raise UnsupportedFeature( 

422 f"The dh_bash-completion file {dh_file.path} more than two words on" 

423 f' line {dhe_line.line_no} (line: "{dhe_line.original_line}").' 

424 ) 

425 source = dhe_line.tokens[0] 

426 dest_basename = ( 

427 dhe_line.tokens[1] 

428 if len(dhe_line.tokens) > 1 

429 else os.path.basename(source) 

430 ) 

431 if source.startswith("debian/") and not has_glob_magic(source): 

432 if dctrl_bin.name != dest_basename: 

433 dest_path = ( 

434 f"debian/{dctrl_bin.name}.{dest_basename}.bash-completion" 

435 ) 

436 else: 

437 dest_path = f"debian/{dest_basename}.bash-completion" 

438 feature_migration.rename_on_success(source, dest_path) 

439 elif len(dhe_line.tokens) == 1: 

440 install_dest_sources.append(source) 

441 else: 

442 install_as_rules.append((source, dest_basename)) 

443 

444 if install_dest_sources: 444 ↛ 458line 444 didn't jump to line 458 because the condition on line 444 was always true

445 sources: list[str] | str = ( 

446 install_dest_sources 

447 if len(install_dest_sources) > 1 

448 else install_dest_sources[0] 

449 ) 

450 installations.append( 

451 AbstractMutableYAMLInstallRule.install_dest( 

452 sources=sources, 

453 dest_dir="{{path:BASH_COMPLETION_DIR}}", 

454 into=dctrl_bin.name if not is_single_binary else None, 

455 ) 

456 ) 

457 

458 for source, dest_basename in install_as_rules: 

459 installations.append( 

460 AbstractMutableYAMLInstallRule.install_as( 

461 source=source, 

462 install_as="{{path:BASH_COMPLETION_DIR}}/" + dest_basename, 

463 into=dctrl_bin.name if not is_single_binary else None, 

464 ) 

465 ) 

466 

467 

468_SHELL_COMPLETIONS_RE = re.compile(r"^\s*\S+\s+\S+\s+\S") 

469 

470 

471def migrate_shell_completions( 

472 migration_request: MigrationRequest, 

473 feature_migration: FeatureMigration, 

474) -> None: 

475 feature_migration.tagline = "dh_shell_completions files" 

476 manifest = migration_request.manifest 

477 debian_dir = migration_request.debian_dir 

478 is_single_binary = migration_request.is_single_binary_package 

479 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

480 installations = mutable_manifest.installations(create_if_absent=False) 

481 # Note: The bash completion script used `bash-completion` whereas `dh_shell_completions` uses 

482 # `...-completions` (note the trailing `s`). In `debputy`, we always use the singular notation 

483 # because we use "one file, one completion (ruleset)". 

484 completions = ["bash", "fish", "zsh"] 

485 

486 for completion, dctrl_bin in product(completions, migration_request.all_packages): 

487 dh_file = dhe_pkgfile(debian_dir, dctrl_bin, f"{completion}-completions") 

488 if dh_file is None: 

489 continue 

490 is_completion_file = False 

491 with dh_file.open() as fd: 

492 for line in fd: 

493 line = line.strip() 

494 if not line or line[0] == "#": 

495 continue 

496 if _SHELL_COMPLETIONS_RE.search(line): 

497 is_completion_file = True 

498 break 

499 if is_completion_file: 

500 dest_path = f"debian/{dctrl_bin.name}.{completion}-completion" 

501 feature_migration.rename_on_success(dh_file.fs_path, dest_path) 

502 continue 

503 

504 _, content = _dh_config_file( 

505 debian_dir, 

506 dctrl_bin, 

507 f"{completion}-completions", 

508 "dh_shell_completions", 

509 migration_request.acceptable_migration_issues, 

510 feature_migration, 

511 manifest, 

512 remove_on_migration=True, 

513 ) 

514 

515 if content: 515 ↛ 486line 515 didn't jump to line 486 because the condition on line 515 was always true

516 install_dest_sources: list[str] = [] 

517 install_as_rules: list[tuple[str, str]] = [] 

518 for dhe_line in content: 

519 if len(dhe_line.tokens) > 2: 519 ↛ 520line 519 didn't jump to line 520 because the condition on line 519 was never true

520 raise UnsupportedFeature( 

521 f"The dh_shell_completions file {dh_file.path} more than two words on" 

522 f' line {dhe_line.line_no} (line: "{dhe_line.original_line}").' 

523 ) 

524 source = dhe_line.tokens[0] 

525 dest_basename = ( 

526 dhe_line.tokens[1] 

527 if len(dhe_line.tokens) > 1 

528 else os.path.basename(source) 

529 ) 

530 if source.startswith("debian/") and not has_glob_magic(source): 

531 if dctrl_bin.name != dest_basename: 

532 dest_path = f"debian/{dctrl_bin.name}.{dest_basename}.{completion}-completion" 

533 else: 

534 dest_path = f"debian/{dest_basename}.{completion}-completion" 

535 feature_migration.rename_on_success(source, dest_path) 

536 elif len(dhe_line.tokens) == 1: 

537 install_dest_sources.append(source) 

538 else: 

539 install_as_rules.append((source, dest_basename)) 

540 

541 completion_dir_variable = ( 

542 "{{path:" + f"{completion.upper()}_COMPLETION_DIR" + "}}" 

543 ) 

544 

545 if install_dest_sources: 545 ↛ 559line 545 didn't jump to line 559 because the condition on line 545 was always true

546 sources: list[str] | str = ( 

547 install_dest_sources 

548 if len(install_dest_sources) > 1 

549 else install_dest_sources[0] 

550 ) 

551 installations.append( 

552 AbstractMutableYAMLInstallRule.install_dest( 

553 sources=sources, 

554 dest_dir=completion_dir_variable, 

555 into=dctrl_bin.name if not is_single_binary else None, 

556 ) 

557 ) 

558 

559 for source, dest_basename in install_as_rules: 

560 installations.append( 

561 AbstractMutableYAMLInstallRule.install_as( 

562 source=source, 

563 install_as=f"{completion_dir_variable}/{dest_basename}", 

564 into=dctrl_bin.name if not is_single_binary else None, 

565 ) 

566 ) 

567 

568 

569def migrate_dh_builtusing( 

570 migration_request: MigrationRequest, 

571 feature_migration: FeatureMigration, 

572) -> None: 

573 feature_migration.tagline = "dh_builtusing configuration" 

574 for pkg in migration_request.all_packages: 

575 built_using = pkg.fields.get("Built-Using", "") 

576 static_built_using = pkg.fields.get("Static-Built-Using", "") 

577 

578 if ( 

579 "${dh-builtusing:" in built_using 

580 or "${dh-builtusing:" in static_built_using 

581 ): 

582 # TODO: Automate when migration can update `d/control`. 

583 feature_migration.warn( 

584 f"Migrate all `${ dh-builtusing:custom-pattern} ` instances in the" 

585 f" (Static-)Built-Using of {pkg.name} to" 

586 f" `packages.{pkg.name}.(static-)built-using.sources-for: glob-pattern`" 

587 f" in `debian/debputy.manifest`" 

588 ) 

589 

590 

591def migrate_dh_installsystemd_files( 

592 migration_request: MigrationRequest, 

593 feature_migration: FeatureMigration, 

594) -> None: 

595 debian_dir = migration_request.debian_dir 

596 feature_migration.tagline = "dh_installsystemd files" 

597 for dctrl_bin in migration_request.all_packages: 

598 for stem in [ 

599 "path", 

600 "service", 

601 "socket", 

602 "target", 

603 "timer", 

604 ]: 

605 pkgfile = dhe_pkgfile( 

606 debian_dir, dctrl_bin, stem, bug_950723_prefix_matching=True 

607 ) 

608 if not pkgfile: 

609 continue 

610 if not pkgfile.name.endswith(f".{stem}") or "@." not in pkgfile.name: 610 ↛ 611line 610 didn't jump to line 611 because the condition on line 610 was never true

611 raise UnsupportedFeature( 

612 f'Unable to determine the correct name for {pkgfile.fs_path}. It should be a ".@{stem}"' 

613 f" file now (foo@.service => foo.@service)" 

614 ) 

615 newname = pkgfile.name.replace("@.", ".") 

616 newname = newname[: -len(stem)] + f"@{stem}" 

617 feature_migration.rename_on_success( 

618 pkgfile.fs_path, os.path.join(debian_dir.fs_path, newname) 

619 ) 

620 

621 

622def migrate_clean_file( 

623 migration_request: MigrationRequest, 

624 feature_migration: FeatureMigration, 

625) -> None: 

626 feature_migration.tagline = "debian/clean" 

627 clean_file = migration_request.debian_dir.get("clean") 

628 if clean_file is None: 

629 return 

630 

631 mutable_manifest = assume_not_none(migration_request.manifest.mutable_manifest) 

632 

633 substitution = DHMigrationSubstitution( 

634 DpkgArchitectureBuildProcessValuesTable(), 

635 migration_request.acceptable_migration_issues, 

636 feature_migration, 

637 mutable_manifest, 

638 ) 

639 content = dhe_filedoublearray( 

640 clean_file, 

641 substitution, 

642 ) 

643 

644 remove_during_clean_rules = mutable_manifest.remove_during_clean( 

645 create_if_absent=False 

646 ) 

647 tokens = chain.from_iterable(c.tokens for c in content) 

648 rules_before = len(remove_during_clean_rules) 

649 remove_during_clean_rules.extend(tokens) 

650 rules_after = len(remove_during_clean_rules) 

651 feature_migration.successful_manifest_changes += rules_after - rules_before 

652 feature_migration.remove_on_success(clean_file.fs_path) 

653 

654 

655def migrate_maintscript( 

656 migration_request: MigrationRequest, 

657 feature_migration: FeatureMigration, 

658) -> None: 

659 feature_migration.tagline = "dh_installdeb files" 

660 manifest = migration_request.manifest 

661 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

662 for dctrl_bin in migration_request.all_packages: 

663 mainscript_file, content = _dh_config_file( 

664 migration_request.debian_dir, 

665 dctrl_bin, 

666 "maintscript", 

667 "dh_installdeb", 

668 migration_request.acceptable_migration_issues, 

669 feature_migration, 

670 manifest, 

671 ) 

672 

673 if mainscript_file is None: 

674 continue 

675 assert content is not None 

676 

677 package_definition = mutable_manifest.package(dctrl_bin.name) 

678 conffiles = { 

679 it.obsolete_conffile: it 

680 for it in package_definition.conffile_management_items() 

681 } 

682 seen_conffiles = set() 

683 

684 for dhe_line in content: 

685 cmd = dhe_line.tokens[0] 

686 if cmd not in {"rm_conffile", "mv_conffile"}: 686 ↛ 687line 686 didn't jump to line 687 because the condition on line 686 was never true

687 raise UnsupportedFeature( 

688 f"The dh_installdeb file {mainscript_file.path} contains the (currently)" 

689 f' unsupported command "{cmd}" on line {dhe_line.line_no}' 

690 f' (line: "{dhe_line.original_line}")' 

691 ) 

692 

693 try: 

694 ( 

695 _, 

696 obsolete_conffile, 

697 new_conffile, 

698 prior_to_version, 

699 owning_package, 

700 ) = _validate_rm_mv_conffile(dctrl_bin.name, dhe_line) 

701 except ValueError as e: 

702 _error( 

703 f"Validation error in {mainscript_file} on line {dhe_line.line_no}. The error was: {e.args[0]}." 

704 ) 

705 

706 if obsolete_conffile in seen_conffiles: 706 ↛ 707line 706 didn't jump to line 707 because the condition on line 706 was never true

707 raise ConflictingChange( 

708 f'The {mainscript_file} file defines actions for "{obsolete_conffile}" twice!' 

709 f" Please ensure that it is defined at most once in that file." 

710 ) 

711 seen_conffiles.add(obsolete_conffile) 

712 

713 if cmd == "rm_conffile": 

714 item = MutableYAMLConffileManagementItem.rm_conffile( 

715 obsolete_conffile, 

716 prior_to_version, 

717 owning_package, 

718 ) 

719 else: 

720 assert cmd == "mv_conffile" 

721 item = MutableYAMLConffileManagementItem.mv_conffile( 

722 obsolete_conffile, 

723 assume_not_none(new_conffile), 

724 prior_to_version, 

725 owning_package, 

726 ) 

727 

728 existing_def = conffiles.get(item.obsolete_conffile) 

729 if existing_def is not None: 729 ↛ 730line 729 didn't jump to line 730 because the condition on line 729 was never true

730 if not ( 

731 item.command == existing_def.command 

732 and item.new_conffile == existing_def.new_conffile 

733 and item.prior_to_version == existing_def.prior_to_version 

734 and item.owning_package == existing_def.owning_package 

735 ): 

736 raise ConflictingChange( 

737 f"The maintscript defines the action {item.command} for" 

738 f' "{obsolete_conffile}" in {mainscript_file}, but there is another' 

739 f" conffile management definition for same path defined already (in the" 

740 f" existing manifest or an migration e.g., inside {mainscript_file})" 

741 ) 

742 continue 

743 

744 package_definition.add_conffile_management(item) 

745 feature_migration.successful_manifest_changes += 1 

746 

747 

748@dataclasses.dataclass(slots=True) 

749class SourcesAndConditional: 

750 dest_dir: str | None = None 

751 sources: list[str] = dataclasses.field(default_factory=list) 

752 conditional: str | Mapping[str, Any] | None = None 

753 

754 

755def _raise_on_unsupported_path( 

756 p: str, 

757 path: VirtualPath, 

758 line_no: int, 

759) -> str: 

760 if p.startswith("../") or any(s == ".." for s in p.split("/")): 760 ↛ 761line 760 didn't jump to line 761 because the condition on line 760 was never true

761 _error( 

762 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" 

763 ) 

764 return p 

765 

766 

767def _strip_d_tmp(p: str, path: VirtualPath, line_no: int) -> str: 

768 if p.startswith("debian/tmp/") and len(p) > 11: 

769 return p[11:] 

770 if p.startswith("../"): 

771 pruned = p[3:] 

772 if pruned.startswith("../"): 772 ↛ 773line 772 didn't jump to line 773 because the condition on line 772 was never true

773 _raise_on_unsupported_path(p, path, line_no) 

774 _error( 

775 f"Internal error: _raise_on_unsupported_path should have rejected the path at {p!r} ({path.fs_path}:{line_no})" 

776 ) 

777 return f"debian/{pruned}" 

778 

779 return p 

780 

781 

782def migrate_install_file( 

783 migration_request: MigrationRequest, 

784 feature_migration: FeatureMigration, 

785) -> None: 

786 feature_migration.tagline = "dh_install config files" 

787 manifest = migration_request.manifest 

788 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

789 installations = mutable_manifest.installations(create_if_absent=False) 

790 priority_lines = [] 

791 remaining_install_lines = [] 

792 warn_about_fixmes_in_dest_dir = False 

793 

794 is_single_binary = migration_request.is_single_binary_package 

795 

796 for dctrl_bin in migration_request.all_packages: 

797 install_file, content = _dh_config_file( 

798 migration_request.debian_dir, 

799 dctrl_bin, 

800 "install", 

801 "dh_install", 

802 migration_request.acceptable_migration_issues, 

803 feature_migration, 

804 manifest, 

805 support_executable_files=True, 

806 allow_dh_exec_rename=True, 

807 ) 

808 if not install_file or not content: 

809 continue 

810 current_sources = [] 

811 sources_by_destdir: dict[tuple[str, tuple[str, ...]], SourcesAndConditional] = ( 

812 {} 

813 ) 

814 install_as_rules = [] 

815 multi_dest = collections.defaultdict(list) 

816 seen_sources = set() 

817 multi_dest_sources: set[str] = set() 

818 

819 for dhe_line in content: 

820 special_rule = None 

821 if "=>" in dhe_line.tokens: 

822 if dhe_line.tokens[0] == "=>" and len(dhe_line.tokens) == 2: 

823 # This rule must be as early as possible to retain the semantics 

824 path = _strip_d_tmp( 

825 _normalize_path( 

826 dhe_line.tokens[1], 

827 with_prefix=False, 

828 allow_and_keep_upward_segments=True, 

829 ), 

830 dhe_line.config_file, 

831 dhe_line.line_no, 

832 ) 

833 special_rule = AbstractMutableYAMLInstallRule.install_dest( 

834 path, 

835 dctrl_bin.name if not is_single_binary else None, 

836 dest_dir=None, 

837 when=dhe_line.conditional(), 

838 ) 

839 elif len(dhe_line.tokens) != 3: 839 ↛ 840line 839 didn't jump to line 840 because the condition on line 839 was never true

840 _error( 

841 f"Validation error in {install_file.path} on line {dhe_line.line_no}. Cannot migrate dh-exec" 

842 ' renames that is not exactly "SOURCE => TARGET" or "=> TARGET".' 

843 ) 

844 else: 

845 install_rule = AbstractMutableYAMLInstallRule.install_as( 

846 _strip_d_tmp( 

847 _normalize_path( 

848 dhe_line.tokens[0], 

849 with_prefix=False, 

850 allow_and_keep_upward_segments=True, 

851 ), 

852 dhe_line.config_file, 

853 dhe_line.line_no, 

854 ), 

855 _raise_on_unsupported_path( 

856 _normalize_path( 

857 dhe_line.tokens[2], 

858 with_prefix=False, 

859 allow_and_keep_upward_segments=True, 

860 ), 

861 dhe_line.config_file, 

862 dhe_line.line_no, 

863 ), 

864 dctrl_bin.name if not is_single_binary else None, 

865 when=dhe_line.conditional(), 

866 ) 

867 install_as_rules.append(install_rule) 

868 else: 

869 if len(dhe_line.tokens) > 1: 

870 sources = list( 

871 _strip_d_tmp( 

872 _normalize_path( 

873 w, 

874 with_prefix=False, 

875 allow_and_keep_upward_segments=True, 

876 ), 

877 dhe_line.config_file, 

878 dhe_line.line_no, 

879 ) 

880 for w in dhe_line.tokens[:-1] 

881 ) 

882 dest_dir = _raise_on_unsupported_path( 

883 _normalize_path( 

884 dhe_line.tokens[-1], 

885 with_prefix=False, 

886 allow_and_keep_upward_segments=True, 

887 ), 

888 dhe_line.config_file, 

889 dhe_line.line_no, 

890 ) 

891 else: 

892 sources = list( 

893 _strip_d_tmp( 

894 _normalize_path( 

895 w, 

896 with_prefix=False, 

897 allow_and_keep_upward_segments=True, 

898 ), 

899 dhe_line.config_file, 

900 dhe_line.line_no, 

901 ) 

902 for w in dhe_line.tokens 

903 ) 

904 dest_dir = None 

905 

906 multi_dest_sources.update(s for s in sources if s in seen_sources) 

907 seen_sources.update(sources) 

908 

909 if dest_dir is None and dhe_line.conditional() is None: 

910 current_sources.extend(sources) 

911 continue 

912 key = (dest_dir, dhe_line.conditional_key()) 

913 ctor = functools.partial( 

914 SourcesAndConditional, 

915 dest_dir=dest_dir, 

916 conditional=dhe_line.conditional(), 

917 ) 

918 md = _fetch_or_create( 

919 sources_by_destdir, 

920 key, 

921 ctor, 

922 ) 

923 md.sources.extend(sources) 

924 

925 if special_rule: 

926 priority_lines.append(special_rule) 

927 

928 remaining_install_lines.extend(install_as_rules) 

929 

930 for md in sources_by_destdir.values(): 

931 if multi_dest_sources: 

932 sources = [s for s in md.sources if s not in multi_dest_sources] 

933 already_installed = (s for s in md.sources if s in multi_dest_sources) 

934 for s in already_installed: 

935 # The sources are ignored, so we can reuse the object as-is 

936 multi_dest[s].append(md) 

937 if not sources: 

938 continue 

939 else: 

940 sources = md.sources 

941 install_rule = AbstractMutableYAMLInstallRule.install_dest( 

942 sources[0] if len(sources) == 1 else sources, 

943 dctrl_bin.name if not is_single_binary else None, 

944 dest_dir=md.dest_dir, 

945 when=md.conditional, 

946 ) 

947 remaining_install_lines.append(install_rule) 

948 

949 if current_sources: 

950 if multi_dest_sources: 

951 sources = [s for s in current_sources if s not in multi_dest_sources] 

952 already_installed = ( 

953 s for s in current_sources if s in multi_dest_sources 

954 ) 

955 for s in already_installed: 

956 # The sources are ignored, so we can reuse the object as-is 

957 dest_dir = os.path.dirname(s) 

958 if has_glob_magic(dest_dir): 

959 warn_about_fixmes_in_dest_dir = True 

960 dest_dir = f"FIXME: {dest_dir} (could not reliably compute the dest dir)" 

961 multi_dest[s].append( 

962 SourcesAndConditional( 

963 dest_dir=dest_dir, 

964 conditional=None, 

965 ) 

966 ) 

967 else: 

968 sources = current_sources 

969 

970 if sources: 

971 install_rule = AbstractMutableYAMLInstallRule.install_dest( 

972 sources[0] if len(sources) == 1 else sources, 

973 dctrl_bin.name if not is_single_binary else None, 

974 dest_dir=None, 

975 ) 

976 remaining_install_lines.append(install_rule) 

977 

978 if multi_dest: 

979 for source, dest_and_conditionals in multi_dest.items(): 

980 dest_dirs = [dac.dest_dir for dac in dest_and_conditionals] 

981 # We assume the conditional is the same. 

982 conditional = next( 

983 iter( 

984 dac.conditional 

985 for dac in dest_and_conditionals 

986 if dac.conditional is not None 

987 ), 

988 None, 

989 ) 

990 remaining_install_lines.append( 

991 AbstractMutableYAMLInstallRule.multi_dest_install( 

992 source, 

993 dest_dirs, 

994 dctrl_bin.name if not is_single_binary else None, 

995 when=conditional, 

996 ) 

997 ) 

998 

999 if priority_lines: 

1000 installations.extend(priority_lines) 

1001 

1002 if remaining_install_lines: 

1003 installations.extend(remaining_install_lines) 

1004 

1005 feature_migration.successful_manifest_changes += len(priority_lines) + len( 

1006 remaining_install_lines 

1007 ) 

1008 if warn_about_fixmes_in_dest_dir: 

1009 feature_migration.warn( 

1010 "TODO: FIXME left in dest-dir(s) of some installation rules." 

1011 " Please review these and remove the FIXME (plus correct as necessary)" 

1012 ) 

1013 

1014 

1015def migrate_installdocs_file( 

1016 migration_request: MigrationRequest, 

1017 feature_migration: FeatureMigration, 

1018) -> None: 

1019 feature_migration.tagline = "dh_installdocs config files" 

1020 manifest = migration_request.manifest 

1021 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1022 installations = mutable_manifest.installations(create_if_absent=False) 

1023 

1024 is_single_binary = migration_request.is_single_binary_package 

1025 

1026 for dctrl_bin in migration_request.all_packages: 

1027 install_file, content = _dh_config_file( 

1028 migration_request.debian_dir, 

1029 dctrl_bin, 

1030 "docs", 

1031 "dh_installdocs", 

1032 migration_request.acceptable_migration_issues, 

1033 feature_migration, 

1034 manifest, 

1035 support_executable_files=True, 

1036 ) 

1037 if not install_file: 

1038 continue 

1039 assert content is not None 

1040 docs: list[str] = [] 

1041 for dhe_line in content: 

1042 if dhe_line.arch_filter or dhe_line.build_profile_filter: 1042 ↛ 1043line 1042 didn't jump to line 1043 because the condition on line 1042 was never true

1043 _error( 

1044 f"Unable to migrate line {dhe_line.line_no} of {install_file.path}." 

1045 " Missing support for conditions." 

1046 ) 

1047 docs.extend( 

1048 _raise_on_unsupported_path( 

1049 _normalize_path( 

1050 w, with_prefix=False, allow_and_keep_upward_segments=True 

1051 ), 

1052 dhe_line.config_file, 

1053 dhe_line.line_no, 

1054 ) 

1055 for w in dhe_line.tokens 

1056 ) 

1057 

1058 if not docs: 1058 ↛ 1059line 1058 didn't jump to line 1059 because the condition on line 1058 was never true

1059 continue 

1060 feature_migration.successful_manifest_changes += 1 

1061 install_rule = AbstractMutableYAMLInstallRule.install_docs( 

1062 docs if len(docs) > 1 else docs[0], 

1063 dctrl_bin.name if not is_single_binary else None, 

1064 ) 

1065 installations.create_definition_if_missing() 

1066 installations.append(install_rule) 

1067 

1068 

1069def migrate_installexamples_file( 

1070 migration_request: MigrationRequest, 

1071 feature_migration: FeatureMigration, 

1072) -> None: 

1073 feature_migration.tagline = "dh_installexamples config files" 

1074 manifest = migration_request.manifest 

1075 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1076 installations = mutable_manifest.installations(create_if_absent=False) 

1077 is_single_binary = migration_request.is_single_binary_package 

1078 

1079 for dctrl_bin in manifest.all_packages: 

1080 install_file, content = _dh_config_file( 

1081 migration_request.debian_dir, 

1082 dctrl_bin, 

1083 "examples", 

1084 "dh_installexamples", 

1085 migration_request.acceptable_migration_issues, 

1086 feature_migration, 

1087 manifest, 

1088 support_executable_files=True, 

1089 ) 

1090 if not install_file: 

1091 continue 

1092 assert content is not None 

1093 examples: list[str] = [] 

1094 for dhe_line in content: 

1095 if dhe_line.arch_filter or dhe_line.build_profile_filter: 1095 ↛ 1096line 1095 didn't jump to line 1096 because the condition on line 1095 was never true

1096 _error( 

1097 f"Unable to migrate line {dhe_line.line_no} of {install_file.path}." 

1098 " Missing support for conditions." 

1099 ) 

1100 examples.extend( 

1101 _raise_on_unsupported_path( 

1102 _normalize_path( 

1103 w, with_prefix=False, allow_and_keep_upward_segments=True 

1104 ), 

1105 dhe_line.config_file, 

1106 dhe_line.line_no, 

1107 ) 

1108 for w in dhe_line.tokens 

1109 ) 

1110 

1111 if not examples: 1111 ↛ 1112line 1111 didn't jump to line 1112 because the condition on line 1111 was never true

1112 continue 

1113 feature_migration.successful_manifest_changes += 1 

1114 install_rule = AbstractMutableYAMLInstallRule.install_examples( 

1115 examples if len(examples) > 1 else examples[0], 

1116 dctrl_bin.name if not is_single_binary else None, 

1117 ) 

1118 installations.create_definition_if_missing() 

1119 installations.append(install_rule) 

1120 

1121 

1122@dataclasses.dataclass(slots=True) 

1123class InfoFilesDefinition: 

1124 sources: list[str] = dataclasses.field(default_factory=list) 

1125 conditional: str | Mapping[str, Any] | None = None 

1126 

1127 

1128def migrate_installinfo_file( 

1129 migration_request: MigrationRequest, 

1130 feature_migration: FeatureMigration, 

1131) -> None: 

1132 feature_migration.tagline = "dh_installinfo config files" 

1133 manifest = migration_request.manifest 

1134 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1135 installations = mutable_manifest.installations(create_if_absent=False) 

1136 is_single_binary = migration_request.is_single_binary_package 

1137 

1138 for dctrl_bin in manifest.all_packages: 

1139 info_file, content = _dh_config_file( 

1140 migration_request.debian_dir, 

1141 dctrl_bin, 

1142 "info", 

1143 "dh_installinfo", 

1144 migration_request.acceptable_migration_issues, 

1145 feature_migration, 

1146 manifest, 

1147 support_executable_files=True, 

1148 ) 

1149 if not info_file: 

1150 continue 

1151 assert content is not None 

1152 info_files_by_condition: dict[tuple[str, ...], InfoFilesDefinition] = {} 

1153 for dhe_line in content: 

1154 key = dhe_line.conditional_key() 

1155 ctr = functools.partial( 

1156 InfoFilesDefinition, conditional=dhe_line.conditional() 

1157 ) 

1158 info_def = _fetch_or_create( 

1159 info_files_by_condition, 

1160 key, 

1161 ctr, 

1162 ) 

1163 info_def.sources.extend( 

1164 _raise_on_unsupported_path( 

1165 _normalize_path( 

1166 w, with_prefix=False, allow_and_keep_upward_segments=True 

1167 ), 

1168 dhe_line.config_file, 

1169 dhe_line.line_no, 

1170 ) 

1171 for w in dhe_line.tokens 

1172 ) 

1173 

1174 if not info_files_by_condition: 1174 ↛ 1175line 1174 didn't jump to line 1175 because the condition on line 1174 was never true

1175 continue 

1176 feature_migration.successful_manifest_changes += 1 

1177 installations.create_definition_if_missing() 

1178 for info_def in info_files_by_condition.values(): 

1179 info_files = info_def.sources 

1180 install_rule = AbstractMutableYAMLInstallRule.install_docs( 

1181 info_files if len(info_files) > 1 else info_files[0], 

1182 dctrl_bin.name if not is_single_binary else None, 

1183 dest_dir="{{path:GNU_INFO_DIR}}", 

1184 when=info_def.conditional, 

1185 ) 

1186 installations.append(install_rule) 

1187 

1188 

1189@dataclasses.dataclass(slots=True) 

1190class ManpageDefinition: 

1191 sources: list[str] = dataclasses.field(default_factory=list) 

1192 language: str | None = None 

1193 conditional: str | Mapping[str, Any] | None = None 

1194 

1195 

1196DK = TypeVar("DK") 

1197DV = TypeVar("DV") 

1198 

1199 

1200def _fetch_or_create(d: dict[DK, DV], key: DK, factory: Callable[[], DV]) -> DV: 

1201 v = d.get(key) 

1202 if v is None: 

1203 v = factory() 

1204 d[key] = v 

1205 return v 

1206 

1207 

1208def migrate_installman_file( 

1209 migration_request: MigrationRequest, 

1210 feature_migration: FeatureMigration, 

1211) -> None: 

1212 feature_migration.tagline = "dh_installman config files" 

1213 manifest = migration_request.manifest 

1214 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1215 installations = mutable_manifest.installations(create_if_absent=False) 

1216 is_single_binary = migration_request.is_single_binary_package 

1217 warn_about_basename = False 

1218 

1219 for dctrl_bin in migration_request.all_packages: 

1220 manpages_file, content = _dh_config_file( 

1221 migration_request.debian_dir, 

1222 dctrl_bin, 

1223 "manpages", 

1224 "dh_installman", 

1225 migration_request.acceptable_migration_issues, 

1226 feature_migration, 

1227 manifest, 

1228 support_executable_files=True, 

1229 allow_dh_exec_rename=True, 

1230 ) 

1231 if not manpages_file: 

1232 continue 

1233 assert content is not None 

1234 

1235 vanilla_definitions = [] 

1236 install_as_rules = [] 

1237 complex_definitions: dict[ 

1238 tuple[str | None, tuple[str, ...]], ManpageDefinition 

1239 ] = {} 

1240 install_rule: AbstractMutableYAMLInstallRule 

1241 for dhe_line in content: 

1242 if "=>" in dhe_line.tokens: 1242 ↛ 1245line 1242 didn't jump to line 1245 because the condition on line 1242 was never true

1243 # dh-exec allows renaming features. For `debputy`, we degenerate it into an `install` (w. `as`) feature 

1244 # without any of the `install-man` features. 

1245 if dhe_line.tokens[0] == "=>" and len(dhe_line.tokens) == 2: 

1246 _error( 

1247 f'Unsupported "=> DEST" rule for error in {manpages_file.path} on line {dhe_line.line_no}."' 

1248 f' Cannot migrate dh-exec renames that is not exactly "SOURCE => TARGET" for d/manpages files.' 

1249 ) 

1250 elif len(dhe_line.tokens) != 3: 

1251 _error( 

1252 f"Validation error in {manpages_file.path} on line {dhe_line.line_no}. Cannot migrate dh-exec" 

1253 ' renames that is not exactly "SOURCE => TARGET" or "=> TARGET".' 

1254 ) 

1255 else: 

1256 install_rule = AbstractMutableYAMLInstallRule.install_doc_as( 

1257 _raise_on_unsupported_path( 

1258 _normalize_path( 

1259 dhe_line.tokens[0], 

1260 with_prefix=False, 

1261 allow_and_keep_upward_segments=True, 

1262 ), 

1263 dhe_line.config_file, 

1264 dhe_line.line_no, 

1265 ), 

1266 _raise_on_unsupported_path( 

1267 _normalize_path( 

1268 dhe_line.tokens[2], 

1269 with_prefix=False, 

1270 allow_and_keep_upward_segments=True, 

1271 ), 

1272 dhe_line.config_file, 

1273 dhe_line.line_no, 

1274 ), 

1275 dctrl_bin.name if not is_single_binary else None, 

1276 when=dhe_line.conditional(), 

1277 ) 

1278 install_as_rules.append(install_rule) 

1279 continue 

1280 

1281 sources = [ 

1282 _raise_on_unsupported_path( 

1283 _normalize_path( 

1284 w, with_prefix=False, allow_and_keep_upward_segments=True 

1285 ), 

1286 dhe_line.config_file, 

1287 dhe_line.line_no, 

1288 ) 

1289 for w in dhe_line.tokens 

1290 ] 

1291 needs_basename = any( 

1292 MAN_GUESS_FROM_BASENAME.search(x) 

1293 and not MAN_GUESS_LANG_FROM_PATH.search(x) 

1294 for x in sources 

1295 ) 

1296 if needs_basename or dhe_line.conditional() is not None: 

1297 if needs_basename: 1297 ↛ 1301line 1297 didn't jump to line 1301 because the condition on line 1297 was always true

1298 warn_about_basename = True 

1299 language = "derive-from-basename" 

1300 else: 

1301 language = None 

1302 key = (language, dhe_line.conditional_key()) 

1303 ctor = functools.partial( 

1304 ManpageDefinition, 

1305 language=language, 

1306 conditional=dhe_line.conditional(), 

1307 ) 

1308 manpage_def = _fetch_or_create( 

1309 complex_definitions, 

1310 key, 

1311 ctor, 

1312 ) 

1313 manpage_def.sources.extend(sources) 

1314 else: 

1315 vanilla_definitions.extend(sources) 

1316 

1317 if not install_as_rules and not vanilla_definitions and not complex_definitions: 1317 ↛ 1318line 1317 didn't jump to line 1318 because the condition on line 1317 was never true

1318 continue 

1319 feature_migration.successful_manifest_changes += 1 

1320 installations.create_definition_if_missing() 

1321 installations.extend(install_as_rules) 

1322 if vanilla_definitions: 1322 ↛ 1334line 1322 didn't jump to line 1334 because the condition on line 1322 was always true

1323 man_source = ( 

1324 vanilla_definitions 

1325 if len(vanilla_definitions) > 1 

1326 else vanilla_definitions[0] 

1327 ) 

1328 install_rule = AbstractMutableYAMLInstallRule.install_man( 

1329 man_source, 

1330 dctrl_bin.name if not is_single_binary else None, 

1331 None, 

1332 ) 

1333 installations.append(install_rule) 

1334 for manpage_def in complex_definitions.values(): 

1335 sources = manpage_def.sources 

1336 install_rule = AbstractMutableYAMLInstallRule.install_man( 

1337 sources if len(sources) > 1 else sources[0], 

1338 dctrl_bin.name if not is_single_binary else None, 

1339 manpage_def.language, 

1340 when=manpage_def.conditional, 

1341 ) 

1342 installations.append(install_rule) 

1343 

1344 if warn_about_basename: 

1345 feature_migration.warn( 

1346 'Detected man pages that might rely on "derive-from-basename" logic. Please double check' 

1347 " that the generated `install-man` rules are correct" 

1348 ) 

1349 

1350 

1351def migrate_not_installed_file( 

1352 migration_request: MigrationRequest, 

1353 feature_migration: FeatureMigration, 

1354) -> None: 

1355 feature_migration.tagline = "dh_missing's not-installed config file" 

1356 manifest = migration_request.manifest 

1357 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1358 installations = mutable_manifest.installations(create_if_absent=False) 

1359 main_binary = migration_request.main_binary 

1360 

1361 missing_file, content = _dh_config_file( 

1362 migration_request.debian_dir, 

1363 main_binary, 

1364 "not-installed", 

1365 "dh_missing", 

1366 migration_request.acceptable_migration_issues, 

1367 feature_migration, 

1368 manifest, 

1369 support_executable_files=False, 

1370 pkgfile_lookup=False, 

1371 ) 

1372 discard_rules: list[str] = [] 

1373 if missing_file: 

1374 assert content is not None 

1375 for dhe_line in content: 

1376 discard_rules.extend( 

1377 _raise_on_unsupported_path( 

1378 _normalize_path( 

1379 w, with_prefix=False, allow_and_keep_upward_segments=True 

1380 ), 

1381 dhe_line.config_file, 

1382 dhe_line.line_no, 

1383 ) 

1384 for w in dhe_line.tokens 

1385 ) 

1386 

1387 if discard_rules: 

1388 feature_migration.successful_manifest_changes += 1 

1389 install_rule = AbstractMutableYAMLInstallRule.discard( 

1390 discard_rules if len(discard_rules) > 1 else discard_rules[0], 

1391 ) 

1392 installations.create_definition_if_missing() 

1393 installations.append(install_rule) 

1394 

1395 

1396def min_dh_compat_check( 

1397 migration_request: MigrationRequest, 

1398 feature_migration: FeatureMigration, 

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 = ( 

1404 14 if any(not p.is_arch_all for p in migration_request.all_packages) else 12 

1405 ) 

1406 feature_migration.assumed_compat = min_compat 

1407 

1408 

1409def detect_modprobe_files( 

1410 migration_request: MigrationRequest, 

1411 feature_migration: FeatureMigration, 

1412) -> None: 

1413 feature_migration.tagline = "detect dh_installmodules files (min dh compat)" 

1414 for dctrl_bin in migration_request.all_packages: 

1415 dh_config_file = dhe_pkgfile( 

1416 migration_request.debian_dir, dctrl_bin, "modprobe" 

1417 ) 

1418 if dh_config_file is not None: 

1419 feature_migration.assumed_compat = 14 

1420 break 

1421 

1422 

1423def migrate_tmpfile( 

1424 migration_request: MigrationRequest, 

1425 feature_migration: FeatureMigration, 

1426) -> None: 

1427 feature_migration.tagline = "dh_installtmpfiles config files" 

1428 feature_migration.assumed_compat = 14 

1429 debian_dir = migration_request.debian_dir 

1430 for dctrl_bin in migration_request.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 ) 

1444 

1445 

1446def migrate_lintian_overrides_files( 

1447 migration_request: MigrationRequest, 

1448 feature_migration: FeatureMigration, 

1449) -> None: 

1450 feature_migration.tagline = "dh_lintian config files" 

1451 for dctrl_bin in migration_request.all_packages: 

1452 # We do not support executable lintian-overrides and `_dh_config_file` handles all of that. 

1453 # Therefore, the return value is irrelevant to us. 

1454 _dh_config_file( 

1455 migration_request.debian_dir, 

1456 dctrl_bin, 

1457 "lintian-overrides", 

1458 "dh_lintian", 

1459 migration_request.acceptable_migration_issues, 

1460 feature_migration, 

1461 migration_request.manifest, 

1462 support_executable_files=False, 

1463 remove_on_migration=False, 

1464 ) 

1465 

1466 

1467def migrate_links_files( 

1468 migration_request: MigrationRequest, 

1469 feature_migration: FeatureMigration, 

1470) -> None: 

1471 feature_migration.tagline = "dh_link files" 

1472 manifest = migration_request.manifest 

1473 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1474 for dctrl_bin in migration_request.all_packages: 

1475 links_file, content = _dh_config_file( 

1476 migration_request.debian_dir, 

1477 dctrl_bin, 

1478 "links", 

1479 "dh_link", 

1480 migration_request.acceptable_migration_issues, 

1481 feature_migration, 

1482 manifest, 

1483 support_executable_files=True, 

1484 ) 

1485 

1486 if links_file is None: 

1487 continue 

1488 assert content is not None 

1489 

1490 package_definition = mutable_manifest.package(dctrl_bin.name) 

1491 defined_symlink = { 

1492 symlink.symlink_path: symlink.symlink_target 

1493 for symlink in package_definition.symlinks() 

1494 } 

1495 

1496 seen_symlinks: set[str] = set() 

1497 

1498 for dhe_line in content: 

1499 if len(dhe_line.tokens) != 2: 1499 ↛ 1500line 1499 didn't jump to line 1500 because the condition on line 1499 was never true

1500 raise UnsupportedFeature( 

1501 f"The dh_link file {links_file.fs_path} did not have exactly two paths on line" 

1502 f' {dhe_line.line_no} (line: "{dhe_line.original_line}"' 

1503 ) 

1504 target, source = dhe_line.tokens 

1505 source = _raise_on_unsupported_path( 

1506 source, 

1507 dhe_line.config_file, 

1508 dhe_line.line_no, 

1509 ) 

1510 target = _raise_on_unsupported_path( 

1511 target, 

1512 dhe_line.config_file, 

1513 dhe_line.line_no, 

1514 ) 

1515 if source in seen_symlinks: 1515 ↛ 1517line 1515 didn't jump to line 1517 because the condition on line 1515 was never true

1516 # According to #934499, this has happened in the wild already 

1517 raise ConflictingChange( 

1518 f"The {links_file.fs_path} file defines the link path {source} twice! Please ensure" 

1519 " that it is defined at most once in that file" 

1520 ) 

1521 seen_symlinks.add(source) 

1522 # Symlinks in .links are always considered absolute, but you were not required to have a leading slash. 

1523 # However, in the debputy manifest, you can have relative links, so we should ensure it is explicitly 

1524 # absolute. 

1525 if not target.startswith("/"): 1525 ↛ 1527line 1525 didn't jump to line 1527 because the condition on line 1525 was always true

1526 target = "/" + target 

1527 existing_target = defined_symlink.get(source) 

1528 if existing_target is not None: 1528 ↛ 1529line 1528 didn't jump to line 1529 because the condition on line 1528 was never true

1529 if existing_target != target: 

1530 raise ConflictingChange( 

1531 f'The symlink "{source}" points to "{target}" in {links_file}, but there is' 

1532 f' another symlink with same path pointing to "{existing_target}" defined' 

1533 " already (in the existing manifest or an migration e.g., inside" 

1534 f" {links_file.fs_path})" 

1535 ) 

1536 continue 

1537 condition = dhe_line.conditional() 

1538 package_definition.add_symlink( 

1539 MutableYAMLSymlink.new_symlink( 

1540 source, 

1541 target, 

1542 condition, 

1543 ) 

1544 ) 

1545 feature_migration.successful_manifest_changes += 1 

1546 

1547 

1548def migrate_misspelled_readme_debian_files( 

1549 migration_request: MigrationRequest, 

1550 feature_migration: FeatureMigration, 

1551) -> None: 

1552 feature_migration.tagline = "misspelled README.Debian files" 

1553 debian_dir = migration_request.debian_dir 

1554 for dctrl_bin in migration_request.all_packages: 

1555 readme, _ = _dh_config_file( 

1556 debian_dir, 

1557 dctrl_bin, 

1558 "README.debian", 

1559 "dh_installdocs", 

1560 migration_request.acceptable_migration_issues, 

1561 feature_migration, 

1562 migration_request.manifest, 

1563 support_executable_files=False, 

1564 remove_on_migration=False, 

1565 ) 

1566 if readme is None: 

1567 continue 

1568 new_name = readme.name.replace("README.debian", "README.Debian") 

1569 assert readme.name != new_name 

1570 _rename_file_if_exists( 

1571 debian_dir, 

1572 readme.name, 

1573 new_name, 

1574 feature_migration, 

1575 ) 

1576 

1577 

1578def migrate_doc_base_files( 

1579 migration_request: MigrationRequest, 

1580 feature_migration: FeatureMigration, 

1581) -> None: 

1582 feature_migration.tagline = "doc-base files" 

1583 debian_dir = migration_request.debian_dir 

1584 # ignore the dh_make ".EX" file if one should still be present. The dh_installdocs tool ignores it too. 

1585 possible_effected_doc_base_files = [ 

1586 f 

1587 for f in debian_dir.iterdir() 

1588 if ( 

1589 (".doc-base." in f.name or f.name.startswith("doc-base.")) 

1590 and not f.name.endswith("doc-base.EX") 

1591 ) 

1592 ] 

1593 known_packages = {d.name: d for d in migration_request.all_packages} 

1594 main_package = migration_request.main_binary 

1595 for doc_base_file in possible_effected_doc_base_files: 

1596 parts = doc_base_file.name.split(".") 

1597 owning_package = known_packages.get(parts[0]) 

1598 if owning_package is None: 1598 ↛ 1599line 1598 didn't jump to line 1599 because the condition on line 1598 was never true

1599 owning_package = main_package 

1600 package_part = None 

1601 else: 

1602 package_part = parts[0] 

1603 parts = parts[1:] 

1604 

1605 if not parts or parts[0] != "doc-base": 1605 ↛ 1607line 1605 didn't jump to line 1607 because the condition on line 1605 was never true

1606 # Not a doc-base file after all 

1607 continue 

1608 

1609 if len(parts) > 1: 1609 ↛ 1616line 1609 didn't jump to line 1616 because the condition on line 1609 was always true

1610 name_part = ".".join(parts[1:]) 

1611 if package_part is None: 1611 ↛ 1613line 1611 didn't jump to line 1613 because the condition on line 1611 was never true

1612 # Named files must have a package prefix 

1613 package_part = owning_package.name 

1614 else: 

1615 # No rename needed 

1616 continue 

1617 

1618 new_basename = ".".join(filter(None, (package_part, name_part, "doc-base"))) 

1619 _rename_file_if_exists( 

1620 debian_dir, 

1621 doc_base_file.name, 

1622 new_basename, 

1623 feature_migration, 

1624 ) 

1625 

1626 

1627def migrate_dh_hook_targets( 

1628 migration_request: MigrationRequest, 

1629 feature_migration: FeatureMigration, 

1630) -> None: 

1631 feature_migration.tagline = "dh hook targets" 

1632 source_root = os.path.dirname(migration_request.debian_dir.fs_path) 

1633 if source_root == "": 

1634 source_root = "." 

1635 detected_hook_targets = json.loads( 

1636 subprocess.check_output( 

1637 ["dh_assistant", "detect-hook-targets"], 

1638 cwd=source_root, 

1639 ).decode("utf-8") 

1640 ) 

1641 sample_hook_target: str | None = None 

1642 debputy_integration_mode = migration_request.migration_target 

1643 assert debputy_integration_mode is not None 

1644 replaced_commands = DH_COMMANDS_REPLACED[debputy_integration_mode] 

1645 

1646 for hook_target_def in detected_hook_targets["hook-targets"]: 

1647 if hook_target_def["is-empty"]: 

1648 continue 

1649 command = hook_target_def["command"] 

1650 if command not in replaced_commands: 

1651 continue 

1652 hook_target = hook_target_def["target-name"] 

1653 advice = MIGRATION_AID_FOR_OVERRIDDEN_COMMANDS.get(command) 

1654 if advice is None: 

1655 if sample_hook_target is None: 

1656 sample_hook_target = hook_target 

1657 feature_migration.warn( 

1658 f"TODO: MANUAL MIGRATION required for hook target {hook_target}" 

1659 ) 

1660 else: 

1661 feature_migration.warn( 

1662 f"TODO: MANUAL MIGRATION required for hook target {hook_target}. Please see {advice}" 

1663 f" for migration advice." 

1664 ) 

1665 if ( 

1666 feature_migration.warnings 

1667 and "dh-hook-targets" not in migration_request.acceptable_migration_issues 

1668 and sample_hook_target is not None 

1669 ): 

1670 raise UnsupportedFeature( 

1671 f"The debian/rules file contains one or more non empty dh hook targets that will not" 

1672 f" be run with the requested debputy dh sequence with no known migration advice. One of these would be" 

1673 f" {sample_hook_target}.", 

1674 ["dh-hook-targets"], 

1675 ) 

1676 

1677 

1678def detect_maintscript_needing_conversion( 

1679 migration_request: MigrationRequest, 

1680 feature_migration: FeatureMigration, 

1681) -> None: 

1682 feature_migration.tagline = "Known unsupported features" 

1683 for dctrl_bin in migration_request.all_packages: 

1684 relevant_scripts: list[str] = sorted( 

1685 SUPPORTED_UDEB_SCRIPTS if dctrl_bin.is_udeb else DPKG_DEB_CONTROL_SCRIPTS 

1686 ) 

1687 for script in relevant_scripts: 

1688 dh_config_file = dhe_pkgfile( 

1689 migration_request.debian_dir, 

1690 dctrl_bin, 

1691 script, 

1692 ) 

1693 if dh_config_file: 

1694 feature_migration.warn( 

1695 f"TODO: MANUAL MIGRATION {dh_config_file.fs_path} needs porting" 

1696 f" (keywords include `maintscript-snippets` & `clean-after-removal` depending on use-case)" 

1697 ) 

1698 

1699 

1700def detect_unsupported_zz_debputy_features( 

1701 migration_request: MigrationRequest, 

1702 feature_migration: FeatureMigration, 

1703) -> None: 

1704 feature_migration.tagline = "Known unsupported features" 

1705 

1706 for unsupported_config in UNSUPPORTED_DH_CONFIGS_AND_TOOLS_FOR_ZZ_DEBPUTY: 

1707 _unsupported_debhelper_config_file( 

1708 migration_request, 

1709 unsupported_config, 

1710 feature_migration, 

1711 ) 

1712 

1713 

1714def detect_obsolete_substvars( 

1715 migration_request: MigrationRequest, 

1716 feature_migration: FeatureMigration, 

1717) -> None: 

1718 feature_migration.tagline = ( 

1719 "Check for obsolete ${foo:var} variables in debian/control" 

1720 ) 

1721 ctrl_file = migration_request.debian_dir.get("control") 

1722 if not ctrl_file: 1722 ↛ 1723line 1722 didn't jump to line 1723 because the condition on line 1722 was never true

1723 feature_migration.warn( 

1724 "Cannot find debian/control. Detection of obsolete substvars could not be performed." 

1725 ) 

1726 return 

1727 with ctrl_file.open() as fd: 

1728 ctrl = list(Deb822.iter_paragraphs(fd)) 

1729 

1730 relationship_fields = dpkg_field_list_pkg_dep() 

1731 relationship_fields_lc = frozenset(x.lower() for x in relationship_fields) 

1732 

1733 for p in ctrl[1:]: 

1734 seen_obsolete_relationship_substvars = set() 

1735 obsolete_fields = set() 

1736 is_essential = p.get("Essential") == "yes" 

1737 for df in relationship_fields: 

1738 field: str | None = p.get(df) 

1739 if field is None: 

1740 continue 

1741 df_lc = df.lower() 

1742 number_of_relations = 0 

1743 obsolete_substvars_in_field = set() 

1744 for d in (d.strip() for d in field.strip().split(",")): 

1745 if not d: 

1746 continue 

1747 number_of_relations += 1 

1748 if not d.startswith("${"): 

1749 continue 

1750 try: 

1751 end_idx = d.index("}") 

1752 except ValueError: 

1753 continue 

1754 substvar_name = d[2:end_idx] 

1755 if ":" not in substvar_name: 1755 ↛ 1756line 1755 didn't jump to line 1756 because the condition on line 1755 was never true

1756 continue 

1757 _, field = substvar_name.rsplit(":", 1) 

1758 field_lc = field.lower() 

1759 if field_lc not in relationship_fields_lc: 1759 ↛ 1760line 1759 didn't jump to line 1760 because the condition on line 1759 was never true

1760 continue 

1761 is_obsolete = field_lc == df_lc 

1762 if ( 

1763 not is_obsolete 

1764 and is_essential 

1765 and substvar_name.lower() == "shlibs:depends" 

1766 and df_lc == "pre-depends" 

1767 ): 

1768 is_obsolete = True 

1769 

1770 if is_obsolete: 

1771 obsolete_substvars_in_field.add(d) 

1772 

1773 if number_of_relations == len(obsolete_substvars_in_field): 

1774 obsolete_fields.add(df) 

1775 else: 

1776 seen_obsolete_relationship_substvars.update(obsolete_substvars_in_field) 

1777 

1778 package = p.get("Package", "(Missing package name!?)") 

1779 fo = feature_migration.fo 

1780 if obsolete_fields: 

1781 fields = ", ".join(obsolete_fields) 

1782 feature_migration.warn( 

1783 f"The following relationship fields can be removed from {package}: {fields}." 

1784 f" (The content in them would be applied automatically. Note: {fo.bts('1067653')})" 

1785 ) 

1786 if seen_obsolete_relationship_substvars: 

1787 v = ", ".join(sorted(seen_obsolete_relationship_substvars)) 

1788 feature_migration.warn( 

1789 f"The following relationship substitution variables can be removed from {package}: {v}" 

1790 f" (Note: {fo.bts('1067653')})" 

1791 ) 

1792 

1793 

1794def detect_dh_addons_zz_debputy_rrr( 

1795 migration_request: MigrationRequest, 

1796 feature_migration: FeatureMigration, 

1797) -> None: 

1798 feature_migration.tagline = "Check for dh-sequence-addons" 

1799 r = read_dh_addon_sequences(migration_request.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 

1806 

1807 bd_sequences, dr_sequences, _ = r 

1808 

1809 remaining_sequences = bd_sequences | dr_sequences 

1810 saw_dh_debputy = "zz-debputy-rrr" in remaining_sequences 

1811 

1812 if not saw_dh_debputy: 

1813 feature_migration.warn("Missing Build-Depends on dh-sequence-zz-debputy-rrr") 

1814 

1815 

1816def detect_dh_addons_with_full_integration( 

1817 _migration_request: MigrationRequest, 

1818 feature_migration: FeatureMigration, 

1819) -> None: 

1820 feature_migration.tagline = "Check for dh-sequence-addons and Build-Depends" 

1821 feature_migration.warn( 

1822 "TODO: Not implemented: Please remove any dh-sequence Build-Dependency" 

1823 ) 

1824 feature_migration.warn( 

1825 "TODO: Not implemented: Please ensure there is a Build-Dependency on `debputy (>= 0.1.45~)" 

1826 ) 

1827 feature_migration.warn( 

1828 "TODO: Not implemented: Please ensure there is a Build-Dependency on `dpkg-dev (>= 1.22.7~)" 

1829 ) 

1830 

1831 

1832def detect_dh_addons_with_zz_integration( 

1833 migration_request: MigrationRequest, 

1834 feature_migration: FeatureMigration, 

1835) -> None: 

1836 feature_migration.tagline = "Check for dh-sequence-addons" 

1837 r = read_dh_addon_sequences(migration_request.debian_dir) 

1838 acceptable_migration_issues = migration_request.acceptable_migration_issues 

1839 if r is None: 

1840 feature_migration.warn( 

1841 "Cannot find debian/control. Detection of unsupported/missing dh-sequence addon" 

1842 " could not be performed. Please ensure the package will Build-Depend on dh-sequence-zz-debputy" 

1843 " and not rely on any other debhelper sequence addons except those debputy explicitly supports." 

1844 ) 

1845 return 

1846 

1847 assert migration_request.migration_target != INTEGRATION_MODE_FULL 

1848 

1849 bd_sequences, dr_sequences, _ = r 

1850 

1851 remaining_sequences = bd_sequences | dr_sequences 

1852 saw_dh_debputy = ( 

1853 "debputy" in remaining_sequences or "zz-debputy" in remaining_sequences 

1854 ) 

1855 saw_zz_debputy = "zz-debputy" in remaining_sequences 

1856 must_use_zz_debputy = False 

1857 remaining_sequences -= SUPPORTED_DH_ADDONS_WITH_ZZ_DEBPUTY 

1858 for sequence in remaining_sequences & DH_ADDONS_TO_PLUGINS.keys(): 

1859 migration = DH_ADDONS_TO_PLUGINS[sequence] 

1860 feature_migration.require_plugin(migration.debputy_plugin) 

1861 if migration.remove_dh_sequence: 1861 ↛ 1862line 1861 didn't jump to line 1862 because the condition on line 1861 was never true

1862 if migration.must_use_zz_debputy: 

1863 must_use_zz_debputy = True 

1864 if sequence in bd_sequences: 

1865 feature_migration.warn( 

1866 f"TODO: MANUAL MIGRATION - Remove build-dependency on dh-sequence-{sequence}" 

1867 f" (replaced by debputy-plugin-{migration.debputy_plugin})" 

1868 ) 

1869 else: 

1870 feature_migration.warn( 

1871 f"TODO: MANUAL MIGRATION - Remove --with {sequence} from dh in d/rules" 

1872 f" (replaced by debputy-plugin-{migration.debputy_plugin})" 

1873 ) 

1874 

1875 remaining_sequences -= DH_ADDONS_TO_PLUGINS.keys() 

1876 

1877 alt_key = "unsupported-dh-sequences" 

1878 for sequence in remaining_sequences & DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY: 1878 ↛ 1879line 1878 didn't jump to line 1879 because the loop on line 1878 never started

1879 if sequence in bd_sequences: 

1880 feature_migration.warn( 

1881 f"TODO: MANUAL MIGRATION - Remove build dependency on dh-sequence-{sequence}" 

1882 ) 

1883 else: 

1884 feature_migration.warn( 

1885 f"TODO: MANUAL MIGRATION - Remove --with {sequence} from dh in d/rules" 

1886 ) 

1887 

1888 remaining_sequences -= DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY 

1889 

1890 for sequence in remaining_sequences: 

1891 key = f"unsupported-dh-sequence-{sequence}" 

1892 msg = f'The dh addon "{sequence}" is not known to work with dh-debputy and might malfunction' 

1893 if ( 

1894 key not in acceptable_migration_issues 

1895 and alt_key not in acceptable_migration_issues 

1896 ): 

1897 raise UnsupportedFeature(msg, [key, alt_key]) 

1898 feature_migration.warn(msg) 

1899 

1900 if not saw_dh_debputy: 

1901 feature_migration.warn("Missing Build-Depends on dh-sequence-zz-debputy") 

1902 elif must_use_zz_debputy and not saw_zz_debputy: 1902 ↛ 1903line 1902 didn't jump to line 1903 because the condition on line 1902 was never true

1903 feature_migration.warn( 

1904 "Please use the zz-debputy sequence rather than the debputy (needed due to dh add-on load order)" 

1905 ) 

1906 

1907 

1908def _rename_file_if_exists( 

1909 debian_dir: VirtualPath, 

1910 source: str, 

1911 dest: str, 

1912 feature_migration: FeatureMigration, 

1913) -> None: 

1914 source_path = debian_dir.get(source) 

1915 dest_path = debian_dir.get(dest) 

1916 spath = ( 

1917 source_path.path 

1918 if source_path is not None 

1919 else os.path.join(debian_dir.path, source) 

1920 ) 

1921 dpath = ( 

1922 dest_path.path if dest_path is not None else os.path.join(debian_dir.path, dest) 

1923 ) 

1924 if source_path is not None and source_path.is_file: 

1925 if dest_path is not None: 

1926 if not dest_path.is_file: 

1927 feature_migration.warnings.append( 

1928 f'TODO: MANUAL MIGRATION - there is a "{spath}" (file) and "{dpath}" (not a file).' 

1929 f' The migration wanted to replace "{spath}" with "{dpath}", but since "{dpath}" is not' 

1930 " a file, this step is left as a manual migration." 

1931 ) 

1932 return 

1933 if ( 

1934 subprocess.call(["cmp", "-s", source_path.fs_path, dest_path.fs_path]) 

1935 != 0 

1936 ): 

1937 feature_migration.warnings.append( 

1938 f'TODO: MANUAL MIGRATION - there is a "{source_path.path}" and "{dest_path.path}"' 

1939 f" file. Normally these files are for the same package and there would only be one of" 

1940 f" them. In this case, they both exist but their content differs. Be advised that" 

1941 f' debputy tool will use the "{dest_path.path}".' 

1942 ) 

1943 else: 

1944 feature_migration.remove_on_success(source_path.fs_path) 

1945 else: 

1946 feature_migration.rename_on_success( 

1947 source_path.fs_path, 

1948 os.path.join(debian_dir.fs_path, dest), 

1949 ) 

1950 elif source_path is not None: 1950 ↛ exitline 1950 didn't return from function '_rename_file_if_exists' because the condition on line 1950 was always true

1951 feature_migration.warnings.append( 

1952 f'TODO: MANUAL MIGRATION - The migration would normally have renamed "{spath}" to "{dpath}".' 

1953 f' However, the migration assumed "{spath}" would be a file and it is not. Therefore, this step' 

1954 " as a manual migration." 

1955 ) 

1956 

1957 

1958def _find_dh_config_file_for_any_pkg( 

1959 migration_request: MigrationRequest, 

1960 unsupported_config: UnsupportedDHConfig, 

1961) -> Iterable[VirtualPath]: 

1962 for dctrl_bin in migration_request.all_packages: 

1963 dh_config_file = dhe_pkgfile( 

1964 migration_request.debian_dir, 

1965 dctrl_bin, 

1966 unsupported_config.dh_config_basename, 

1967 bug_950723_prefix_matching=unsupported_config.bug_950723_prefix_matching, 

1968 ) 

1969 if dh_config_file is not None: 

1970 yield dh_config_file 

1971 

1972 

1973def _unsupported_debhelper_config_file( 

1974 migration_request: MigrationRequest, 

1975 unsupported_config: UnsupportedDHConfig, 

1976 feature_migration: FeatureMigration, 

1977) -> None: 

1978 dh_config_files = list( 

1979 _find_dh_config_file_for_any_pkg(migration_request, unsupported_config) 

1980 ) 

1981 if not dh_config_files: 

1982 return 

1983 dh_tool = unsupported_config.dh_tool 

1984 basename = unsupported_config.dh_config_basename 

1985 file_stem = ( 

1986 f"@{basename}" if unsupported_config.bug_950723_prefix_matching else basename 

1987 ) 

1988 dh_config_file = dh_config_files[0] 

1989 if unsupported_config.is_missing_migration: 

1990 feature_migration.warn( 

1991 f'Missing migration support for the "{dh_config_file.path}" debhelper config file' 

1992 f" (used by {dh_tool}). Manual migration may be feasible depending on the exact features" 

1993 " required." 

1994 ) 

1995 return 

1996 primary_key = f"unsupported-dh-config-file-{file_stem}" 

1997 secondary_key = "any-unsupported-dh-config-file" 

1998 if ( 

1999 primary_key not in migration_request.acceptable_migration_issues 

2000 and secondary_key not in migration_request.acceptable_migration_issues 

2001 ): 

2002 msg = ( 

2003 f'The "{dh_config_file.path}" debhelper config file (used by {dh_tool} is currently not' 

2004 " supported by debputy." 

2005 ) 

2006 raise UnsupportedFeature( 

2007 msg, 

2008 [primary_key, secondary_key], 

2009 ) 

2010 for dh_config_file in dh_config_files: 

2011 feature_migration.warn( 

2012 f'TODO: MANUAL MIGRATION - Use of unsupported "{dh_config_file.path}" file (used by {dh_tool})' 

2013 )