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

775 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-12-28 16:12 +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 import DEBPUTY_DOC_ROOT_DIR 

18from debputy.architecture_support import dpkg_architecture_table 

19from debputy.deb_packaging_support import dpkg_field_list_pkg_dep 

20from debputy.dh.debhelper_emulation import ( 

21 dhe_filedoublearray, 

22 DHConfigFileLine, 

23 dhe_pkgfile, 

24) 

25from debputy.dh.dh_assistant import ( 

26 read_dh_addon_sequences, 

27) 

28from debputy.dh_migration.models import ( 

29 ConflictingChange, 

30 FeatureMigration, 

31 UnsupportedFeature, 

32 AcceptableMigrationIssues, 

33 DHMigrationSubstitution, 

34 MigrationRequest, 

35) 

36from debputy.highlevel_manifest import ( 

37 MutableYAMLSymlink, 

38 HighLevelManifest, 

39 MutableYAMLConffileManagementItem, 

40 AbstractMutableYAMLInstallRule, 

41) 

42from debputy.installations import MAN_GUESS_FROM_BASENAME, MAN_GUESS_LANG_FROM_PATH 

43from debputy.packages import BinaryPackage 

44from debputy.plugin.api import VirtualPath 

45from debputy.plugin.api.spec import ( 

46 INTEGRATION_MODE_DH_DEBPUTY_RRR, 

47 INTEGRATION_MODE_DH_DEBPUTY, 

48 DebputyIntegrationMode, 

49 INTEGRATION_MODE_FULL, 

50) 

51from debputy.util import ( 

52 _error, 

53 PKGVERSION_REGEX, 

54 PKGNAME_REGEX, 

55 _normalize_path, 

56 assume_not_none, 

57 has_glob_magic, 

58) 

59 

60 

61class ContainsEverything: 

62 

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

64 return True 

65 

66 

67# Align with debputy.py 

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

69 INTEGRATION_MODE_DH_DEBPUTY_RRR: frozenset( 

70 { 

71 "dh_fixperms", 

72 "dh_shlibdeps", 

73 "dh_gencontrol", 

74 "dh_md5sums", 

75 "dh_builddeb", 

76 } 

77 ), 

78 INTEGRATION_MODE_DH_DEBPUTY: frozenset( 

79 { 

80 "dh_install", 

81 "dh_installdocs", 

82 "dh_installchangelogs", 

83 "dh_installexamples", 

84 "dh_installman", 

85 "dh_installcatalogs", 

86 "dh_installcron", 

87 "dh_installdebconf", 

88 "dh_installemacsen", 

89 "dh_installifupdown", 

90 "dh_installinfo", 

91 "dh_installinit", 

92 "dh_installsysusers", 

93 "dh_installtmpfiles", 

94 "dh_installsystemd", 

95 "dh_installsystemduser", 

96 "dh_installmenu", 

97 "dh_installmime", 

98 "dh_installmodules", 

99 "dh_installlogcheck", 

100 "dh_installlogrotate", 

101 "dh_installpam", 

102 "dh_installppp", 

103 "dh_installudev", 

104 "dh_installgsettings", 

105 "dh_installinitramfs", 

106 "dh_installalternatives", 

107 "dh_bugfiles", 

108 "dh_ucf", 

109 "dh_lintian", 

110 "dh_icons", 

111 "dh_usrlocal", 

112 "dh_perl", 

113 "dh_link", 

114 "dh_installwm", 

115 "dh_installxfonts", 

116 "dh_strip_nondeterminism", 

117 "dh_compress", 

118 "dh_fixperms", 

119 "dh_dwz", 

120 "dh_strip", 

121 "dh_makeshlibs", 

122 "dh_shlibdeps", 

123 "dh_missing", 

124 "dh_installdeb", 

125 "dh_gencontrol", 

126 "dh_md5sums", 

127 "dh_builddeb", 

128 } 

129 ), 

130 INTEGRATION_MODE_FULL: ContainsEverything(), 

131} 

132 

133_GS_DOC = f"{DEBPUTY_DOC_ROOT_DIR}/GETTING-STARTED-WITH-dh-debputy.md" 

134MIGRATION_AID_FOR_OVERRIDDEN_COMMANDS = { 

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

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

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

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

139} 

140 

141 

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

143class UnsupportedDHConfig: 

144 dh_config_basename: str 

145 dh_tool: str 

146 bug_950723_prefix_matching: bool = False 

147 is_missing_migration: bool = False 

148 

149 

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

151class DHSequenceMigration: 

152 debputy_plugin: str 

153 remove_dh_sequence: bool = True 

154 must_use_zz_debputy: bool = False 

155 

156 

157UNSUPPORTED_DH_CONFIGS_AND_TOOLS_FOR_ZZ_DEBPUTY = [ 

158 UnsupportedDHConfig("config", "dh_installdebconf"), 

159 UnsupportedDHConfig("templates", "dh_installdebconf"), 

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

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

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

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

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

165 UnsupportedDHConfig("upstart", "dh_installinit"), 

166 # dh_installsystemduser 

167 UnsupportedDHConfig( 

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

169 ), 

170 UnsupportedDHConfig( 

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

172 ), 

173 UnsupportedDHConfig( 

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

175 ), 

176 UnsupportedDHConfig( 

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

178 ), 

179 UnsupportedDHConfig( 

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

181 ), 

182 UnsupportedDHConfig( 

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

184 ), 

185 UnsupportedDHConfig( 

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

187 ), 

188 UnsupportedDHConfig( 

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

190 ), 

191 UnsupportedDHConfig( 

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

193 ), 

194 UnsupportedDHConfig( 

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

196 ), 

197 UnsupportedDHConfig("menu", "dh_installmenu"), 

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

199 UnsupportedDHConfig("ucf", "dh_ucf"), 

200 UnsupportedDHConfig("wm", "dh_installwm"), 

201 UnsupportedDHConfig("triggers", "dh_installdeb"), 

202 UnsupportedDHConfig("postinst", "dh_installdeb"), 

203 UnsupportedDHConfig("postrm", "dh_installdeb"), 

204 UnsupportedDHConfig("preinst", "dh_installdeb"), 

205 UnsupportedDHConfig("prerm", "dh_installdeb"), 

206 UnsupportedDHConfig("menutest", "dh_installdeb"), 

207 UnsupportedDHConfig("isinstallable", "dh_installdeb"), 

208] 

209SUPPORTED_DH_ADDONS_WITH_ZZ_DEBPUTY = frozenset( 

210 { 

211 # debputy's own 

212 "debputy", 

213 "zz-debputy", 

214 # debhelper provided sequences that should work. 

215 "single-binary", 

216 } 

217) 

218DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY = frozenset( 

219 [ 

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

221 "zz-debputy-rrr", 

222 # Sequences debputy directly replaces 

223 "dwz", 

224 "elf-tools", 

225 "installinitramfs", 

226 "installsysusers", 

227 "doxygen", 

228 # Sequences that are embedded fully into debputy 

229 "bash-completion", 

230 "shell-completions", 

231 "sodeps", 

232 "builtusing", 

233 ] 

234) 

235DH_ADDONS_TO_PLUGINS = { 

236 "gnome": DHSequenceMigration( 

237 "gnome", 

238 # The sequence still provides a command for the clean sequence 

239 remove_dh_sequence=False, 

240 must_use_zz_debputy=True, 

241 ), 

242 "grantlee": DHSequenceMigration( 

243 "grantlee", 

244 remove_dh_sequence=True, 

245 must_use_zz_debputy=True, 

246 ), 

247 "numpy3": DHSequenceMigration( 

248 "numpy3", 

249 # The sequence provides (build-time) dependencies that we cannot provide 

250 remove_dh_sequence=False, 

251 must_use_zz_debputy=True, 

252 ), 

253 "perl-openssl": DHSequenceMigration( 

254 "perl-openssl", 

255 # The sequence provides (build-time) dependencies that we cannot provide 

256 remove_dh_sequence=False, 

257 must_use_zz_debputy=True, 

258 ), 

259} 

260 

261 

262def _dh_config_file( 

263 debian_dir: VirtualPath, 

264 dctrl_bin: BinaryPackage, 

265 basename: str, 

266 helper_name: str, 

267 acceptable_migration_issues: AcceptableMigrationIssues, 

268 feature_migration: FeatureMigration, 

269 manifest: HighLevelManifest, 

270 support_executable_files: bool = False, 

271 allow_dh_exec_rename: bool = False, 

272 pkgfile_lookup: bool = True, 

273 remove_on_migration: bool = True, 

274) -> tuple[None, None] | tuple[VirtualPath, Iterable[DHConfigFileLine]]: 

275 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

276 dh_config_file = ( 

277 dhe_pkgfile(debian_dir, dctrl_bin, basename) 

278 if pkgfile_lookup 

279 else debian_dir.get(basename) 

280 ) 

281 if dh_config_file is None or dh_config_file.is_dir: 

282 return None, None 

283 if dh_config_file.is_executable and not support_executable_files: 

284 primary_key = f"executable-{helper_name}-config" 

285 if ( 

286 primary_key in acceptable_migration_issues 

287 or "any-executable-dh-configs" in acceptable_migration_issues 

288 ): 

289 feature_migration.warn( 

290 f'TODO: MANUAL MIGRATION of executable dh config "{dh_config_file}" is required.' 

291 ) 

292 return None, None 

293 raise UnsupportedFeature( 

294 f"Executable configuration files not supported (found: {dh_config_file}).", 

295 [primary_key, "any-executable-dh-configs"], 

296 ) 

297 

298 if remove_on_migration: 

299 feature_migration.remove_on_success(dh_config_file.fs_path) 

300 substitution = DHMigrationSubstitution( 

301 dpkg_architecture_table(), 

302 acceptable_migration_issues, 

303 feature_migration, 

304 mutable_manifest, 

305 ) 

306 content = dhe_filedoublearray( 

307 dh_config_file, 

308 substitution, 

309 allow_dh_exec_rename=allow_dh_exec_rename, 

310 ) 

311 return dh_config_file, content 

312 

313 

314def _validate_rm_mv_conffile( 

315 package: str, 

316 config_line: DHConfigFileLine, 

317) -> tuple[str, str, str | None, str | None, str | None]: 

318 cmd, *args = config_line.tokens 

319 if "--" in config_line.tokens: 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true

320 raise ValueError( 

321 f'The maintscripts file "{config_line.config_file.path}" for {package} includes a "--" in line' 

322 f" {config_line.line_no}. The offending line is: {config_line.original_line}" 

323 ) 

324 if cmd == "rm_conffile": 

325 min_args = 1 

326 max_args = 3 

327 else: 

328 min_args = 2 

329 max_args = 4 

330 if len(args) > max_args or len(args) < min_args: 330 ↛ 331line 330 didn't jump to line 331 because the condition on line 330 was never true

331 raise ValueError( 

332 f'The "{cmd}" command takes at least {min_args} and at most {max_args} arguments. However,' 

333 f' in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), there' 

334 f" are {len(args)} arguments. The offending line is: {config_line.original_line}" 

335 ) 

336 

337 obsolete_conffile = args[0] 

338 new_conffile = args[1] if cmd == "mv_conffile" else None 

339 prior_version = args[min_args] if len(args) > min_args else None 

340 owning_package = args[min_args + 1] if len(args) > min_args + 1 else None 

341 if not obsolete_conffile.startswith("/"): 341 ↛ 342line 341 didn't jump to line 342 because the condition on line 341 was never true

342 raise ValueError( 

343 f'The (old-)conffile parameter for {cmd} must be absolute (i.e., start with "/"). However,' 

344 f' in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), it was specified' 

345 f' as "{obsolete_conffile}". The offending line is: {config_line.original_line}' 

346 ) 

347 if new_conffile is not None and not new_conffile.startswith("/"): 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true

348 raise ValueError( 

349 f'The new-conffile parameter for {cmd} must be absolute (i.e., start with "/"). However,' 

350 f' in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), it was specified' 

351 f' as "{new_conffile}". The offending line is: {config_line.original_line}' 

352 ) 

353 if prior_version is not None and not PKGVERSION_REGEX.fullmatch(prior_version): 353 ↛ 354line 353 didn't jump to line 354 because the condition on line 353 was never true

354 raise ValueError( 

355 f"The prior-version parameter for {cmd} must be a valid package version (i.e., match" 

356 f' {PKGVERSION_REGEX}). However, in "{config_line.config_file.path}" line {config_line.line_no}' 

357 f' (for {package}), it was specified as "{prior_version}". The offending line is:' 

358 f" {config_line.original_line}" 

359 ) 

360 if owning_package is not None and not PKGNAME_REGEX.fullmatch(owning_package): 360 ↛ 361line 360 didn't jump to line 361 because the condition on line 360 was never true

361 raise ValueError( 

362 f"The package parameter for {cmd} must be a valid package name (i.e., match {PKGNAME_REGEX})." 

363 f' However, in "{config_line.config_file.path}" line {config_line.line_no} (for {package}), it' 

364 f' was specified as "{owning_package}". The offending line is: {config_line.original_line}' 

365 ) 

366 return cmd, obsolete_conffile, new_conffile, prior_version, owning_package 

367 

368 

369_BASH_COMPLETION_RE = re.compile( 

370 r""" 

371 (^|[|&;])\s*complete.*-[A-Za-z].* 

372 | \$\(.*\) 

373 | \s*compgen.*-[A-Za-z].* 

374 | \s*if.*;.*then/ 

375""", 

376 re.VERBOSE, 

377) 

378 

379 

380def migrate_bash_completion( 

381 migration_request: MigrationRequest, 

382 feature_migration: FeatureMigration, 

383) -> None: 

384 feature_migration.tagline = "dh_bash-completion files" 

385 is_single_binary = migration_request.is_single_binary_package 

386 manifest = migration_request.manifest 

387 debian_dir = migration_request.debian_dir 

388 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

389 installations = mutable_manifest.installations(create_if_absent=False) 

390 

391 for dctrl_bin in migration_request.all_packages: 

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

393 if dh_file is None: 

394 continue 

395 is_bash_completion_file = False 

396 with dh_file.open() as fd: 

397 for line in fd: 

398 line = line.strip() 

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

400 continue 

401 if _BASH_COMPLETION_RE.search(line): 

402 is_bash_completion_file = True 

403 break 

404 if not is_bash_completion_file: 

405 _, content = _dh_config_file( 

406 debian_dir, 

407 dctrl_bin, 

408 "bash-completion", 

409 "dh_bash-completion", 

410 migration_request.acceptable_migration_issues, 

411 feature_migration, 

412 manifest, 

413 support_executable_files=True, 

414 ) 

415 else: 

416 content = None 

417 

418 if content: 

419 install_dest_sources: list[str] = [] 

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

421 for dhe_line in content: 

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

423 raise UnsupportedFeature( 

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

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

426 ) 

427 source = dhe_line.tokens[0] 

428 dest_basename = ( 

429 dhe_line.tokens[1] 

430 if len(dhe_line.tokens) > 1 

431 else os.path.basename(source) 

432 ) 

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

434 if dctrl_bin.name != dest_basename: 

435 dest_path = ( 

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

437 ) 

438 else: 

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

440 feature_migration.rename_on_success(source, dest_path) 

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

442 install_dest_sources.append(source) 

443 else: 

444 install_as_rules.append((source, dest_basename)) 

445 

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

447 sources: list[str] | str = ( 

448 install_dest_sources 

449 if len(install_dest_sources) > 1 

450 else install_dest_sources[0] 

451 ) 

452 installations.append( 

453 AbstractMutableYAMLInstallRule.install_dest( 

454 sources=sources, 

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

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

457 ) 

458 ) 

459 

460 for source, dest_basename in install_as_rules: 

461 installations.append( 

462 AbstractMutableYAMLInstallRule.install_as( 

463 source=source, 

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

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

466 ) 

467 ) 

468 

469 

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

471 

472 

473def migrate_shell_completions( 

474 migration_request: MigrationRequest, 

475 feature_migration: FeatureMigration, 

476) -> None: 

477 feature_migration.tagline = "dh_shell_completions files" 

478 manifest = migration_request.manifest 

479 debian_dir = migration_request.debian_dir 

480 is_single_binary = migration_request.is_single_binary_package 

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

487 

488 for completion, dctrl_bin in product(completions, migration_request.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 

505 

506 _, content = _dh_config_file( 

507 debian_dir, 

508 dctrl_bin, 

509 f"{completion}-completions", 

510 "dh_shell_completions", 

511 migration_request.acceptable_migration_issues, 

512 feature_migration, 

513 manifest, 

514 remove_on_migration=True, 

515 ) 

516 

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)) 

542 

543 completion_dir_variable = ( 

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

545 ) 

546 

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 ) 

560 

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 ) 

569 

570 

571def migrate_dh_builtusing( 

572 migration_request: MigrationRequest, 

573 feature_migration: FeatureMigration, 

574) -> None: 

575 feature_migration.tagline = "dh_builtusing configuration" 

576 for pkg in migration_request.all_packages: 

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

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

579 

580 if ( 

581 "${dh-builtusing:" in built_using 

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

583 ): 

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

585 feature_migration.warn( 

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

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

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

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

590 ) 

591 

592 

593def migrate_dh_installsystemd_files( 

594 migration_request: MigrationRequest, 

595 feature_migration: FeatureMigration, 

596) -> None: 

597 debian_dir = migration_request.debian_dir 

598 feature_migration.tagline = "dh_installsystemd files" 

599 for dctrl_bin in migration_request.all_packages: 

600 for stem in [ 

601 "path", 

602 "service", 

603 "socket", 

604 "target", 

605 "timer", 

606 ]: 

607 pkgfile = dhe_pkgfile( 

608 debian_dir, dctrl_bin, stem, bug_950723_prefix_matching=True 

609 ) 

610 if not pkgfile: 

611 continue 

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

613 raise UnsupportedFeature( 

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

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

616 ) 

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

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

619 feature_migration.rename_on_success( 

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

621 ) 

622 

623 

624def migrate_clean_file( 

625 migration_request: MigrationRequest, 

626 feature_migration: FeatureMigration, 

627) -> None: 

628 feature_migration.tagline = "debian/clean" 

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

630 if clean_file is None: 

631 return 

632 

633 mutable_manifest = assume_not_none(migration_request.manifest.mutable_manifest) 

634 

635 substitution = DHMigrationSubstitution( 

636 dpkg_architecture_table(), 

637 migration_request.acceptable_migration_issues, 

638 feature_migration, 

639 mutable_manifest, 

640 ) 

641 content = dhe_filedoublearray( 

642 clean_file, 

643 substitution, 

644 ) 

645 

646 remove_during_clean_rules = mutable_manifest.remove_during_clean( 

647 create_if_absent=False 

648 ) 

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

650 rules_before = len(remove_during_clean_rules) 

651 remove_during_clean_rules.extend(tokens) 

652 rules_after = len(remove_during_clean_rules) 

653 feature_migration.successful_manifest_changes += rules_after - rules_before 

654 feature_migration.remove_on_success(clean_file.fs_path) 

655 

656 

657def migrate_maintscript( 

658 migration_request: MigrationRequest, 

659 feature_migration: FeatureMigration, 

660) -> None: 

661 feature_migration.tagline = "dh_installdeb files" 

662 manifest = migration_request.manifest 

663 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

664 for dctrl_bin in migration_request.all_packages: 

665 mainscript_file, content = _dh_config_file( 

666 migration_request.debian_dir, 

667 dctrl_bin, 

668 "maintscript", 

669 "dh_installdeb", 

670 migration_request.acceptable_migration_issues, 

671 feature_migration, 

672 manifest, 

673 ) 

674 

675 if mainscript_file is None: 

676 continue 

677 assert content is not None 

678 

679 package_definition = mutable_manifest.package(dctrl_bin.name) 

680 conffiles = { 

681 it.obsolete_conffile: it 

682 for it in package_definition.conffile_management_items() 

683 } 

684 seen_conffiles = set() 

685 

686 for dhe_line in content: 

687 cmd = dhe_line.tokens[0] 

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

689 raise UnsupportedFeature( 

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

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

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

693 ) 

694 

695 try: 

696 ( 

697 _, 

698 obsolete_conffile, 

699 new_conffile, 

700 prior_to_version, 

701 owning_package, 

702 ) = _validate_rm_mv_conffile(dctrl_bin.name, dhe_line) 

703 except ValueError as e: 

704 _error( 

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

706 ) 

707 

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

709 raise ConflictingChange( 

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

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

712 ) 

713 seen_conffiles.add(obsolete_conffile) 

714 

715 if cmd == "rm_conffile": 

716 item = MutableYAMLConffileManagementItem.rm_conffile( 

717 obsolete_conffile, 

718 prior_to_version, 

719 owning_package, 

720 ) 

721 else: 

722 assert cmd == "mv_conffile" 

723 item = MutableYAMLConffileManagementItem.mv_conffile( 

724 obsolete_conffile, 

725 assume_not_none(new_conffile), 

726 prior_to_version, 

727 owning_package, 

728 ) 

729 

730 existing_def = conffiles.get(item.obsolete_conffile) 

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

732 if not ( 

733 item.command == existing_def.command 

734 and item.new_conffile == existing_def.new_conffile 

735 and item.prior_to_version == existing_def.prior_to_version 

736 and item.owning_package == existing_def.owning_package 

737 ): 

738 raise ConflictingChange( 

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

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

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

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

743 ) 

744 continue 

745 

746 package_definition.add_conffile_management(item) 

747 feature_migration.successful_manifest_changes += 1 

748 

749 

750@dataclasses.dataclass(slots=True) 

751class SourcesAndConditional: 

752 dest_dir: str | None = None 

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

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

755 

756 

757def _raise_on_unsupported_path( 

758 p: str, 

759 path: VirtualPath, 

760 line_no: int, 

761) -> str: 

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

763 _error( 

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

765 ) 

766 return p 

767 

768 

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

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

771 return p[11:] 

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

773 pruned = p[3:] 

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

775 _raise_on_unsupported_path(p, path, line_no) 

776 _error( 

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

778 ) 

779 return f"debian/{pruned}" 

780 

781 return p 

782 

783 

784def migrate_install_file( 

785 migration_request: MigrationRequest, 

786 feature_migration: FeatureMigration, 

787) -> None: 

788 feature_migration.tagline = "dh_install config files" 

789 manifest = migration_request.manifest 

790 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

791 installations = mutable_manifest.installations(create_if_absent=False) 

792 priority_lines = [] 

793 remaining_install_lines = [] 

794 warn_about_fixmes_in_dest_dir = False 

795 

796 is_single_binary = migration_request.is_single_binary_package 

797 

798 for dctrl_bin in migration_request.all_packages: 

799 install_file, content = _dh_config_file( 

800 migration_request.debian_dir, 

801 dctrl_bin, 

802 "install", 

803 "dh_install", 

804 migration_request.acceptable_migration_issues, 

805 feature_migration, 

806 manifest, 

807 support_executable_files=True, 

808 allow_dh_exec_rename=True, 

809 ) 

810 if not install_file or not content: 

811 continue 

812 current_sources = [] 

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

814 {} 

815 ) 

816 install_as_rules = [] 

817 multi_dest = collections.defaultdict(list) 

818 seen_sources = set() 

819 multi_dest_sources: set[str] = set() 

820 

821 for dhe_line in content: 

822 special_rule = None 

823 if "=>" in dhe_line.tokens: 

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

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

826 path = _strip_d_tmp( 

827 _normalize_path( 

828 dhe_line.tokens[1], 

829 with_prefix=False, 

830 allow_and_keep_upward_segments=True, 

831 ), 

832 dhe_line.config_file, 

833 dhe_line.line_no, 

834 ) 

835 special_rule = AbstractMutableYAMLInstallRule.install_dest( 

836 path, 

837 dctrl_bin.name if not is_single_binary else None, 

838 dest_dir=None, 

839 when=dhe_line.conditional(), 

840 ) 

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

842 _error( 

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

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

845 ) 

846 else: 

847 install_rule = AbstractMutableYAMLInstallRule.install_as( 

848 _strip_d_tmp( 

849 _normalize_path( 

850 dhe_line.tokens[0], 

851 with_prefix=False, 

852 allow_and_keep_upward_segments=True, 

853 ), 

854 dhe_line.config_file, 

855 dhe_line.line_no, 

856 ), 

857 _raise_on_unsupported_path( 

858 _normalize_path( 

859 dhe_line.tokens[2], 

860 with_prefix=False, 

861 allow_and_keep_upward_segments=True, 

862 ), 

863 dhe_line.config_file, 

864 dhe_line.line_no, 

865 ), 

866 dctrl_bin.name if not is_single_binary else None, 

867 when=dhe_line.conditional(), 

868 ) 

869 install_as_rules.append(install_rule) 

870 else: 

871 if len(dhe_line.tokens) > 1: 

872 sources = list( 

873 _strip_d_tmp( 

874 _normalize_path( 

875 w, 

876 with_prefix=False, 

877 allow_and_keep_upward_segments=True, 

878 ), 

879 dhe_line.config_file, 

880 dhe_line.line_no, 

881 ) 

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

883 ) 

884 dest_dir = _raise_on_unsupported_path( 

885 _normalize_path( 

886 dhe_line.tokens[-1], 

887 with_prefix=False, 

888 allow_and_keep_upward_segments=True, 

889 ), 

890 dhe_line.config_file, 

891 dhe_line.line_no, 

892 ) 

893 else: 

894 sources = list( 

895 _strip_d_tmp( 

896 _normalize_path( 

897 w, 

898 with_prefix=False, 

899 allow_and_keep_upward_segments=True, 

900 ), 

901 dhe_line.config_file, 

902 dhe_line.line_no, 

903 ) 

904 for w in dhe_line.tokens 

905 ) 

906 dest_dir = None 

907 

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

909 seen_sources.update(sources) 

910 

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

912 current_sources.extend(sources) 

913 continue 

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

915 ctor = functools.partial( 

916 SourcesAndConditional, 

917 dest_dir=dest_dir, 

918 conditional=dhe_line.conditional(), 

919 ) 

920 md = _fetch_or_create( 

921 sources_by_destdir, 

922 key, 

923 ctor, 

924 ) 

925 md.sources.extend(sources) 

926 

927 if special_rule: 

928 priority_lines.append(special_rule) 

929 

930 remaining_install_lines.extend(install_as_rules) 

931 

932 for md in sources_by_destdir.values(): 

933 if multi_dest_sources: 

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

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

936 for s in already_installed: 

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

938 multi_dest[s].append(md) 

939 if not sources: 

940 continue 

941 else: 

942 sources = md.sources 

943 install_rule = AbstractMutableYAMLInstallRule.install_dest( 

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

945 dctrl_bin.name if not is_single_binary else None, 

946 dest_dir=md.dest_dir, 

947 when=md.conditional, 

948 ) 

949 remaining_install_lines.append(install_rule) 

950 

951 if current_sources: 

952 if multi_dest_sources: 

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

954 already_installed = ( 

955 s for s in current_sources if s in multi_dest_sources 

956 ) 

957 for s in already_installed: 

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

959 dest_dir = os.path.dirname(s) 

960 if has_glob_magic(dest_dir): 

961 warn_about_fixmes_in_dest_dir = True 

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

963 multi_dest[s].append( 

964 SourcesAndConditional( 

965 dest_dir=dest_dir, 

966 conditional=None, 

967 ) 

968 ) 

969 else: 

970 sources = current_sources 

971 

972 if sources: 

973 install_rule = AbstractMutableYAMLInstallRule.install_dest( 

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

975 dctrl_bin.name if not is_single_binary else None, 

976 dest_dir=None, 

977 ) 

978 remaining_install_lines.append(install_rule) 

979 

980 if multi_dest: 

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

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

983 # We assume the conditional is the same. 

984 conditional = next( 

985 iter( 

986 dac.conditional 

987 for dac in dest_and_conditionals 

988 if dac.conditional is not None 

989 ), 

990 None, 

991 ) 

992 remaining_install_lines.append( 

993 AbstractMutableYAMLInstallRule.multi_dest_install( 

994 source, 

995 dest_dirs, 

996 dctrl_bin.name if not is_single_binary else None, 

997 when=conditional, 

998 ) 

999 ) 

1000 

1001 if priority_lines: 

1002 installations.extend(priority_lines) 

1003 

1004 if remaining_install_lines: 

1005 installations.extend(remaining_install_lines) 

1006 

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

1008 remaining_install_lines 

1009 ) 

1010 if warn_about_fixmes_in_dest_dir: 

1011 feature_migration.warn( 

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

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

1014 ) 

1015 

1016 

1017def migrate_installdocs_file( 

1018 migration_request: MigrationRequest, 

1019 feature_migration: FeatureMigration, 

1020) -> None: 

1021 feature_migration.tagline = "dh_installdocs config files" 

1022 manifest = migration_request.manifest 

1023 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1024 installations = mutable_manifest.installations(create_if_absent=False) 

1025 

1026 is_single_binary = migration_request.is_single_binary_package 

1027 

1028 for dctrl_bin in migration_request.all_packages: 

1029 install_file, content = _dh_config_file( 

1030 migration_request.debian_dir, 

1031 dctrl_bin, 

1032 "docs", 

1033 "dh_installdocs", 

1034 migration_request.acceptable_migration_issues, 

1035 feature_migration, 

1036 manifest, 

1037 support_executable_files=True, 

1038 ) 

1039 if not install_file: 

1040 continue 

1041 assert content is not None 

1042 docs: list[str] = [] 

1043 for dhe_line in content: 

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

1045 _error( 

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

1047 " Missing support for conditions." 

1048 ) 

1049 docs.extend( 

1050 _raise_on_unsupported_path( 

1051 _normalize_path( 

1052 w, with_prefix=False, allow_and_keep_upward_segments=True 

1053 ), 

1054 dhe_line.config_file, 

1055 dhe_line.line_no, 

1056 ) 

1057 for w in dhe_line.tokens 

1058 ) 

1059 

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

1061 continue 

1062 feature_migration.successful_manifest_changes += 1 

1063 install_rule = AbstractMutableYAMLInstallRule.install_docs( 

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

1065 dctrl_bin.name if not is_single_binary else None, 

1066 ) 

1067 installations.create_definition_if_missing() 

1068 installations.append(install_rule) 

1069 

1070 

1071def migrate_installexamples_file( 

1072 migration_request: MigrationRequest, 

1073 feature_migration: FeatureMigration, 

1074) -> None: 

1075 feature_migration.tagline = "dh_installexamples config files" 

1076 manifest = migration_request.manifest 

1077 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1078 installations = mutable_manifest.installations(create_if_absent=False) 

1079 is_single_binary = migration_request.is_single_binary_package 

1080 

1081 for dctrl_bin in manifest.all_packages: 

1082 install_file, content = _dh_config_file( 

1083 migration_request.debian_dir, 

1084 dctrl_bin, 

1085 "examples", 

1086 "dh_installexamples", 

1087 migration_request.acceptable_migration_issues, 

1088 feature_migration, 

1089 manifest, 

1090 support_executable_files=True, 

1091 ) 

1092 if not install_file: 

1093 continue 

1094 assert content is not None 

1095 examples: list[str] = [] 

1096 for dhe_line in content: 

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

1098 _error( 

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

1100 " Missing support for conditions." 

1101 ) 

1102 examples.extend( 

1103 _raise_on_unsupported_path( 

1104 _normalize_path( 

1105 w, with_prefix=False, allow_and_keep_upward_segments=True 

1106 ), 

1107 dhe_line.config_file, 

1108 dhe_line.line_no, 

1109 ) 

1110 for w in dhe_line.tokens 

1111 ) 

1112 

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

1114 continue 

1115 feature_migration.successful_manifest_changes += 1 

1116 install_rule = AbstractMutableYAMLInstallRule.install_examples( 

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

1118 dctrl_bin.name if not is_single_binary else None, 

1119 ) 

1120 installations.create_definition_if_missing() 

1121 installations.append(install_rule) 

1122 

1123 

1124@dataclasses.dataclass(slots=True) 

1125class InfoFilesDefinition: 

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

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

1128 

1129 

1130def migrate_installinfo_file( 

1131 migration_request: MigrationRequest, 

1132 feature_migration: FeatureMigration, 

1133) -> None: 

1134 feature_migration.tagline = "dh_installinfo config files" 

1135 manifest = migration_request.manifest 

1136 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1137 installations = mutable_manifest.installations(create_if_absent=False) 

1138 is_single_binary = migration_request.is_single_binary_package 

1139 

1140 for dctrl_bin in manifest.all_packages: 

1141 info_file, content = _dh_config_file( 

1142 migration_request.debian_dir, 

1143 dctrl_bin, 

1144 "info", 

1145 "dh_installinfo", 

1146 migration_request.acceptable_migration_issues, 

1147 feature_migration, 

1148 manifest, 

1149 support_executable_files=True, 

1150 ) 

1151 if not info_file: 

1152 continue 

1153 assert content is not None 

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

1155 for dhe_line in content: 

1156 key = dhe_line.conditional_key() 

1157 ctr = functools.partial( 

1158 InfoFilesDefinition, conditional=dhe_line.conditional() 

1159 ) 

1160 info_def = _fetch_or_create( 

1161 info_files_by_condition, 

1162 key, 

1163 ctr, 

1164 ) 

1165 info_def.sources.extend( 

1166 _raise_on_unsupported_path( 

1167 _normalize_path( 

1168 w, with_prefix=False, allow_and_keep_upward_segments=True 

1169 ), 

1170 dhe_line.config_file, 

1171 dhe_line.line_no, 

1172 ) 

1173 for w in dhe_line.tokens 

1174 ) 

1175 

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

1177 continue 

1178 feature_migration.successful_manifest_changes += 1 

1179 installations.create_definition_if_missing() 

1180 for info_def in info_files_by_condition.values(): 

1181 info_files = info_def.sources 

1182 install_rule = AbstractMutableYAMLInstallRule.install_docs( 

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

1184 dctrl_bin.name if not is_single_binary else None, 

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

1186 when=info_def.conditional, 

1187 ) 

1188 installations.append(install_rule) 

1189 

1190 

1191@dataclasses.dataclass(slots=True) 

1192class ManpageDefinition: 

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

1194 language: str | None = None 

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

1196 

1197 

1198DK = TypeVar("DK") 

1199DV = TypeVar("DV") 

1200 

1201 

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

1203 v = d.get(key) 

1204 if v is None: 

1205 v = factory() 

1206 d[key] = v 

1207 return v 

1208 

1209 

1210def migrate_installman_file( 

1211 migration_request: MigrationRequest, 

1212 feature_migration: FeatureMigration, 

1213) -> None: 

1214 feature_migration.tagline = "dh_installman config files" 

1215 manifest = migration_request.manifest 

1216 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1217 installations = mutable_manifest.installations(create_if_absent=False) 

1218 is_single_binary = migration_request.is_single_binary_package 

1219 warn_about_basename = False 

1220 

1221 for dctrl_bin in migration_request.all_packages: 

1222 manpages_file, content = _dh_config_file( 

1223 migration_request.debian_dir, 

1224 dctrl_bin, 

1225 "manpages", 

1226 "dh_installman", 

1227 migration_request.acceptable_migration_issues, 

1228 feature_migration, 

1229 manifest, 

1230 support_executable_files=True, 

1231 allow_dh_exec_rename=True, 

1232 ) 

1233 if not manpages_file: 

1234 continue 

1235 assert content is not None 

1236 

1237 vanilla_definitions = [] 

1238 install_as_rules = [] 

1239 complex_definitions: dict[ 

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

1241 ] = {} 

1242 install_rule: AbstractMutableYAMLInstallRule 

1243 for dhe_line in content: 

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

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

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

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

1248 _error( 

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

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

1251 ) 

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

1253 _error( 

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

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

1256 ) 

1257 else: 

1258 install_rule = AbstractMutableYAMLInstallRule.install_doc_as( 

1259 _raise_on_unsupported_path( 

1260 _normalize_path( 

1261 dhe_line.tokens[0], 

1262 with_prefix=False, 

1263 allow_and_keep_upward_segments=True, 

1264 ), 

1265 dhe_line.config_file, 

1266 dhe_line.line_no, 

1267 ), 

1268 _raise_on_unsupported_path( 

1269 _normalize_path( 

1270 dhe_line.tokens[2], 

1271 with_prefix=False, 

1272 allow_and_keep_upward_segments=True, 

1273 ), 

1274 dhe_line.config_file, 

1275 dhe_line.line_no, 

1276 ), 

1277 dctrl_bin.name if not is_single_binary else None, 

1278 when=dhe_line.conditional(), 

1279 ) 

1280 install_as_rules.append(install_rule) 

1281 continue 

1282 

1283 sources = [ 

1284 _raise_on_unsupported_path( 

1285 _normalize_path( 

1286 w, with_prefix=False, allow_and_keep_upward_segments=True 

1287 ), 

1288 dhe_line.config_file, 

1289 dhe_line.line_no, 

1290 ) 

1291 for w in dhe_line.tokens 

1292 ] 

1293 needs_basename = any( 

1294 MAN_GUESS_FROM_BASENAME.search(x) 

1295 and not MAN_GUESS_LANG_FROM_PATH.search(x) 

1296 for x in sources 

1297 ) 

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

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

1300 warn_about_basename = True 

1301 language = "derive-from-basename" 

1302 else: 

1303 language = None 

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

1305 ctor = functools.partial( 

1306 ManpageDefinition, 

1307 language=language, 

1308 conditional=dhe_line.conditional(), 

1309 ) 

1310 manpage_def = _fetch_or_create( 

1311 complex_definitions, 

1312 key, 

1313 ctor, 

1314 ) 

1315 manpage_def.sources.extend(sources) 

1316 else: 

1317 vanilla_definitions.extend(sources) 

1318 

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

1320 continue 

1321 feature_migration.successful_manifest_changes += 1 

1322 installations.create_definition_if_missing() 

1323 installations.extend(install_as_rules) 

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

1325 man_source = ( 

1326 vanilla_definitions 

1327 if len(vanilla_definitions) > 1 

1328 else vanilla_definitions[0] 

1329 ) 

1330 install_rule = AbstractMutableYAMLInstallRule.install_man( 

1331 man_source, 

1332 dctrl_bin.name if not is_single_binary else None, 

1333 None, 

1334 ) 

1335 installations.append(install_rule) 

1336 for manpage_def in complex_definitions.values(): 

1337 sources = manpage_def.sources 

1338 install_rule = AbstractMutableYAMLInstallRule.install_man( 

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

1340 dctrl_bin.name if not is_single_binary else None, 

1341 manpage_def.language, 

1342 when=manpage_def.conditional, 

1343 ) 

1344 installations.append(install_rule) 

1345 

1346 if warn_about_basename: 

1347 feature_migration.warn( 

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

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

1350 ) 

1351 

1352 

1353def migrate_not_installed_file( 

1354 migration_request: MigrationRequest, 

1355 feature_migration: FeatureMigration, 

1356) -> None: 

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

1358 manifest = migration_request.manifest 

1359 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1360 installations = mutable_manifest.installations(create_if_absent=False) 

1361 main_binary = migration_request.main_binary 

1362 

1363 missing_file, content = _dh_config_file( 

1364 migration_request.debian_dir, 

1365 main_binary, 

1366 "not-installed", 

1367 "dh_missing", 

1368 migration_request.acceptable_migration_issues, 

1369 feature_migration, 

1370 manifest, 

1371 support_executable_files=False, 

1372 pkgfile_lookup=False, 

1373 ) 

1374 discard_rules: list[str] = [] 

1375 if missing_file: 

1376 assert content is not None 

1377 for dhe_line in content: 

1378 discard_rules.extend( 

1379 _raise_on_unsupported_path( 

1380 _normalize_path( 

1381 w, with_prefix=False, allow_and_keep_upward_segments=True 

1382 ), 

1383 dhe_line.config_file, 

1384 dhe_line.line_no, 

1385 ) 

1386 for w in dhe_line.tokens 

1387 ) 

1388 

1389 if discard_rules: 

1390 feature_migration.successful_manifest_changes += 1 

1391 install_rule = AbstractMutableYAMLInstallRule.discard( 

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

1393 ) 

1394 installations.create_definition_if_missing() 

1395 installations.append(install_rule) 

1396 

1397 

1398def min_dh_compat_check( 

1399 migration_request: MigrationRequest, 

1400 feature_migration: FeatureMigration, 

1401) -> None: 

1402 feature_migration.tagline = "min dh compat level check" 

1403 # We start on compat 12 for arch:any due to the new dh_makeshlibs and dh_installinit default 

1404 # For arch:any, the min is compat 14 due to `dh_dwz` being removed. 

1405 min_compat = ( 

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

1407 ) 

1408 feature_migration.assumed_compat = min_compat 

1409 

1410 

1411def detect_pam_files( 

1412 migration_request: MigrationRequest, 

1413 feature_migration: FeatureMigration, 

1414) -> None: 

1415 feature_migration.tagline = "detect dh_installpam files (min dh compat)" 

1416 for dctrl_bin in migration_request.all_packages: 

1417 dh_config_file = dhe_pkgfile(migration_request.debian_dir, dctrl_bin, "pam") 

1418 if dh_config_file is not None: 

1419 feature_migration.assumed_compat = 14 

1420 break 

1421 

1422 

1423def detect_modprobe_files( 

1424 migration_request: MigrationRequest, 

1425 feature_migration: FeatureMigration, 

1426) -> None: 

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

1428 for dctrl_bin in migration_request.all_packages: 

1429 dh_config_file = dhe_pkgfile( 

1430 migration_request.debian_dir, dctrl_bin, "modprobe" 

1431 ) 

1432 if dh_config_file is not None: 

1433 feature_migration.assumed_compat = 14 

1434 break 

1435 

1436 

1437def migrate_tmpfile( 

1438 migration_request: MigrationRequest, 

1439 feature_migration: FeatureMigration, 

1440) -> None: 

1441 feature_migration.tagline = "dh_installtmpfiles config files" 

1442 debian_dir = migration_request.debian_dir 

1443 for dctrl_bin in migration_request.all_packages: 

1444 dh_config_file = dhe_pkgfile(debian_dir, dctrl_bin, "tmpfile") 

1445 if dh_config_file is not None: 

1446 target = ( 

1447 dh_config_file.name.replace(".tmpfile", ".tmpfiles") 

1448 if "." in dh_config_file.name 

1449 else "tmpfiles" 

1450 ) 

1451 _rename_file_if_exists( 

1452 debian_dir, 

1453 dh_config_file.name, 

1454 target, 

1455 feature_migration, 

1456 ) 

1457 

1458 

1459def migrate_lintian_overrides_files( 

1460 migration_request: MigrationRequest, 

1461 feature_migration: FeatureMigration, 

1462) -> None: 

1463 feature_migration.tagline = "dh_lintian config files" 

1464 for dctrl_bin in migration_request.all_packages: 

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

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

1467 _dh_config_file( 

1468 migration_request.debian_dir, 

1469 dctrl_bin, 

1470 "lintian-overrides", 

1471 "dh_lintian", 

1472 migration_request.acceptable_migration_issues, 

1473 feature_migration, 

1474 migration_request.manifest, 

1475 support_executable_files=False, 

1476 remove_on_migration=False, 

1477 ) 

1478 

1479 

1480def migrate_links_files( 

1481 migration_request: MigrationRequest, 

1482 feature_migration: FeatureMigration, 

1483) -> None: 

1484 feature_migration.tagline = "dh_link files" 

1485 manifest = migration_request.manifest 

1486 mutable_manifest = assume_not_none(manifest.mutable_manifest) 

1487 for dctrl_bin in migration_request.all_packages: 

1488 links_file, content = _dh_config_file( 

1489 migration_request.debian_dir, 

1490 dctrl_bin, 

1491 "links", 

1492 "dh_link", 

1493 migration_request.acceptable_migration_issues, 

1494 feature_migration, 

1495 manifest, 

1496 support_executable_files=True, 

1497 ) 

1498 

1499 if links_file is None: 

1500 continue 

1501 assert content is not None 

1502 

1503 package_definition = mutable_manifest.package(dctrl_bin.name) 

1504 defined_symlink = { 

1505 symlink.symlink_path: symlink.symlink_target 

1506 for symlink in package_definition.symlinks() 

1507 } 

1508 

1509 seen_symlinks: set[str] = set() 

1510 

1511 for dhe_line in content: 

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

1513 raise UnsupportedFeature( 

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

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

1516 ) 

1517 target, source = dhe_line.tokens 

1518 source = _raise_on_unsupported_path( 

1519 source, 

1520 dhe_line.config_file, 

1521 dhe_line.line_no, 

1522 ) 

1523 target = _raise_on_unsupported_path( 

1524 target, 

1525 dhe_line.config_file, 

1526 dhe_line.line_no, 

1527 ) 

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

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

1530 raise ConflictingChange( 

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

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

1533 ) 

1534 seen_symlinks.add(source) 

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

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

1537 # absolute. 

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

1539 target = "/" + target 

1540 existing_target = defined_symlink.get(source) 

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

1542 if existing_target != target: 

1543 raise ConflictingChange( 

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

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

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

1547 f" {links_file.fs_path})" 

1548 ) 

1549 continue 

1550 condition = dhe_line.conditional() 

1551 package_definition.add_symlink( 

1552 MutableYAMLSymlink.new_symlink( 

1553 source, 

1554 target, 

1555 condition, 

1556 ) 

1557 ) 

1558 feature_migration.successful_manifest_changes += 1 

1559 

1560 

1561def migrate_misspelled_readme_debian_files( 

1562 migration_request: MigrationRequest, 

1563 feature_migration: FeatureMigration, 

1564) -> None: 

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

1566 debian_dir = migration_request.debian_dir 

1567 for dctrl_bin in migration_request.all_packages: 

1568 readme, _ = _dh_config_file( 

1569 debian_dir, 

1570 dctrl_bin, 

1571 "README.debian", 

1572 "dh_installdocs", 

1573 migration_request.acceptable_migration_issues, 

1574 feature_migration, 

1575 migration_request.manifest, 

1576 support_executable_files=False, 

1577 remove_on_migration=False, 

1578 ) 

1579 if readme is None: 

1580 continue 

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

1582 assert readme.name != new_name 

1583 _rename_file_if_exists( 

1584 debian_dir, 

1585 readme.name, 

1586 new_name, 

1587 feature_migration, 

1588 ) 

1589 

1590 

1591def migrate_doc_base_files( 

1592 migration_request: MigrationRequest, 

1593 feature_migration: FeatureMigration, 

1594) -> None: 

1595 feature_migration.tagline = "doc-base files" 

1596 debian_dir = migration_request.debian_dir 

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

1598 possible_effected_doc_base_files = [ 

1599 f 

1600 for f in debian_dir.iterdir 

1601 if ( 

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

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

1604 ) 

1605 ] 

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

1607 main_package = migration_request.main_binary 

1608 for doc_base_file in possible_effected_doc_base_files: 

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

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

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

1612 owning_package = main_package 

1613 package_part = None 

1614 else: 

1615 package_part = parts[0] 

1616 parts = parts[1:] 

1617 

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

1619 # Not a doc-base file after all 

1620 continue 

1621 

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

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

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

1625 # Named files must have a package prefix 

1626 package_part = owning_package.name 

1627 else: 

1628 # No rename needed 

1629 continue 

1630 

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

1632 _rename_file_if_exists( 

1633 debian_dir, 

1634 doc_base_file.name, 

1635 new_basename, 

1636 feature_migration, 

1637 ) 

1638 

1639 

1640def migrate_dh_hook_targets( 

1641 migration_request: MigrationRequest, 

1642 feature_migration: FeatureMigration, 

1643) -> None: 

1644 feature_migration.tagline = "dh hook targets" 

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

1646 if source_root == "": 

1647 source_root = "." 

1648 detected_hook_targets = json.loads( 

1649 subprocess.check_output( 

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

1651 cwd=source_root, 

1652 ).decode("utf-8") 

1653 ) 

1654 sample_hook_target: str | None = None 

1655 debputy_integration_mode = migration_request.migration_target 

1656 assert debputy_integration_mode is not None 

1657 replaced_commands = DH_COMMANDS_REPLACED[debputy_integration_mode] 

1658 

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

1660 if hook_target_def["is-empty"]: 

1661 continue 

1662 command = hook_target_def["command"] 

1663 if command not in replaced_commands: 

1664 continue 

1665 hook_target = hook_target_def["target-name"] 

1666 advice = MIGRATION_AID_FOR_OVERRIDDEN_COMMANDS.get(command) 

1667 if advice is None: 

1668 if sample_hook_target is None: 

1669 sample_hook_target = hook_target 

1670 feature_migration.warn( 

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

1672 ) 

1673 else: 

1674 feature_migration.warn( 

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

1676 f" for migration advice." 

1677 ) 

1678 if ( 

1679 feature_migration.warnings 

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

1681 and sample_hook_target is not None 

1682 ): 

1683 raise UnsupportedFeature( 

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

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

1686 f" {sample_hook_target}.", 

1687 ["dh-hook-targets"], 

1688 ) 

1689 

1690 

1691def detect_unsupported_zz_debputy_features( 

1692 migration_request: MigrationRequest, 

1693 feature_migration: FeatureMigration, 

1694) -> None: 

1695 feature_migration.tagline = "Known unsupported features" 

1696 

1697 for unsupported_config in UNSUPPORTED_DH_CONFIGS_AND_TOOLS_FOR_ZZ_DEBPUTY: 

1698 _unsupported_debhelper_config_file( 

1699 migration_request, 

1700 unsupported_config, 

1701 feature_migration, 

1702 ) 

1703 

1704 

1705def detect_obsolete_substvars( 

1706 migration_request: MigrationRequest, 

1707 feature_migration: FeatureMigration, 

1708) -> None: 

1709 feature_migration.tagline = ( 

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

1711 ) 

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

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

1714 feature_migration.warn( 

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

1716 ) 

1717 return 

1718 with ctrl_file.open() as fd: 

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

1720 

1721 relationship_fields = dpkg_field_list_pkg_dep() 

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

1723 

1724 for p in ctrl[1:]: 

1725 seen_obsolete_relationship_substvars = set() 

1726 obsolete_fields = set() 

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

1728 for df in relationship_fields: 

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

1730 if field is None: 

1731 continue 

1732 df_lc = df.lower() 

1733 number_of_relations = 0 

1734 obsolete_substvars_in_field = set() 

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

1736 if not d: 

1737 continue 

1738 number_of_relations += 1 

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

1740 continue 

1741 try: 

1742 end_idx = d.index("}") 

1743 except ValueError: 

1744 continue 

1745 substvar_name = d[2:end_idx] 

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

1747 continue 

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

1749 field_lc = field.lower() 

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

1751 continue 

1752 is_obsolete = field_lc == df_lc 

1753 if ( 

1754 not is_obsolete 

1755 and is_essential 

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

1757 and df_lc == "pre-depends" 

1758 ): 

1759 is_obsolete = True 

1760 

1761 if is_obsolete: 

1762 obsolete_substvars_in_field.add(d) 

1763 

1764 if number_of_relations == len(obsolete_substvars_in_field): 

1765 obsolete_fields.add(df) 

1766 else: 

1767 seen_obsolete_relationship_substvars.update(obsolete_substvars_in_field) 

1768 

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

1770 fo = feature_migration.fo 

1771 if obsolete_fields: 

1772 fields = ", ".join(obsolete_fields) 

1773 feature_migration.warn( 

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

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

1776 ) 

1777 if seen_obsolete_relationship_substvars: 

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

1779 feature_migration.warn( 

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

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

1782 ) 

1783 

1784 

1785def detect_dh_addons_zz_debputy_rrr( 

1786 migration_request: MigrationRequest, 

1787 feature_migration: FeatureMigration, 

1788) -> None: 

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

1790 r = read_dh_addon_sequences(migration_request.debian_dir) 

1791 if r is None: 

1792 feature_migration.warn( 

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

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

1795 ) 

1796 return 

1797 

1798 bd_sequences, dr_sequences, _ = r 

1799 

1800 remaining_sequences = bd_sequences | dr_sequences 

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

1802 

1803 if not saw_dh_debputy: 

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

1805 

1806 

1807def detect_dh_addons_with_full_integration( 

1808 _migration_request: MigrationRequest, 

1809 feature_migration: FeatureMigration, 

1810) -> None: 

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

1812 feature_migration.warn( 

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

1814 ) 

1815 feature_migration.warn( 

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

1817 ) 

1818 feature_migration.warn( 

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

1820 ) 

1821 

1822 

1823def detect_dh_addons_with_zz_integration( 

1824 migration_request: MigrationRequest, 

1825 feature_migration: FeatureMigration, 

1826) -> None: 

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

1828 r = read_dh_addon_sequences(migration_request.debian_dir) 

1829 acceptable_migration_issues = migration_request.acceptable_migration_issues 

1830 if r is None: 

1831 feature_migration.warn( 

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

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

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

1835 ) 

1836 return 

1837 

1838 assert migration_request.migration_target != INTEGRATION_MODE_FULL 

1839 

1840 bd_sequences, dr_sequences, _ = r 

1841 

1842 remaining_sequences = bd_sequences | dr_sequences 

1843 saw_dh_debputy = ( 

1844 "debputy" in remaining_sequences or "zz-debputy" in remaining_sequences 

1845 ) 

1846 saw_zz_debputy = "zz-debputy" in remaining_sequences 

1847 must_use_zz_debputy = False 

1848 remaining_sequences -= SUPPORTED_DH_ADDONS_WITH_ZZ_DEBPUTY 

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

1850 migration = DH_ADDONS_TO_PLUGINS[sequence] 

1851 feature_migration.require_plugin(migration.debputy_plugin) 

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

1853 if migration.must_use_zz_debputy: 

1854 must_use_zz_debputy = True 

1855 if sequence in bd_sequences: 

1856 feature_migration.warn( 

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

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

1859 ) 

1860 else: 

1861 feature_migration.warn( 

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

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

1864 ) 

1865 

1866 remaining_sequences -= DH_ADDONS_TO_PLUGINS.keys() 

1867 

1868 alt_key = "unsupported-dh-sequences" 

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

1870 if sequence in bd_sequences: 

1871 feature_migration.warn( 

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

1873 ) 

1874 else: 

1875 feature_migration.warn( 

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

1877 ) 

1878 

1879 remaining_sequences -= DH_ADDONS_TO_REMOVE_FOR_ZZ_DEBPUTY 

1880 

1881 for sequence in remaining_sequences: 

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

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

1884 if ( 

1885 key not in acceptable_migration_issues 

1886 and alt_key not in acceptable_migration_issues 

1887 ): 

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

1889 feature_migration.warn(msg) 

1890 

1891 if not saw_dh_debputy: 

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

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

1894 feature_migration.warn( 

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

1896 ) 

1897 

1898 

1899def _rename_file_if_exists( 

1900 debian_dir: VirtualPath, 

1901 source: str, 

1902 dest: str, 

1903 feature_migration: FeatureMigration, 

1904) -> None: 

1905 source_path = debian_dir.get(source) 

1906 dest_path = debian_dir.get(dest) 

1907 spath = ( 

1908 source_path.path 

1909 if source_path is not None 

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

1911 ) 

1912 dpath = ( 

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

1914 ) 

1915 if source_path is not None and source_path.is_file: 

1916 if dest_path is not None: 

1917 if not dest_path.is_file: 

1918 feature_migration.warnings.append( 

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

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

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

1922 ) 

1923 return 

1924 if ( 

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

1926 != 0 

1927 ): 

1928 feature_migration.warnings.append( 

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

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

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

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

1933 ) 

1934 else: 

1935 feature_migration.remove_on_success(dest_path.fs_path) 

1936 else: 

1937 feature_migration.rename_on_success( 

1938 source_path.fs_path, 

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

1940 ) 

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

1942 feature_migration.warnings.append( 

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

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

1945 " as a manual migration." 

1946 ) 

1947 

1948 

1949def _find_dh_config_file_for_any_pkg( 

1950 migration_request: MigrationRequest, 

1951 unsupported_config: UnsupportedDHConfig, 

1952) -> Iterable[VirtualPath]: 

1953 for dctrl_bin in migration_request.all_packages: 

1954 dh_config_file = dhe_pkgfile( 

1955 migration_request.debian_dir, 

1956 dctrl_bin, 

1957 unsupported_config.dh_config_basename, 

1958 bug_950723_prefix_matching=unsupported_config.bug_950723_prefix_matching, 

1959 ) 

1960 if dh_config_file is not None: 

1961 yield dh_config_file 

1962 

1963 

1964def _unsupported_debhelper_config_file( 

1965 migration_request: MigrationRequest, 

1966 unsupported_config: UnsupportedDHConfig, 

1967 feature_migration: FeatureMigration, 

1968) -> None: 

1969 dh_config_files = list( 

1970 _find_dh_config_file_for_any_pkg(migration_request, unsupported_config) 

1971 ) 

1972 if not dh_config_files: 

1973 return 

1974 dh_tool = unsupported_config.dh_tool 

1975 basename = unsupported_config.dh_config_basename 

1976 file_stem = ( 

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

1978 ) 

1979 dh_config_file = dh_config_files[0] 

1980 if unsupported_config.is_missing_migration: 

1981 feature_migration.warn( 

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

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

1984 " required." 

1985 ) 

1986 return 

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

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

1989 if ( 

1990 primary_key not in migration_request.acceptable_migration_issues 

1991 and secondary_key not in migration_request.acceptable_migration_issues 

1992 ): 

1993 msg = ( 

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

1995 " supported by debputy." 

1996 ) 

1997 raise UnsupportedFeature( 

1998 msg, 

1999 [primary_key, secondary_key], 

2000 ) 

2001 for dh_config_file in dh_config_files: 

2002 feature_migration.warn( 

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

2004 )