Coverage for src/debputy/plugins/debputy/build_system_rules.py: 37%

672 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-04-19 20:37 +0000

1import collections 

2import dataclasses 

3import json 

4import os 

5import subprocess 

6import textwrap 

7from typing import ( 

8 NotRequired, 

9 TypedDict, 

10 Self, 

11 cast, 

12 Union, 

13 TYPE_CHECKING, 

14 Literal, 

15 Required, 

16) 

17from collections.abc import Mapping, Sequence, MutableMapping, Iterable, Container 

18 

19from debian.debian_support import Version 

20 

21from debputy._manifest_constants import MK_BUILDS 

22from debputy.manifest_conditions import ManifestCondition 

23from debputy.manifest_parser.base_types import ( 

24 BuildEnvironmentDefinition, 

25 DebputyParsedContentStandardConditional, 

26 FileSystemExactMatchRule, 

27) 

28from debputy.manifest_parser.exceptions import ( 

29 ManifestParseException, 

30 ManifestInvalidUserDataException, 

31) 

32from debputy.manifest_parser.parser_data import ParserContextData 

33from debputy.manifest_parser.tagging_types import DebputyParsedContent 

34from debputy.manifest_parser.util import AttributePath 

35from debputy.plugin.api import reference_documentation 

36from debputy.plugin.api.impl import ( 

37 DebputyPluginInitializerProvider, 

38) 

39from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT 

40from debputy.plugin.api.spec import ( 

41 documented_attr, 

42 INTEGRATION_MODE_FULL, 

43 only_integrations, 

44 VirtualPath, 

45) 

46from debputy.plugin.api.std_docs import docs_from 

47from debputy.plugins.debputy.to_be_api_types import ( 

48 BuildRule, 

49 StepBasedBuildSystemRule, 

50 OptionalInstallDirectly, 

51 BuildSystemCharacteristics, 

52 OptionalBuildDirectory, 

53 OptionalInSourceBuild, 

54 MakefileSupport, 

55 BuildRuleParsedFormat, 

56 debputy_build_system, 

57 CleanHelper, 

58 NinjaBuildSupport, 

59 TestRule, 

60 OptionalTestRule, 

61) 

62from debputy.types import EnvironmentModification 

63from debputy.util import ( 

64 _warn, 

65 run_build_system_command, 

66 _error, 

67 PerlConfigVars, 

68 resolve_perl_config, 

69 generated_content_dir, 

70 manifest_format_doc, 

71 _is_debug_log_enabled, 

72) 

73 

74if TYPE_CHECKING: 

75 from debputy.build_support.build_context import BuildContext 

76 from debputy.highlevel_manifest import HighLevelManifest 

77 

78 

79PERL_CMD = "perl" 

80 

81 

82class Conditional(DebputyParsedContent): 

83 when: Required[ManifestCondition] 

84 

85 

86def register_build_system_rules(api: DebputyPluginInitializerProvider) -> None: 

87 register_build_keywords(api) 

88 register_build_rules(api) 

89 

90 

91def register_build_keywords(api: DebputyPluginInitializerProvider) -> None: 

92 

93 api.pluggable_manifest_rule( 

94 OPARSER_MANIFEST_ROOT, 

95 "build-environments", 

96 list[NamedEnvironmentSourceFormat], 

97 _parse_build_environments, 

98 register_value=False, 

99 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

100 inline_reference_documentation=reference_documentation( 

101 title="Build Environments (`build-environments`)", 

102 description=textwrap.dedent("""\ 

103 Define named environments to set the environment for any build commands that needs 

104 a non-default environment. 

105 

106 The environment definitions can be used to tweak the environment variables used by the 

107 build commands. An example: 

108 

109 build-environments: 

110 - name: custom-env 

111 set: 

112 ENV_VAR: foo 

113 ANOTHER_ENV_VAR: bar 

114 builds: 

115 - autoconf: 

116 environment: custom-env 

117 

118 The environment definition has multiple attributes for setting environment variables 

119 which determines when the definition is applied. The resulting environment is the 

120 result of the following order of operations. 

121 

122 1. The environment `debputy` received from its parent process. 

123 2. Apply all the variable definitions from `set` (if the attribute is present) 

124 3. Apply all computed variables (such as variables from `dpkg-buildflags`). 

125 4. Apply all the variable definitions from `override` (if the attribute is present) 

126 5. Remove all variables listed in `unset` (if the attribute is present). 

127 

128 Accordingly, both `override` and `unset` will overrule any computed variables while 

129 `set` will be overruled by any computed variables. 

130 

131 Note that these variables are not available via manifest substitution. They are 

132 only available and visible to build commands. 

133 """), 

134 attributes=[ 

135 documented_attr( 

136 "name", 

137 textwrap.dedent("""\ 

138 The name of the environment 

139 

140 The name is used to reference the environment from build rules. 

141 """), 

142 ), 

143 documented_attr( 

144 "set", 

145 textwrap.dedent("""\ 

146 A mapping of environment variables to be set. 

147 

148 Note these environment variables are set before computed variables (such 

149 as `dpkg-buildflags`) are provided. They can affect the content of the 

150 computed variables, but they cannot overrule them. If you need to overrule 

151 a computed variable, please use `override` instead. 

152 """), 

153 ), 

154 documented_attr( 

155 "override", 

156 textwrap.dedent("""\ 

157 A mapping of environment variables to set. 

158 

159 Similar to `set`, but it can overrule computed variables like those from 

160 `dpkg-buildflags`. 

161 """), 

162 ), 

163 documented_attr( 

164 "unset", 

165 textwrap.dedent("""\ 

166 A list of environment variables to unset. 

167 

168 Any environment variable named here will be unset. No warnings or errors 

169 will be raised if a given variable was not set. 

170 """), 

171 ), 

172 ], 

173 reference_documentation_url=manifest_format_doc( 

174 "build-environment-build-environment" 

175 ), 

176 ), 

177 ) 

178 api.pluggable_manifest_rule( 

179 OPARSER_MANIFEST_ROOT, 

180 "default-build-environment", 

181 EnvironmentSourceFormat, 

182 _parse_default_environment, 

183 register_value=False, 

184 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

185 inline_reference_documentation=reference_documentation( 

186 title="Default Build Environment (`default-build-environment`)", 

187 description=textwrap.dedent("""\ 

188 Define the environment variables used in all build commands that uses the default 

189 environment. 

190 

191 The environment definition can be used to tweak the environment variables used by the 

192 build commands. An example: 

193 

194 default-build-environment: 

195 set: 

196 ENV_VAR: foo 

197 ANOTHER_ENV_VAR: bar 

198 

199 The environment definition has multiple attributes for setting environment variables 

200 which determines when the definition is applied. The resulting environment is the 

201 result of the following order of operations. 

202 

203 1. The environment `debputy` received from its parent process. 

204 2. Apply all the variable definitions from `set` (if the attribute is present) 

205 3. Apply all computed variables (such as variables from `dpkg-buildflags`). 

206 4. Apply all the variable definitions from `override` (if the attribute is present) 

207 5. Remove all variables listed in `unset` (if the attribute is present). 

208 

209 Accordingly, both `override` and `unset` will overrule any computed variables while 

210 `set` will be overruled by any computed variables. 

211 

212 Note that these variables are not available via manifest substitution. They are 

213 only available and visible to build commands. 

214 """), 

215 attributes=[ 

216 documented_attr( 

217 "set", 

218 textwrap.dedent("""\ 

219 A mapping of environment variables to be set. 

220 

221 Note these environment variables are set before computed variables (such 

222 as `dpkg-buildflags`) are provided. They can affect the content of the 

223 computed variables, but they cannot overrule them. If you need to overrule 

224 a computed variable, please use `override` instead. 

225 """), 

226 ), 

227 documented_attr( 

228 "override", 

229 textwrap.dedent("""\ 

230 A mapping of environment variables to set. 

231 

232 Similar to `set`, but it can overrule computed variables like those from 

233 `dpkg-buildflags`. 

234 """), 

235 ), 

236 documented_attr( 

237 "unset", 

238 textwrap.dedent("""\ 

239 A list of environment variables to unset. 

240 

241 Any environment variable named here will be unset. No warnings or errors 

242 will be raised if a given variable was not set. 

243 """), 

244 ), 

245 ], 

246 reference_documentation_url=manifest_format_doc( 

247 "build-environment-build-environment" 

248 ), 

249 ), 

250 ) 

251 api.pluggable_manifest_rule( 

252 OPARSER_MANIFEST_ROOT, 

253 MK_BUILDS, 

254 list[BuildRule], 

255 _handle_build_rules, 

256 register_value=False, 

257 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

258 inline_reference_documentation=reference_documentation( 

259 title=f"Build rules (`{MK_BUILDS}`)", 

260 description=textwrap.dedent("""\ 

261 Define how to build the upstream part of the package. Usually this is done via "build systems", 

262 which also defines the clean rules. 

263 

264 A simple example is: 

265 

266 ```yaml 

267 builds: 

268 - autoconf: 

269 configure-args: 

270 - "--enable-foo" 

271 - "--without=bar" 

272 ``` 

273 

274 Multiple builds are supported out of box. Here an example of how the build rules 

275 for `libpam-krb5` might look. 

276 

277 ```yaml 

278 builds: 

279 - autoconf: 

280 for: libpam-krb5 

281 install-directly-to-package: true 

282 configure-args: 

283 - "--enable-reduced-depends" 

284 - "--with-krb5-include=/usr/include/mit-krb5" 

285 - "--with-krb5-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/mit-krb5" 

286 - "--with-kadm-client-include=/usr/include/mit-krb5" 

287 - "--with-kadm-client-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/mit-krb5" 

288 - autoconf: 

289 for: libpam-heimdal 

290 install-directly-to-package: true 

291 configure-args: 

292 - "--enable-reduced-depends" 

293 - "--with-krb5-include=/usr/include/heimdal" 

294 - "--with-krb5-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/heimdal" 

295 - "--with-kadm-client-include=/usr/include/heimdal" 

296 - "--with-kadm-client-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/heimdal" 

297 ``` 

298 """), 

299 ), 

300 ) 

301 

302 api.provide_manifest_keyword( 

303 TestRule, 

304 "skip-tests", 

305 lambda n, ap, pc: TestRule( 

306 ap.path, 

307 ManifestCondition.literal_bool(False), 

308 ManifestCondition.literal_bool(True), 

309 ), 

310 inline_reference_documentation=reference_documentation( 

311 title="Skip build time tests (`skip-tests`)", 

312 description=textwrap.dedent("""\ 

313 Skip all build time tests. 

314 

315 Example: 

316 

317 ```yaml 

318 ...: 

319 # The tests require internet access and cannot be run at build time. 

320 test-rule: skip-tests 

321 ``` 

322 

323 """), 

324 ), 

325 ) 

326 

327 api.pluggable_manifest_rule( 

328 TestRule, 

329 "skip-tests-when", 

330 Conditional, 

331 lambda n, pf, ap, pc: TestRule( 

332 ap.path, 

333 pf["when"].negated(), 

334 ManifestCondition.literal_bool(False), 

335 ), 

336 source_format=ManifestCondition, 

337 inline_reference_documentation=reference_documentation( 

338 title="Conditionally skip build time tests (`skip-tests-when`)", 

339 description=textwrap.dedent("""\ 

340 The `skip-tests-when` test rule accepts a conditional. When that conditional evaluates to 

341 true, the build time tests will be skipped. Otherwise, they will be run as usual. 

342 

343 Note if you want to only run the tests when the conditional evaluates to `true`, 

344 then wrap the conditional with `not:`. 

345 

346 Example: 

347 

348 ```yaml 

349 ...: 

350 # Only run for linux-any (`DEB_HOST_ARCH`). 

351 test-rule: 

352 skip-tests-when: 

353 not: 

354 arch-matches: "linux-any" 

355 ``` 

356 

357 """), 

358 ), 

359 ) 

360 

361 

362def register_build_rules(api: DebputyPluginInitializerProvider) -> None: 

363 api.register_build_system(ParsedAutoconfBuildRuleDefinition) 

364 api.register_build_system(ParsedMakeBuildRuleDefinition) 

365 

366 api.register_build_system(ParsedPerlBuildBuildRuleDefinition) 

367 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition) 

368 api.register_build_system(ParsedDebhelperBuildRuleDefinition) 

369 

370 api.register_build_system(ParsedCMakeBuildRuleDefinition) 

371 api.register_build_system(ParsedMesonBuildRuleDefinition) 

372 

373 api.register_build_system(ParsedQmakeBuildRuleDefinition) 

374 api.register_build_system(ParsedQmake6BuildRuleDefinition) 

375 

376 

377class EnvironmentSourceFormat(TypedDict): 

378 set: NotRequired[dict[str, str]] 

379 override: NotRequired[dict[str, str]] 

380 unset: NotRequired[list[str]] 

381 

382 

383class NamedEnvironmentSourceFormat(EnvironmentSourceFormat): 

384 name: str 

385 

386 

387_READ_ONLY_ENV_VARS = { 

388 "DEB_CHECK_COMMAND": None, 

389 "DEB_SIGN_KEYID": None, 

390 "DEB_SIGN_KEYFILE": None, 

391 "DEB_BUILD_OPTIONS": "DEB_BUILD_MAINT_OPTIONS", 

392 "DEB_BUILD_PROFILES": None, 

393 "DEB_RULES_REQUIRES_ROOT": None, 

394 "DEB_GAIN_ROOT_COMMAND": None, 

395 "DH_EXTRA_ADDONS": None, 

396 "DH_NO_ACT": None, 

397 "XDG_RUNTIME_DIR": None, 

398 "HOME": None, 

399} 

400 

401 

402def _check_variables( 

403 env_vars: Iterable[str], 

404 attribute_path: AttributePath, 

405) -> None: 

406 for env_var in env_vars: 

407 if env_var not in _READ_ONLY_ENV_VARS: 407 ↛ 409line 407 didn't jump to line 409 because the condition on line 407 was always true

408 continue 

409 alt = _READ_ONLY_ENV_VARS.get(env_var) 

410 var_path = attribute_path[env_var].path_key_lc 

411 if alt is None: 

412 raise ManifestParseException( 

413 f"The variable {env_var} cannot be modified by the manifest. This restriction is generally" 

414 f" because the build should not touch those variables or changing them have no effect" 

415 f" (since the consumer will not see the change). The problematic definition was {var_path}" 

416 ) 

417 else: 

418 raise ManifestParseException( 

419 f"The variable {env_var} cannot be modified by the manifest. This restriction is generally" 

420 f" because the build should not touch those variables or changing them have no effect" 

421 f" (since the consumer will not see the change). Depending on what you are trying to" 

422 f' accomplish, the variable "{alt}" might be a suitable alternative.' 

423 f" The problematic definition was {var_path}" 

424 ) 

425 

426 

427def _no_overlap( 

428 lhs: Iterable[str | tuple[int, str]], 

429 rhs: Container[str], 

430 lhs_key: str, 

431 rhs_key: str, 

432 redundant_key: str, 

433 attribute_path: AttributePath, 

434) -> None: 

435 for kt in lhs: 435 ↛ 436line 435 didn't jump to line 436 because the loop on line 435 never started

436 lhs_path_key, var = kt if isinstance(kt, tuple) else (kt, kt) 

437 if var not in rhs: 

438 continue 

439 lhs_path = attribute_path[lhs_key][lhs_path_key].path_key_lc 

440 rhs_path = attribute_path[rhs_key][var].path_key_lc 

441 r_path = lhs_path if redundant_key == rhs_key else rhs_path 

442 raise ManifestParseException( 

443 f"The environment variable {var} was declared in {lhs_path} and {rhs_path}." 

444 f" Due to how the variables are applied, the definition in {r_path} is redundant" 

445 f" and can effectively be removed. Please review the manifest and remove one of" 

446 f" the two definitions." 

447 ) 

448 

449 

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

451class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition): 

452 

453 name: str 

454 is_default: bool 

455 attribute_path: AttributePath 

456 parser_context: ParserContextData 

457 

458 set_vars: Mapping[str, str] 

459 override_vars: Mapping[str, str] 

460 unset_vars: Sequence[str] 

461 

462 @classmethod 

463 def from_environment_definition( 

464 cls, 

465 env: EnvironmentSourceFormat, 

466 attribute_path: AttributePath, 

467 parser_context: ParserContextData, 

468 is_default: bool = False, 

469 ) -> Self: 

470 reference_name: str | None 

471 if is_default: 

472 name = "default-env" 

473 reference_name = None 

474 else: 

475 named_env = cast("NamedEnvironmentSourceFormat", env) 

476 name = named_env["name"] 

477 reference_name = name 

478 

479 set_vars = env.get("set", {}) 

480 override_vars = env.get("override", {}) 

481 unset_vars = env.get("unset", []) 

482 _check_variables(set_vars, attribute_path["set"]) 

483 _check_variables(override_vars, attribute_path["override"]) 

484 _check_variables(unset_vars, attribute_path["unset"]) 

485 

486 if not set_vars and not override_vars and not unset_vars: 486 ↛ 487line 486 didn't jump to line 487 because the condition on line 486 was never true

487 raise ManifestParseException( 

488 f"The environment definition {attribute_path.path_key_lc} was empty. Please provide" 

489 " some content or delete the definition." 

490 ) 

491 

492 _no_overlap( 

493 enumerate(unset_vars), 

494 set_vars, 

495 "unset", 

496 "set", 

497 "set", 

498 attribute_path, 

499 ) 

500 _no_overlap( 

501 enumerate(unset_vars), 

502 override_vars, 

503 "unset", 

504 "override", 

505 "override", 

506 attribute_path, 

507 ) 

508 _no_overlap( 

509 override_vars, 

510 set_vars, 

511 "override", 

512 "set", 

513 "set", 

514 attribute_path, 

515 ) 

516 

517 r = cls( 

518 name, 

519 is_default, 

520 attribute_path, 

521 parser_context, 

522 set_vars, 

523 override_vars, 

524 unset_vars, 

525 ) 

526 parser_context._register_build_environment( 

527 reference_name, 

528 r, 

529 attribute_path, 

530 is_default, 

531 ) 

532 

533 return r 

534 

535 def update_env(self, env: MutableMapping[str, str]) -> None: 

536 if set_vars := self.set_vars: 536 ↛ 538line 536 didn't jump to line 538 because the condition on line 536 was always true

537 env.update(set_vars) 

538 dpkg_env = self.dpkg_buildflags_env(env, self.attribute_path.path_key_lc) 

539 if _is_debug_log_enabled(): 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true

540 self.log_computed_env(f"dpkg-buildflags [{self.name}]", dpkg_env) 

541 if overlapping_env := dpkg_env.keys() & set_vars.keys(): 541 ↛ 542line 541 didn't jump to line 542 because the condition on line 541 was never true

542 for var in overlapping_env: 

543 key_lc = self.attribute_path["set"][var].path_key_lc 

544 _warn( 

545 f'The variable "{var}" defined at {key_lc} is shadowed by a computed variable.' 

546 f" If the manifest definition is more important, please define it via `override` rather than" 

547 f" `set`." 

548 ) 

549 env.update(dpkg_env) 

550 if override_vars := self.override_vars: 550 ↛ 551line 550 didn't jump to line 551 because the condition on line 550 was never true

551 env.update(override_vars) 

552 if unset_vars := self.unset_vars: 552 ↛ 553line 552 didn't jump to line 553 because the condition on line 552 was never true

553 for var in unset_vars: 

554 try: 

555 del env[var] 

556 except KeyError: 

557 pass 

558 

559 

560_MAKE_DEFAULT_TOOLS = [ 

561 ("CC", "gcc"), 

562 ("CXX", "g++"), 

563 ("PKG_CONFIG", "pkg-config"), 

564] 

565 

566 

567class MakefileBuildSystemRule(StepBasedBuildSystemRule): 

568 

569 __slots__ = ("_make_support", "_build_target", "_install_target", "_directory") 

570 

571 def __init__( 

572 self, 

573 attributes: "ParsedMakeBuildRuleDefinition", 

574 attribute_path: AttributePath, 

575 parser_context: Union[ParserContextData, "HighLevelManifest"], 

576 ) -> None: 

577 super().__init__(attributes, attribute_path, parser_context) 

578 directory = attributes.get("directory") 

579 self._directory = directory.match_rule.path if directory else None 

580 self._make_support = MakefileSupport.from_build_system(self) 

581 self._build_target = attributes.get("build_target") 

582 self._test_target = attributes.get("test_target") 

583 self._install_target = attributes.get("install_target") 

584 

585 @classmethod 

586 def auto_detect_build_system( 

587 cls, 

588 source_root: VirtualPath, 

589 *args, 

590 **kwargs, 

591 ) -> bool: 

592 return any(p in source_root for p in ("Makefile", "makefile", "GNUmakefile")) 

593 

594 @classmethod 

595 def characteristics(cls) -> BuildSystemCharacteristics: 

596 return BuildSystemCharacteristics( 

597 out_of_source_builds="not-supported", 

598 ) 

599 

600 def configure_impl( 

601 self, 

602 context: "BuildContext", 

603 manifest: "HighLevelManifest", 

604 **kwargs, 

605 ) -> None: 

606 # No configure step 

607 pass 

608 

609 def build_impl( 

610 self, 

611 context: "BuildContext", 

612 manifest: "HighLevelManifest", 

613 **kwargs, 

614 ) -> None: 

615 extra_vars = [] 

616 build_target = self._build_target 

617 if build_target is not None: 

618 extra_vars.append(build_target) 

619 if context.is_cross_compiling: 

620 for envvar, tool in _MAKE_DEFAULT_TOOLS: 

621 cross_tool = os.environ.get(envvar) 

622 if cross_tool is None: 

623 cross_tool = context.cross_tool(tool) 

624 extra_vars.append(f"{envvar}={cross_tool}") 

625 self._make_support.run_make( 

626 context, 

627 *extra_vars, 

628 "INSTALL=install --strip-program=true", 

629 directory=self._directory, 

630 ) 

631 

632 def test_impl( 

633 self, 

634 context: "BuildContext", 

635 manifest: "HighLevelManifest", 

636 *, 

637 should_ignore_test_errors: bool = False, 

638 **kwargs, 

639 ) -> None: 

640 self._run_make_maybe_explicit_target( 

641 context, 

642 self._test_target, 

643 ["test", "check"], 

644 ) 

645 

646 def install_impl( 

647 self, 

648 context: "BuildContext", 

649 manifest: "HighLevelManifest", 

650 dest_dir: str, 

651 **kwargs, 

652 ) -> None: 

653 self._run_make_maybe_explicit_target( 

654 context, 

655 self._install_target, 

656 ["install"], 

657 f"DESTDIR={dest_dir}", 

658 "AM_UPDATE_INFO_DIR=no", 

659 "INSTALL=install --strip-program=true", 

660 ) 

661 

662 def _run_make_maybe_explicit_target( 

663 self, 

664 context: "BuildContext", 

665 provided_target: str | None, 

666 fallback_targets: Sequence[str], 

667 *make_args: str, 

668 ) -> None: 

669 make_support = self._make_support 

670 if provided_target is not None: 

671 make_support.run_make( 

672 context, 

673 provided_target, 

674 *make_args, 

675 directory=self._directory, 

676 ) 

677 else: 

678 make_support.run_first_existing_target_if_any( 

679 context, 

680 fallback_targets, 

681 *make_args, 

682 directory=self._directory, 

683 ) 

684 

685 def clean_impl( 

686 self, 

687 context: "BuildContext", 

688 manifest: "HighLevelManifest", 

689 clean_helper: "CleanHelper", 

690 **kwargs, 

691 ) -> None: 

692 self._make_support.run_first_existing_target_if_any( 

693 context, 

694 ["distclean", "realclean", "clean"], 

695 ) 

696 

697 

698class PerlBuildBuildSystemRule(StepBasedBuildSystemRule): 

699 

700 __slots__ = "configure_args" 

701 

702 def __init__( 

703 self, 

704 attributes: "ParsedPerlBuildBuildRuleDefinition", 

705 attribute_path: AttributePath, 

706 parser_context: Union[ParserContextData, "HighLevelManifest"], 

707 ) -> None: 

708 super().__init__(attributes, attribute_path, parser_context) 

709 self.configure_args = attributes.get("configure_args", []) 

710 

711 @classmethod 

712 def auto_detect_build_system( 

713 cls, 

714 source_root: VirtualPath, 

715 *args, 

716 **kwargs, 

717 ) -> bool: 

718 return "Build.PL" in source_root 

719 

720 @classmethod 

721 def characteristics(cls) -> BuildSystemCharacteristics: 

722 return BuildSystemCharacteristics( 

723 out_of_source_builds="not-supported", 

724 ) 

725 

726 @staticmethod 

727 def _perl_cross_build_env( 

728 context: "BuildContext", 

729 ) -> tuple[PerlConfigVars, EnvironmentModification | None]: 

730 perl_config_data = resolve_perl_config( 

731 context.dpkg_architecture_variables, 

732 None, 

733 ) 

734 if context.is_cross_compiling: 

735 perl5lib_dir = perl_config_data.cross_inc_dir 

736 if perl5lib_dir is not None: 

737 env_perl5lib = os.environ.get("PERL5LIB") 

738 if env_perl5lib is not None: 

739 perl5lib_dir = ( 

740 perl5lib_dir + perl_config_data.path_sep + env_perl5lib 

741 ) 

742 env_mod = EnvironmentModification( 

743 replacements=(("PERL5LIB", perl5lib_dir),), 

744 ) 

745 return perl_config_data, env_mod 

746 return perl_config_data, None 

747 

748 def configure_impl( 

749 self, 

750 context: "BuildContext", 

751 manifest: "HighLevelManifest", 

752 **kwargs, 

753 ) -> None: 

754 perl_config_data, cross_env_mod = self._perl_cross_build_env(context) 

755 configure_env = EnvironmentModification( 

756 replacements=( 

757 ("PERL_MM_USE_DEFAULT", "1"), 

758 ("PKG_CONFIG", context.cross_tool("pkg-config")), 

759 ) 

760 ) 

761 if cross_env_mod is not None: 

762 configure_env = configure_env.combine(cross_env_mod) 

763 

764 configure_cmd = [ 

765 PERL_CMD, 

766 "Build.PL", 

767 "--installdirs", 

768 "vendor", 

769 ] 

770 cflags = os.environ.get("CFLAGS", "") 

771 cppflags = os.environ.get("CPPFLAGS", "") 

772 ldflags = os.environ.get("LDFLAGS", "") 

773 

774 if cflags != "" or cppflags != "": 

775 configure_cmd.append("--config") 

776 combined = f"{cflags} {cppflags}".strip() 

777 configure_cmd.append(f"optimize={combined}") 

778 

779 if ldflags != "" or cflags != "" or context.is_cross_compiling: 

780 configure_cmd.append("--config") 

781 combined = f"{perl_config_data.ld} {cflags} {ldflags}".strip() 

782 configure_cmd.append(f"ld={combined}") 

783 if self.configure_args: 

784 substitution = self.substitution 

785 attr_path = self.attribute_path["configure_args"] 

786 configure_cmd.extend( 

787 substitution.substitute(v, attr_path[i].path) 

788 for i, v in enumerate(self.configure_args) 

789 ) 

790 run_build_system_command(*configure_cmd, env_mod=configure_env) 

791 

792 def build_impl( 

793 self, 

794 context: "BuildContext", 

795 manifest: "HighLevelManifest", 

796 **kwargs, 

797 ) -> None: 

798 _, cross_env_mod = self._perl_cross_build_env(context) 

799 run_build_system_command(PERL_CMD, "Build", env_mod=cross_env_mod) 

800 

801 def test_impl( 

802 self, 

803 context: "BuildContext", 

804 manifest: "HighLevelManifest", 

805 *, 

806 should_ignore_test_errors: bool = False, 

807 **kwargs, 

808 ) -> None: 

809 _, cross_env_mod = self._perl_cross_build_env(context) 

810 run_build_system_command( 

811 PERL_CMD, 

812 "Build", 

813 "test", 

814 "--verbose", 

815 "1", 

816 env_mod=cross_env_mod, 

817 ) 

818 

819 def install_impl( 

820 self, 

821 context: "BuildContext", 

822 manifest: "HighLevelManifest", 

823 dest_dir: str, 

824 **kwargs, 

825 ) -> None: 

826 _, cross_env_mod = self._perl_cross_build_env(context) 

827 run_build_system_command( 

828 PERL_CMD, 

829 "Build", 

830 "install", 

831 "--destdir", 

832 dest_dir, 

833 "--create_packlist", 

834 "0", 

835 env_mod=cross_env_mod, 

836 ) 

837 

838 def clean_impl( 

839 self, 

840 context: "BuildContext", 

841 manifest: "HighLevelManifest", 

842 clean_helper: "CleanHelper", 

843 **kwargs, 

844 ) -> None: 

845 _, cross_env_mod = self._perl_cross_build_env(context) 

846 if os.path.lexists("Build"): 

847 run_build_system_command( 

848 PERL_CMD, 

849 "Build", 

850 "realclean", 

851 "--allow_mb_mismatch", 

852 "1", 

853 env_mod=cross_env_mod, 

854 ) 

855 

856 

857class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule): 

858 

859 __slots__ = ("configure_args", "_make_support") 

860 

861 def __init__( 

862 self, 

863 attributes: "ParsedPerlBuildBuildRuleDefinition", 

864 attribute_path: AttributePath, 

865 parser_context: Union[ParserContextData, "HighLevelManifest"], 

866 ) -> None: 

867 super().__init__(attributes, attribute_path, parser_context) 

868 self.configure_args = attributes.get("configure_args", []) 

869 self._make_support = MakefileSupport.from_build_system(self) 

870 

871 @classmethod 

872 def auto_detect_build_system( 

873 cls, 

874 source_root: VirtualPath, 

875 *args, 

876 **kwargs, 

877 ) -> bool: 

878 return "Makefile.PL" in source_root 

879 

880 @classmethod 

881 def characteristics(cls) -> BuildSystemCharacteristics: 

882 return BuildSystemCharacteristics( 

883 out_of_source_builds="not-supported", 

884 ) 

885 

886 def configure_impl( 

887 self, 

888 context: "BuildContext", 

889 manifest: "HighLevelManifest", 

890 **kwargs, 

891 ) -> None: 

892 configure_env = EnvironmentModification( 

893 replacements=( 

894 ("PERL_MM_USE_DEFAULT", "1"), 

895 ("PERL_AUTOINSTALL", "--skipdeps"), 

896 ("PKG_CONFIG", context.cross_tool("pkg-config")), 

897 ) 

898 ) 

899 perl_args = [] 

900 mm_args = ["INSTALLDIRS=vendor"] 

901 if "CFLAGS" in os.environ: 

902 mm_args.append( 

903 f"OPTIMIZE={os.environ['CFLAGS']} {os.environ['CPPFLAGS']}".rstrip() 

904 ) 

905 

906 perl_config_data = resolve_perl_config( 

907 context.dpkg_architecture_variables, 

908 None, 

909 ) 

910 

911 if "LDFLAGS" in os.environ: 

912 mm_args.append( 

913 f"LD={perl_config_data.ld} {os.environ['CFLAGS']} {os.environ['LDFLAGS']}" 

914 ) 

915 

916 if context.is_cross_compiling: 

917 perl5lib_dir = perl_config_data.cross_inc_dir 

918 if perl5lib_dir is not None: 

919 perl_args.append(f"-I{perl5lib_dir}") 

920 

921 if self.configure_args: 

922 substitution = self.substitution 

923 attr_path = self.attribute_path["configure_args"] 

924 mm_args.extend( 

925 substitution.substitute(v, attr_path[i].path) 

926 for i, v in enumerate(self.configure_args) 

927 ) 

928 run_build_system_command( 

929 PERL_CMD, 

930 *perl_args, 

931 "Makefile.PL", 

932 *mm_args, 

933 env_mod=configure_env, 

934 ) 

935 

936 def build_impl( 

937 self, 

938 context: "BuildContext", 

939 manifest: "HighLevelManifest", 

940 **kwargs, 

941 ) -> None: 

942 self._make_support.run_make(context) 

943 

944 def test_impl( 

945 self, 

946 context: "BuildContext", 

947 manifest: "HighLevelManifest", 

948 *, 

949 should_ignore_test_errors: bool = False, 

950 **kwargs, 

951 ) -> None: 

952 self._make_support.run_first_existing_target_if_any( 

953 context, 

954 ["check", "test"], 

955 "TEST_VERBOSE=1", 

956 ) 

957 

958 def install_impl( 

959 self, 

960 context: "BuildContext", 

961 manifest: "HighLevelManifest", 

962 dest_dir: str, 

963 **kwargs, 

964 ) -> None: 

965 is_mm_makefile = False 

966 with open("Makefile", "rb") as fd: 

967 for line in fd: 

968 if b"generated automatically by MakeMaker" in line: 

969 is_mm_makefile = True 

970 break 

971 

972 install_args = [f"DESTDIR={dest_dir}"] 

973 

974 # Special case for Makefile.PL that uses 

975 # Module::Build::Compat. PREFIX should not be passed 

976 # for those; it already installs into /usr by default. 

977 if is_mm_makefile: 

978 install_args.append("PREFIX=/usr") 

979 

980 self._make_support.run_first_existing_target_if_any( 

981 context, 

982 ["install"], 

983 *install_args, 

984 ) 

985 

986 def clean_impl( 

987 self, 

988 context: "BuildContext", 

989 manifest: "HighLevelManifest", 

990 clean_helper: "CleanHelper", 

991 **kwargs, 

992 ) -> None: 

993 self._make_support.run_first_existing_target_if_any( 

994 context, 

995 ["distclean", "realclean", "clean"], 

996 ) 

997 

998 

999class DebhelperBuildSystemRule(StepBasedBuildSystemRule): 

1000 

1001 __slots__ = ("configure_args", "dh_build_system") 

1002 

1003 def __init__( 

1004 self, 

1005 parsed_data: "ParsedDebhelperBuildRuleDefinition", 

1006 attribute_path: AttributePath, 

1007 parser_context: Union[ParserContextData, "HighLevelManifest"], 

1008 ) -> None: 

1009 super().__init__(parsed_data, attribute_path, parser_context) 

1010 self.configure_args = parsed_data.get("configure_args", []) 

1011 self.dh_build_system = parsed_data.get("dh_build_system") 

1012 

1013 @classmethod 

1014 def auto_detect_build_system( 

1015 cls, 

1016 source_root: VirtualPath, 

1017 *args, 

1018 **kwargs, 

1019 ) -> bool: 

1020 try: 

1021 v = subprocess.check_output( 

1022 ["dh_assistant", "which-build-system"], 

1023 # Packages without `debhelper-compat` will trigger an error, which will just be noise 

1024 stderr=subprocess.DEVNULL, 

1025 cwd=source_root.fs_path, 

1026 ) 

1027 except subprocess.CalledProcessError: 

1028 return False 

1029 d = json.loads(v) 

1030 build_system = d.get("build-system") 

1031 return build_system is not None and build_system != "none" 

1032 

1033 @classmethod 

1034 def characteristics(cls) -> BuildSystemCharacteristics: 

1035 return BuildSystemCharacteristics( 

1036 out_of_source_builds="supported-but-not-default", 

1037 ) 

1038 

1039 def before_first_impl_step( 

1040 self, *, stage: Literal["build", "clean"], **kwargs 

1041 ) -> None: 

1042 dh_build_system = self.dh_build_system 

1043 if dh_build_system is None: 

1044 return 

1045 try: 

1046 subprocess.check_call( 

1047 ["dh_assistant", "which-build-system", f"-S{dh_build_system}"] 

1048 ) 

1049 except FileNotFoundError: 

1050 _error( 

1051 "The debhelper build system assumes `dh_assistant` is available (`debhelper (>= 13.5~)`)" 

1052 ) 

1053 except subprocess.SubprocessError: 

1054 raise ManifestInvalidUserDataException( 

1055 f'The debhelper build system "{dh_build_system}" does not seem to' 

1056 f" be available according to" 

1057 f" `dh_assistant which-build-system -S{dh_build_system}`" 

1058 ) from None 

1059 

1060 def _default_options(self) -> list[str]: 

1061 default_options = [] 

1062 if self.dh_build_system is not None: 

1063 default_options.append(f"-S{self.dh_build_system}") 

1064 if self.build_directory is not None: 

1065 default_options.append(f"-B{self.build_directory}") 

1066 

1067 return default_options 

1068 

1069 def configure_impl( 

1070 self, 

1071 context: "BuildContext", 

1072 manifest: "HighLevelManifest", 

1073 **kwargs, 

1074 ) -> None: 

1075 if ( 

1076 os.path.lexists("configure.ac") or os.path.lexists("configure.in") 

1077 ) and not os.path.lexists("debian/autoreconf.before"): 

1078 run_build_system_command("dh_update_autotools_config") 

1079 run_build_system_command("dh_autoreconf") 

1080 

1081 default_options = self._default_options() 

1082 configure_args = default_options.copy() 

1083 if self.configure_args: 

1084 configure_args.append("--") 

1085 substitution = self.substitution 

1086 attr_path = self.attribute_path["configure_args"] 

1087 configure_args.extend( 

1088 substitution.substitute(v, attr_path[i].path) 

1089 for i, v in enumerate(self.configure_args) 

1090 ) 

1091 run_build_system_command("dh_auto_configure", *configure_args) 

1092 

1093 def build_impl( 

1094 self, 

1095 context: "BuildContext", 

1096 manifest: "HighLevelManifest", 

1097 **kwargs, 

1098 ) -> None: 

1099 default_options = self._default_options() 

1100 run_build_system_command("dh_auto_build", *default_options) 

1101 

1102 def test_impl( 

1103 self, 

1104 context: "BuildContext", 

1105 manifest: "HighLevelManifest", 

1106 *, 

1107 should_ignore_test_errors: bool = False, 

1108 **kwargs, 

1109 ) -> None: 

1110 default_options = self._default_options() 

1111 run_build_system_command("dh_auto_test", *default_options) 

1112 

1113 def install_impl( 

1114 self, 

1115 context: "BuildContext", 

1116 manifest: "HighLevelManifest", 

1117 dest_dir: str, 

1118 **kwargs, 

1119 ) -> None: 

1120 default_options = self._default_options() 

1121 run_build_system_command( 

1122 "dh_auto_install", 

1123 *default_options, 

1124 f"--destdir={dest_dir}", 

1125 ) 

1126 

1127 def clean_impl( 

1128 self, 

1129 context: "BuildContext", 

1130 manifest: "HighLevelManifest", 

1131 clean_helper: "CleanHelper", 

1132 **kwargs, 

1133 ) -> None: 

1134 default_options = self._default_options() 

1135 run_build_system_command("dh_auto_clean", *default_options) 

1136 # The "global" clean logic takes care of `dh_autoreconf_clean` and `dh_clean` 

1137 

1138 

1139class AutoconfBuildSystemRule(StepBasedBuildSystemRule): 

1140 

1141 __slots__ = ("configure_args", "_make_support") 

1142 

1143 def __init__( 

1144 self, 

1145 parsed_data: "ParsedAutoconfBuildRuleDefinition", 

1146 attribute_path: AttributePath, 

1147 parser_context: Union[ParserContextData, "HighLevelManifest"], 

1148 ) -> None: 

1149 super().__init__(parsed_data, attribute_path, parser_context) 

1150 self.configure_args = list(parsed_data.get("configure_args", [])) 

1151 self._make_support = MakefileSupport.from_build_system(self) 

1152 

1153 @classmethod 

1154 def characteristics(cls) -> BuildSystemCharacteristics: 

1155 return BuildSystemCharacteristics( 

1156 out_of_source_builds="supported-and-default", 

1157 ) 

1158 

1159 @classmethod 

1160 def auto_detect_build_system( 

1161 cls, 

1162 source_root: VirtualPath, 

1163 *args, 

1164 **kwargs, 

1165 ) -> bool: 

1166 if "configure.ac" in source_root: 

1167 return True 

1168 configure_in = source_root.get("configure.in") 

1169 if configure_in is not None and configure_in.is_file: 

1170 with configure_in.open(byte_io=True, buffering=4096) as fd: 

1171 for no, line in enumerate(fd): 

1172 if no > 100: 1172 ↛ 1173line 1172 didn't jump to line 1173 because the condition on line 1172 was never true

1173 break 

1174 if b"AC_INIT" in line or b"AC_PREREQ" in line: 

1175 return True 

1176 configure = source_root.get("configure") 

1177 if configure is None or not configure.is_executable or not configure.is_file: 

1178 return False 

1179 with configure.open(byte_io=True, buffering=4096) as fd: 

1180 for no, line in enumerate(fd): 

1181 if no > 10: 1181 ↛ 1182line 1181 didn't jump to line 1182 because the condition on line 1181 was never true

1182 break 

1183 if b"GNU Autoconf" in line: 

1184 return True 

1185 return False 

1186 

1187 def configure_impl( 

1188 self, 

1189 context: "BuildContext", 

1190 manifest: "HighLevelManifest", 

1191 **kwargs, 

1192 ) -> None: 

1193 if ( 

1194 os.path.lexists("configure.ac") or os.path.lexists("configure.in") 

1195 ) and not os.path.lexists("debian/autoreconf.before"): 

1196 run_build_system_command("dh_update_autotools_config") 

1197 run_build_system_command("dh_autoreconf") 

1198 

1199 dpkg_architecture_variables = context.dpkg_architecture_variables 

1200 multi_arch = dpkg_architecture_variables.current_host_multiarch 

1201 silent_rules = ( 

1202 "--enable-silent-rules" 

1203 if context.is_terse_build 

1204 else "--disable-silent-rules" 

1205 ) 

1206 

1207 configure_args = [ 

1208 f"--build={dpkg_architecture_variables['DEB_BUILD_GNU_TYPE']}", 

1209 "--prefix=/usr", 

1210 "--includedir=${prefix}/include", 

1211 "--mandir=${prefix}/share/man", 

1212 "--infodir=${prefix}/share/info", 

1213 "--sysconfdir=/etc", 

1214 "--localstatedir=/var", 

1215 "--disable-option-checking", 

1216 silent_rules, 

1217 f"--libdir=${ prefix} /lib/{multi_arch}", 

1218 "--runstatedir=/run", 

1219 "--disable-maintainer-mode", 

1220 "--disable-dependency-tracking", 

1221 ] 

1222 if dpkg_architecture_variables.is_cross_compiling: 

1223 configure_args.append( 

1224 f"--host={dpkg_architecture_variables['DEB_HOST_GNU_TYPE']}" 

1225 ) 

1226 if self.configure_args: 

1227 substitution = self.substitution 

1228 attr_path = self.attribute_path["configure_args"] 

1229 configure_args.extend( 

1230 substitution.substitute(v, attr_path[i].path) 

1231 for i, v in enumerate(self.configure_args) 

1232 ) 

1233 self.ensure_build_dir_exists() 

1234 configure_script = self.relative_from_builddir_to_source("configure") 

1235 with self.dump_logs_on_error("config.log"): 

1236 run_build_system_command( 

1237 configure_script, 

1238 *configure_args, 

1239 cwd=self.build_directory, 

1240 ) 

1241 

1242 def build_impl( 

1243 self, 

1244 context: "BuildContext", 

1245 manifest: "HighLevelManifest", 

1246 **kwargs, 

1247 ) -> None: 

1248 self._make_support.run_make(context) 

1249 

1250 def test_impl( 

1251 self, 

1252 context: "BuildContext", 

1253 manifest: "HighLevelManifest", 

1254 *, 

1255 should_ignore_test_errors: bool = False, 

1256 **kwargs, 

1257 ) -> None: 

1258 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

1259 testsuite_flags = [f"-j{limit}"] if limit else ["-j"] 

1260 

1261 if not context.is_terse_build: 

1262 testsuite_flags.append("--verbose") 

1263 self._make_support.run_first_existing_target_if_any( 

1264 context, 

1265 # Order is deliberately inverse compared to debhelper (#924052) 

1266 ["check", "test"], 

1267 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}", 

1268 "VERBOSE=1", 

1269 ) 

1270 

1271 def install_impl( 

1272 self, 

1273 context: "BuildContext", 

1274 manifest: "HighLevelManifest", 

1275 dest_dir: str, 

1276 **kwargs, 

1277 ) -> None: 

1278 enable_parallelization = not os.path.lexists(self.build_dir_path("libtool")) 

1279 self._make_support.run_first_existing_target_if_any( 

1280 context, 

1281 ["install"], 

1282 f"DESTDIR={dest_dir}", 

1283 "AM_UPDATE_INFO_DIR=no", 

1284 enable_parallelization=enable_parallelization, 

1285 ) 

1286 

1287 def clean_impl( 

1288 self, 

1289 context: "BuildContext", 

1290 manifest: "HighLevelManifest", 

1291 clean_helper: "CleanHelper", 

1292 **kwargs, 

1293 ) -> None: 

1294 if self.out_of_source_build: 

1295 return 

1296 self._make_support.run_first_existing_target_if_any( 

1297 context, 

1298 ["distclean", "realclean", "clean"], 

1299 ) 

1300 # The "global" clean logic takes care of `dh_autoreconf_clean` and `dh_clean` 

1301 

1302 

1303class CMakeBuildSystemRule(StepBasedBuildSystemRule): 

1304 

1305 __slots__ = ( 

1306 "configure_args", 

1307 "target_build_system", 

1308 "_make_support", 

1309 "_ninja_support", 

1310 ) 

1311 

1312 def __init__( 

1313 self, 

1314 parsed_data: "ParsedCMakeBuildRuleDefinition", 

1315 attribute_path: AttributePath, 

1316 parser_context: Union[ParserContextData, "HighLevelManifest"], 

1317 ) -> None: 

1318 super().__init__(parsed_data, attribute_path, parser_context) 

1319 self.configure_args = list(parsed_data.get("configure_args", [])) 

1320 self.target_build_system: Literal["make", "ninja"] = parsed_data.get( 

1321 "target_build_system", "make" 

1322 ) 

1323 self._make_support = MakefileSupport.from_build_system(self) 

1324 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1325 

1326 @classmethod 

1327 def characteristics(cls) -> BuildSystemCharacteristics: 

1328 return BuildSystemCharacteristics( 

1329 out_of_source_builds="required", 

1330 ) 

1331 

1332 @classmethod 

1333 def auto_detect_build_system( 

1334 cls, 

1335 source_root: VirtualPath, 

1336 *args, 

1337 **kwargs, 

1338 ) -> bool: 

1339 return "CMakeLists.txt" in source_root 

1340 

1341 @staticmethod 

1342 def _default_cmake_env( 

1343 build_context: "BuildContext", 

1344 ) -> EnvironmentModification: 

1345 replacements = {} 

1346 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1347 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1348 if "PKG_CONFIG" not in os.environ: 

1349 pkg_config = build_context.cross_tool("pkg-config") 

1350 replacements["PKG_CONFIG"] = f"/usr/bin/{pkg_config}" 

1351 return EnvironmentModification( 

1352 replacements=tuple((k, v) for k, v in replacements.items()) 

1353 ) 

1354 

1355 @classmethod 

1356 def cmake_generator(cls, target_build_system: Literal["make", "ninja"]) -> str: 

1357 cmake_generators = { 

1358 "make": "Unix Makefiles", 

1359 "ninja": "Ninja", 

1360 } 

1361 return cmake_generators[target_build_system] 

1362 

1363 @staticmethod 

1364 def _compiler_and_cross_flags( 

1365 context: "BuildContext", 

1366 cmake_flags: list[str], 

1367 ) -> None: 

1368 

1369 if "CC" in os.environ: 

1370 cmake_flags.append(f"-DCMAKE_C_COMPILER={os.environ['CC']}") 

1371 elif context.is_cross_compiling: 

1372 cmake_flags.append(f"-DCMAKE_C_COMPILER={context.cross_tool('gcc')}") 

1373 

1374 if "CXX" in os.environ: 

1375 cmake_flags.append(f"-DCMAKE_CXX_COMPILER={os.environ['CXX']}") 

1376 elif context.is_cross_compiling: 

1377 cmake_flags.append(f"-DCMAKE_CXX_COMPILER={context.cross_tool('g++')}") 

1378 

1379 if context.is_cross_compiling: 

1380 deb_host2cmake_system = { 

1381 "linux": "Linux", 

1382 "kfreebsd": "kFreeBSD", 

1383 "hurd": "GNU", 

1384 } 

1385 

1386 gnu_cpu2system_processor = { 

1387 "arm": "armv7l", 

1388 "misp64el": "mips64", 

1389 "powerpc64le": "ppc64le", 

1390 } 

1391 dpkg_architecture_variables = context.dpkg_architecture_variables 

1392 

1393 try: 

1394 system_name = deb_host2cmake_system[ 

1395 dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1396 ] 

1397 except KeyError as e: 

1398 name = e.args[0] 

1399 _error( 

1400 f"Cannot cross-compile via cmake: Missing CMAKE_SYSTEM_NAME for the DEB_HOST_ARCH_OS {name}" 

1401 ) 

1402 

1403 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"] 

1404 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu) 

1405 

1406 cmake_flags.append(f"-DCMAKE_SYSTEM_NAME={system_name}") 

1407 cmake_flags.append(f"-DCMAKE_SYSTEM_PROCESSOR={system_processor}") 

1408 

1409 pkg_config = context.cross_tool("pkg-config") 

1410 # Historical uses. Current versions of cmake uses the env variable instead. 

1411 cmake_flags.append(f"-DPKG_CONFIG_EXECUTABLE=/usr/bin/{pkg_config}") 

1412 cmake_flags.append(f"-DPKGCONFIG_EXECUTABLE=/usr/bin/{pkg_config}") 

1413 cmake_flags.append( 

1414 f"-DQMAKE_EXECUTABLE=/usr/bin/{context.cross_tool('qmake')}" 

1415 ) 

1416 

1417 def configure_impl( 

1418 self, 

1419 context: "BuildContext", 

1420 manifest: "HighLevelManifest", 

1421 **kwargs, 

1422 ) -> None: 

1423 cmake_flags = [ 

1424 "-DCMAKE_INSTALL_PREFIX=/usr", 

1425 "-DCMAKE_BUILD_TYPE=None", 

1426 "-DCMAKE_INSTALL_SYSCONFDIR=/etc", 

1427 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var", 

1428 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON", 

1429 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", 

1430 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON", 

1431 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON", 

1432 "-DCMAKE_INSTALL_RUNSTATEDIR=/run", 

1433 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON", 

1434 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON", 

1435 f"-DCMAKE_INSTALL_LIBDIR=lib/{context.dpkg_architecture_variables['DEB_HOST_MULTIARCH']}", 

1436 f"-G{self.cmake_generator(self.target_build_system)}", 

1437 ] 

1438 if not context.is_terse_build: 

1439 cmake_flags.append("-DCMAKE_VERBOSE_MAKEFILE=ON") 

1440 if not context.should_run_tests: 

1441 cmake_flags.append("-DBUILD_TESTING:BOOL=OFF") 

1442 

1443 self._compiler_and_cross_flags(context, cmake_flags) 

1444 

1445 if self.configure_args: 

1446 substitution = self.substitution 

1447 attr_path = self.attribute_path["configure_args"] 

1448 cmake_flags.extend( 

1449 substitution.substitute(v, attr_path[i].path) 

1450 for i, v in enumerate(self.configure_args) 

1451 ) 

1452 

1453 env_mod = self._default_cmake_env(context) 

1454 if "CPPFLAGS" in os.environ: 

1455 # CMake doesn't respect CPPFLAGS, see #653916. 

1456 cppflags = os.environ["CPPFLAGS"] 

1457 cflags = f"{os.environ.get('CFLAGS', '')} {cppflags}".lstrip() 

1458 cxxflags = f"{os.environ.get('CXXFLAGS', '')} {cppflags}".lstrip() 

1459 env_mod = env_mod.combine( 

1460 # The debhelper build system never showed this delta, so people might find it annoying. 

1461 EnvironmentModification( 

1462 replacements=( 

1463 ("CFLAGS", cflags), 

1464 ("CXXFLAGS", cxxflags), 

1465 ) 

1466 ) 

1467 ) 

1468 if "ASMFLAGS" not in os.environ and "ASFLAGS" in os.environ: 

1469 env_mod = env_mod.combine( 

1470 # The debhelper build system never showed this delta, so people might find it annoying. 

1471 EnvironmentModification( 

1472 replacements=(("ASMFLAGS", os.environ["ASFLAGS"]),), 

1473 ) 

1474 ) 

1475 self.ensure_build_dir_exists() 

1476 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1477 

1478 with self.dump_logs_on_error( 

1479 "CMakeCache.txt", 

1480 "CMakeFiles/CMakeOutput.log", 

1481 "CMakeFiles/CMakeError.log", 

1482 ): 

1483 run_build_system_command( 

1484 "cmake", 

1485 *cmake_flags, 

1486 source_dir_from_build_dir, 

1487 cwd=self.build_directory, 

1488 env_mod=env_mod, 

1489 ) 

1490 

1491 def build_impl( 

1492 self, 

1493 context: "BuildContext", 

1494 manifest: "HighLevelManifest", 

1495 **kwargs, 

1496 ) -> None: 

1497 if self.target_build_system == "make": 

1498 make_flags = [] 

1499 if not context.is_terse_build: 

1500 make_flags.append("VERBOSE=1") 

1501 self._make_support.run_make(context, *make_flags) 

1502 else: 

1503 self._ninja_support.run_ninja_build(context) 

1504 

1505 def test_impl( 

1506 self, 

1507 context: "BuildContext", 

1508 manifest: "HighLevelManifest", 

1509 *, 

1510 should_ignore_test_errors: bool = False, 

1511 **kwargs, 

1512 ) -> None: 

1513 env_mod = EnvironmentModification( 

1514 replacements=(("CTEST_OUTPUT_ON_FAILURE", "1"),), 

1515 ) 

1516 if self.target_build_system == "make": 

1517 # Unlike make, CTest does not have "unlimited parallel" setting (-j implies 

1518 # -j1). Therefore, we do not set "allow zero as unlimited" here. 

1519 make_flags = [f"ARGS+=-j{context.parallelization_limit()}"] 

1520 if not context.is_terse_build: 

1521 make_flags.append("ARGS+=--verbose") 

1522 self._make_support.run_first_existing_target_if_any( 

1523 context, 

1524 ["check", "test"], 

1525 *make_flags, 

1526 env_mod=env_mod, 

1527 ) 

1528 else: 

1529 self._ninja_support.run_ninja_test(context, env_mod=env_mod) 

1530 

1531 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

1532 testsuite_flags = [f"-j{limit}"] if limit else ["-j"] 

1533 

1534 if not context.is_terse_build: 

1535 testsuite_flags.append("--verbose") 

1536 self._make_support.run_first_existing_target_if_any( 

1537 context, 

1538 # Order is deliberately inverse compared to debhelper (#924052) 

1539 ["check", "test"], 

1540 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}", 

1541 "VERBOSE=1", 

1542 ) 

1543 

1544 def install_impl( 

1545 self, 

1546 context: "BuildContext", 

1547 manifest: "HighLevelManifest", 

1548 dest_dir: str, 

1549 **kwargs, 

1550 ) -> None: 

1551 env_mod = EnvironmentModification( 

1552 replacements=( 

1553 ("LC_ALL", "C.UTF-8"), 

1554 ("DESTDIR", dest_dir), 

1555 ) 

1556 ).combine(self._default_cmake_env(context)) 

1557 run_build_system_command( 

1558 "cmake", 

1559 "--install", 

1560 self.build_directory, 

1561 env_mod=env_mod, 

1562 ) 

1563 

1564 def clean_impl( 

1565 self, 

1566 context: "BuildContext", 

1567 manifest: "HighLevelManifest", 

1568 clean_helper: "CleanHelper", 

1569 **kwargs, 

1570 ) -> None: 

1571 if self.out_of_source_build: 

1572 return 

1573 if self.target_build_system == "make": 

1574 # Keep it here in case we change the `required` "out of source" to "supported-default" 

1575 self._make_support.run_first_existing_target_if_any( 

1576 context, 

1577 ["distclean", "realclean", "clean"], 

1578 ) 

1579 else: 

1580 self._ninja_support.run_ninja_clean(context) 

1581 

1582 

1583class MesonBuildSystemRule(StepBasedBuildSystemRule): 

1584 

1585 __slots__ = ( 

1586 "configure_args", 

1587 "_ninja_support", 

1588 ) 

1589 

1590 def __init__( 

1591 self, 

1592 parsed_data: "ParsedMesonBuildRuleDefinition", 

1593 attribute_path: AttributePath, 

1594 parser_context: Union[ParserContextData, "HighLevelManifest"], 

1595 ) -> None: 

1596 super().__init__(parsed_data, attribute_path, parser_context) 

1597 self.configure_args = list(parsed_data.get("configure_args", [])) 

1598 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1599 

1600 @classmethod 

1601 def characteristics(cls) -> BuildSystemCharacteristics: 

1602 return BuildSystemCharacteristics( 

1603 out_of_source_builds="required", 

1604 ) 

1605 

1606 @classmethod 

1607 def auto_detect_build_system( 

1608 cls, 

1609 source_root: VirtualPath, 

1610 *args, 

1611 **kwargs, 

1612 ) -> bool: 

1613 return "meson.build" in source_root 

1614 

1615 @staticmethod 

1616 def _default_meson_env() -> EnvironmentModification: 

1617 replacements = { 

1618 "LC_ALL": "C.UTF-8", 

1619 } 

1620 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1621 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1622 return EnvironmentModification( 

1623 replacements=tuple((k, v) for k, v in replacements.items()) 

1624 ) 

1625 

1626 @classmethod 

1627 def cmake_generator(cls, target_build_system: Literal["make", "ninja"]) -> str: 

1628 cmake_generators = { 

1629 "make": "Unix Makefiles", 

1630 "ninja": "Ninja", 

1631 } 

1632 return cmake_generators[target_build_system] 

1633 

1634 @staticmethod 

1635 def _cross_flags( 

1636 context: "BuildContext", 

1637 meson_flags: list[str], 

1638 ) -> None: 

1639 if not context.is_cross_compiling: 

1640 return 

1641 # Needs a cross-file http://mesonbuild.com/Cross-compilation.html 

1642 cross_files_dir = os.path.abspath( 

1643 generated_content_dir( 

1644 subdir_key="meson-cross-files", 

1645 ) 

1646 ) 

1647 host_arch = context.dpkg_architecture_variables.current_host_arch 

1648 cross_file = os.path.join(cross_files_dir, f"meson-cross-file-{host_arch}.conf") 

1649 if not os.path.isfile(cross_file): 

1650 subprocess.check_call( 

1651 [ 

1652 "/usr/share/meson/debcrossgen", 

1653 f"--arch={host_arch}", 

1654 f"-o{cross_file}", 

1655 ], 

1656 stdout=subprocess.DEVNULL, 

1657 env=collections.ChainMap({"LC_ALL": "C.UTF-8"}, os.environ), 

1658 ) 

1659 

1660 meson_flags.append("--cross-file") 

1661 meson_flags.append(cross_file) 

1662 

1663 def configure_impl( 

1664 self, 

1665 context: "BuildContext", 

1666 manifest: "HighLevelManifest", 

1667 **kwargs, 

1668 ) -> None: 

1669 meson_version = Version( 

1670 subprocess.check_output( 

1671 ["meson", "--version"], 

1672 encoding="utf-8", 

1673 ).strip() 

1674 ) 

1675 dpkg_architecture_variables = context.dpkg_architecture_variables 

1676 

1677 meson_flags = [ 

1678 "--wrap-mode=nodownload", 

1679 "--buildtype=plain", 

1680 "--prefix=/usr", 

1681 "--sysconfdir=/etc", 

1682 "--localstatedir=/var", 

1683 f"--libdir=lib/{dpkg_architecture_variables.current_host_multiarch}", 

1684 "--auto-features=enabled", 

1685 ] 

1686 if meson_version >= Version("1.2.0"): 

1687 # There was a behavior change in Meson 1.2.0: previously 

1688 # byte-compilation wasn't supported, but since 1.2.0 it is on by 

1689 # default. We can only use this option to turn it off in versions 

1690 # where the option exists. 

1691 meson_flags.append("-Dpython.bytecompile=-1") 

1692 

1693 self._cross_flags(context, meson_flags) 

1694 

1695 if self.configure_args: 

1696 substitution = self.substitution 

1697 attr_path = self.attribute_path["configure_args"] 

1698 meson_flags.extend( 

1699 substitution.substitute(v, attr_path[i].path) 

1700 for i, v in enumerate(self.configure_args) 

1701 ) 

1702 

1703 env_mod = self._default_meson_env() 

1704 

1705 self.ensure_build_dir_exists() 

1706 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1707 

1708 with self.dump_logs_on_error("meson-logs/meson-log.txt"): 

1709 run_build_system_command( 

1710 "meson", 

1711 "setup", 

1712 source_dir_from_build_dir, 

1713 *meson_flags, 

1714 cwd=self.build_directory, 

1715 env_mod=env_mod, 

1716 ) 

1717 

1718 def build_impl( 

1719 self, 

1720 context: "BuildContext", 

1721 manifest: "HighLevelManifest", 

1722 **kwargs, 

1723 ) -> None: 

1724 self._ninja_support.run_ninja_build(context) 

1725 

1726 def test_impl( 

1727 self, 

1728 context: "BuildContext", 

1729 manifest: "HighLevelManifest", 

1730 *, 

1731 should_ignore_test_errors: bool = False, 

1732 **kwargs, 

1733 ) -> None: 

1734 env_mod = EnvironmentModification( 

1735 replacements=(("MESON_TESTTHREADS", f"{context.parallelization_limit()}"),), 

1736 ).combine(self._default_meson_env()) 

1737 meson_args = [] 

1738 if not context.is_terse_build: 

1739 meson_args.append("--verbose") 

1740 with self.dump_logs_on_error("meson-logs/testlog.txt"): 

1741 run_build_system_command( 

1742 "meson", 

1743 "test", 

1744 *meson_args, 

1745 env_mod=env_mod, 

1746 cwd=self.build_directory, 

1747 ) 

1748 

1749 def install_impl( 

1750 self, 

1751 context: "BuildContext", 

1752 manifest: "HighLevelManifest", 

1753 dest_dir: str, 

1754 **kwargs, 

1755 ) -> None: 

1756 run_build_system_command( 

1757 "meson", 

1758 "install", 

1759 "--destdir", 

1760 dest_dir, 

1761 cwd=self.build_directory, 

1762 env_mod=self._default_meson_env(), 

1763 ) 

1764 

1765 def clean_impl( 

1766 self, 

1767 context: "BuildContext", 

1768 manifest: "HighLevelManifest", 

1769 clean_helper: "CleanHelper", 

1770 **kwargs, 

1771 ) -> None: 

1772 # `debputy` will handle all the cleanup for us by virtue of "out of source build" 

1773 assert self.out_of_source_build 

1774 

1775 

1776def _add_qmake_flag(options: list[str], envvar: str, *, include_cppflags: bool) -> None: 

1777 value = os.environ.get(envvar) 

1778 if value is None: 

1779 return 

1780 if include_cppflags: 

1781 cppflags = os.environ.get("CPPFLAGS") 

1782 if cppflags: 

1783 value = f"{value} {cppflags}" 

1784 

1785 options.append(f"QMAKE_{envvar}_RELEASE={value}") 

1786 options.append(f"QMAKE_{envvar}_DEBUG={value}") 

1787 

1788 

1789class ParsedGenericQmakeBuildRuleDefinition( 

1790 OptionalInstallDirectly, 

1791 OptionalInSourceBuild, 

1792 OptionalBuildDirectory, 

1793 OptionalTestRule, 

1794): 

1795 configure_args: NotRequired[list[str]] 

1796 

1797 

1798class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule): 

1799 

1800 __slots__ = ("configure_args", "_make_support") 

1801 

1802 def __init__( 

1803 self, 

1804 parsed_data: "ParsedGenericQmakeBuildRuleDefinition", 

1805 attribute_path: AttributePath, 

1806 parser_context: Union[ParserContextData, "HighLevelManifest"], 

1807 ) -> None: 

1808 super().__init__(parsed_data, attribute_path, parser_context) 

1809 self.configure_args = list(parsed_data.get("configure_args", [])) 

1810 self._make_support = MakefileSupport.from_build_system(self) 

1811 

1812 @classmethod 

1813 def characteristics(cls) -> BuildSystemCharacteristics: 

1814 return BuildSystemCharacteristics( 

1815 out_of_source_builds="supported-and-default", 

1816 ) 

1817 

1818 @classmethod 

1819 def auto_detect_build_system( 

1820 cls, 

1821 source_root: VirtualPath, 

1822 *args, 

1823 **kwargs, 

1824 ) -> bool: 

1825 return any(p.name.endswith(".pro") for p in source_root.iterdir()) 

1826 

1827 @classmethod 

1828 def os_mkspec_mapping(cls) -> Mapping[str, str]: 

1829 return { 

1830 "linux": "linux-g++", 

1831 "kfreebsd": "gnukfreebsd-g++", 

1832 "hurd": "hurd-g++", 

1833 } 

1834 

1835 def qmake_command(self) -> str: 

1836 raise NotImplementedError 

1837 

1838 def configure_impl( 

1839 self, 

1840 context: "BuildContext", 

1841 manifest: "HighLevelManifest", 

1842 **kwargs, 

1843 ) -> None: 

1844 

1845 configure_args = [ 

1846 "-makefile", 

1847 ] 

1848 qmake_cmd = context.cross_tool(self.qmake_command()) 

1849 

1850 if context.is_cross_compiling: 

1851 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1852 os2mkspec = self.os_mkspec_mapping() 

1853 try: 

1854 spec = os2mkspec[host_os] 

1855 except KeyError: 

1856 _error( 

1857 f'Sorry, `debputy` cannot cross build this package for "{host_os}".' 

1858 f' Missing a "DEB OS -> qmake -spec <VALUE>" mapping.' 

1859 ) 

1860 configure_args.append("-spec") 

1861 configure_args.append(spec) 

1862 

1863 _add_qmake_flag(configure_args, "CFLAGS", include_cppflags=True) 

1864 _add_qmake_flag(configure_args, "CXXFLAGS", include_cppflags=True) 

1865 _add_qmake_flag(configure_args, "LDFLAGS", include_cppflags=False) 

1866 

1867 configure_args.append("QMAKE_STRIP=:") 

1868 configure_args.append("PREFIX=/usr") 

1869 

1870 if self.configure_args: 

1871 substitution = self.substitution 

1872 attr_path = self.attribute_path["configure_args"] 

1873 configure_args.extend( 

1874 substitution.substitute(v, attr_path[i].path) 

1875 for i, v in enumerate(self.configure_args) 

1876 ) 

1877 

1878 self.ensure_build_dir_exists() 

1879 if not self.out_of_source_build: 

1880 configure_args.append(self.relative_from_builddir_to_source()) 

1881 

1882 with self.dump_logs_on_error("config.log"): 

1883 run_build_system_command( 

1884 qmake_cmd, 

1885 *configure_args, 

1886 cwd=self.build_directory, 

1887 ) 

1888 

1889 def build_impl( 

1890 self, 

1891 context: "BuildContext", 

1892 manifest: "HighLevelManifest", 

1893 **kwargs, 

1894 ) -> None: 

1895 self._make_support.run_make(context) 

1896 

1897 def test_impl( 

1898 self, 

1899 context: "BuildContext", 

1900 manifest: "HighLevelManifest", 

1901 *, 

1902 should_ignore_test_errors: bool = False, 

1903 **kwargs, 

1904 ) -> None: 

1905 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

1906 testsuite_flags = [f"-j{limit}"] if limit else ["-j"] 

1907 

1908 if not context.is_terse_build: 

1909 testsuite_flags.append("--verbose") 

1910 self._make_support.run_first_existing_target_if_any( 

1911 context, 

1912 # Order is deliberately inverse compared to debhelper (#924052) 

1913 ["check", "test"], 

1914 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}", 

1915 "VERBOSE=1", 

1916 ) 

1917 

1918 def install_impl( 

1919 self, 

1920 context: "BuildContext", 

1921 manifest: "HighLevelManifest", 

1922 dest_dir: str, 

1923 **kwargs, 

1924 ) -> None: 

1925 enable_parallelization = not os.path.lexists(self.build_dir_path("libtool")) 

1926 self._make_support.run_first_existing_target_if_any( 

1927 context, 

1928 ["install"], 

1929 f"DESTDIR={dest_dir}", 

1930 "AM_UPDATE_INFO_DIR=no", 

1931 enable_parallelization=enable_parallelization, 

1932 ) 

1933 

1934 def clean_impl( 

1935 self, 

1936 context: "BuildContext", 

1937 manifest: "HighLevelManifest", 

1938 clean_helper: "CleanHelper", 

1939 **kwargs, 

1940 ) -> None: 

1941 if self.out_of_source_build: 

1942 return 

1943 self._make_support.run_first_existing_target_if_any( 

1944 context, 

1945 ["distclean", "realclean", "clean"], 

1946 ) 

1947 

1948 

1949class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule): 

1950 

1951 def qmake_command(self) -> str: 

1952 return "qmake" 

1953 

1954 

1955class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule): 

1956 

1957 def qmake_command(self) -> str: 

1958 return "qmake6" 

1959 

1960 

1961@debputy_build_system( 

1962 "make", 

1963 MakefileBuildSystemRule, 

1964 auto_detection_shadows_build_systems="debhelper", 

1965 online_reference_documentation=reference_documentation( 

1966 title="Make Build System", 

1967 description=textwrap.dedent("""\ 

1968 Run a plain `make` file with nothing else. 

1969 

1970 This build system will attempt to use `make` to leverage instructions 

1971 in a makefile (such as, `Makefile` or `GNUMakefile`). 

1972 

1973 By default, the makefile build system assumes it should use "in-source" 

1974 build semantics. If needed be, an explicit `build-directory` can be 

1975 provided if the `Makefile` is not in the source folder but instead in 

1976 some other directory. 

1977 """), 

1978 attributes=[ 

1979 documented_attr( 

1980 "directory", 

1981 textwrap.dedent("""\ 

1982 The directory from which to run make if it is not the source root 

1983 

1984 This works like using `make -C DIRECTORY ...` (or `cd DIRECTORY && make ...`). 

1985 """), 

1986 ), 

1987 documented_attr( 

1988 "build_target", 

1989 textwrap.dedent("""\ 

1990 The target name to use for the "build" step. 

1991 

1992 If omitted, `make` will be run without any explicit target leaving it to decide 

1993 the default. 

1994 """), 

1995 ), 

1996 documented_attr( 

1997 "test_target", 

1998 textwrap.dedent("""\ 

1999 The target name to use for the "test" step. 

2000 

2001 If omitted, `make check` or `make test` will be used if it looks like `make` 

2002 will accept one of those targets. Otherwise, the step will be skipped. 

2003 """), 

2004 ), 

2005 documented_attr( 

2006 "install_target", 

2007 textwrap.dedent("""\ 

2008 The target name to use for the "install" step. 

2009 

2010 If omitted, `make install` will be used if it looks like `make` will accept that target. 

2011 Otherwise, the step will be skipped. 

2012 """), 

2013 ), 

2014 *docs_from( 

2015 DebputyParsedContentStandardConditional, 

2016 OptionalInstallDirectly, 

2017 OptionalTestRule, 

2018 BuildRuleParsedFormat, 

2019 ), 

2020 ], 

2021 ), 

2022) 

2023class ParsedMakeBuildRuleDefinition( 

2024 OptionalInstallDirectly, 

2025 OptionalTestRule, 

2026): 

2027 directory: NotRequired[FileSystemExactMatchRule] 

2028 build_target: NotRequired[str] 

2029 test_target: NotRequired[str] 

2030 install_target: NotRequired[str] 

2031 

2032 

2033@debputy_build_system( 

2034 "autoconf", 

2035 AutoconfBuildSystemRule, 

2036 auto_detection_shadows_build_systems=["debhelper", "make"], 

2037 online_reference_documentation=reference_documentation( 

2038 title="Autoconf Build System (`autoconf`)", 

2039 description=textwrap.dedent("""\ 

2040 Run an autoconf-based build system as the upstream build system. 

2041 

2042 This build rule will attempt to use autoreconf to update the `configure` 

2043 script before running the `configure` script if needed. Otherwise, it 

2044 follows the classic `./configure && make && make install` pattern. 

2045 

2046 The build rule uses "out of source" builds by default since it is easier 

2047 and more reliable for clean and makes it easier to support multiple 

2048 builds (that is, two or more build systems for the same source package). 

2049 This is in contract to `debhelper`, which defaults to "in source" builds 

2050 for `autoconf`. If you need that behavior, please set 

2051 `perform-in-source-build: true`. 

2052 """), 

2053 attributes=[ 

2054 documented_attr( 

2055 "configure_args", 

2056 textwrap.dedent("""\ 

2057 Arguments to be passed to the `configure` script. 

2058 """), 

2059 ), 

2060 *docs_from( 

2061 DebputyParsedContentStandardConditional, 

2062 OptionalInstallDirectly, 

2063 OptionalInSourceBuild, 

2064 OptionalBuildDirectory, 

2065 OptionalTestRule, 

2066 BuildRuleParsedFormat, 

2067 ), 

2068 ], 

2069 ), 

2070) 

2071class ParsedAutoconfBuildRuleDefinition( 

2072 OptionalInstallDirectly, 

2073 OptionalInSourceBuild, 

2074 OptionalBuildDirectory, 

2075 OptionalTestRule, 

2076): 

2077 configure_args: NotRequired[list[str]] 

2078 

2079 

2080@debputy_build_system( 

2081 "cmake", 

2082 CMakeBuildSystemRule, 

2083 auto_detection_shadows_build_systems=["debhelper", "make"], 

2084 online_reference_documentation=reference_documentation( 

2085 title="CMake Build System (`cmake`)", 

2086 description=textwrap.dedent("""\ 

2087 Run an cmake-based build system as the upstream build system. 

2088 

2089 The build rule uses "out of source" builds. 

2090 """), 

2091 attributes=[ 

2092 documented_attr( 

2093 "configure_args", 

2094 textwrap.dedent("""\ 

2095 Arguments to be passed to the `cmake` command. 

2096 """), 

2097 ), 

2098 documented_attr( 

2099 "target_build_system", 

2100 textwrap.dedent("""\ 

2101 Which build system should `cmake` should delegate the build system to. 

2102 In `cmake` terms, this is a Generator (the `-G` option). 

2103 

2104 Supported options are: 

2105 * `make` - GNU Make 

2106 * `ninja` - Ninja 

2107 """), 

2108 ), 

2109 *docs_from( 

2110 DebputyParsedContentStandardConditional, 

2111 OptionalInstallDirectly, 

2112 OptionalBuildDirectory, 

2113 OptionalTestRule, 

2114 BuildRuleParsedFormat, 

2115 ), 

2116 ], 

2117 ), 

2118) 

2119class ParsedCMakeBuildRuleDefinition( 

2120 OptionalInstallDirectly, 

2121 OptionalBuildDirectory, 

2122 OptionalTestRule, 

2123): 

2124 configure_args: NotRequired[list[str]] 

2125 target_build_system: Literal["make", "ninja"] 

2126 

2127 

2128@debputy_build_system( 

2129 "meson", 

2130 MesonBuildSystemRule, 

2131 auto_detection_shadows_build_systems=["debhelper", "make"], 

2132 online_reference_documentation=reference_documentation( 

2133 title="Meson Build System (`meson`)", 

2134 description=textwrap.dedent("""\ 

2135 Run an meson-based build system as the upstream build system. 

2136 

2137 The build rule uses "out of source" builds. 

2138 """), 

2139 attributes=[ 

2140 documented_attr( 

2141 "configure_args", 

2142 textwrap.dedent("""\ 

2143 Arguments to be passed to the `meson` command. 

2144 """), 

2145 ), 

2146 *docs_from( 

2147 DebputyParsedContentStandardConditional, 

2148 OptionalInstallDirectly, 

2149 OptionalBuildDirectory, 

2150 OptionalTestRule, 

2151 BuildRuleParsedFormat, 

2152 ), 

2153 ], 

2154 ), 

2155) 

2156class ParsedMesonBuildRuleDefinition( 

2157 OptionalInstallDirectly, 

2158 OptionalBuildDirectory, 

2159 OptionalTestRule, 

2160): 

2161 configure_args: NotRequired[list[str]] 

2162 

2163 

2164@debputy_build_system( 

2165 "perl-build", 

2166 PerlBuildBuildSystemRule, 

2167 auto_detection_shadows_build_systems=[ 

2168 "debhelper", 

2169 "make", 

2170 "perl-makemaker", 

2171 ], 

2172 online_reference_documentation=reference_documentation( 

2173 title='Perl "Build.PL" Build System (`perl-build`)', 

2174 description=textwrap.dedent("""\ 

2175 Build using the `Build.PL` Build system used by some Perl packages. 

2176 

2177 This build rule will attempt to use the `Build.PL` script to build the 

2178 upstream code. 

2179 """), 

2180 attributes=[ 

2181 documented_attr( 

2182 "configure_args", 

2183 textwrap.dedent("""\ 

2184 Arguments to be passed to the `Build.PL` script. 

2185 """), 

2186 ), 

2187 *docs_from( 

2188 DebputyParsedContentStandardConditional, 

2189 OptionalInstallDirectly, 

2190 OptionalTestRule, 

2191 BuildRuleParsedFormat, 

2192 ), 

2193 ], 

2194 ), 

2195) 

2196class ParsedPerlBuildBuildRuleDefinition( 

2197 OptionalInstallDirectly, 

2198 OptionalTestRule, 

2199): 

2200 configure_args: NotRequired[list[str]] 

2201 

2202 

2203@debputy_build_system( 

2204 "debhelper", 

2205 DebhelperBuildSystemRule, 

2206 online_reference_documentation=reference_documentation( 

2207 title="Debhelper Build System (`debhelper`)", 

2208 description=textwrap.dedent("""\ 

2209 Delegate to a debhelper provided build system 

2210 

2211 This build rule will attempt to use the `dh_auto_*` tools to build the 

2212 upstream code. By default, `dh_auto_*` will use auto-detection to determine 

2213 which build system they will use. This can be overridden by the 

2214 `dh-build-system` attribute. 

2215 """), 

2216 attributes=[ 

2217 documented_attr( 

2218 "dh_build_system", 

2219 textwrap.dedent("""\ 

2220 Which debhelper build system to use. This attribute is passed to 

2221 the `dh_auto_*` commands as the `-S` parameter, so any value valid 

2222 for that will be accepted. 

2223 

2224 Note that many debhelper build systems require extra build 

2225 dependencies before they can be used. Please consult the documentation 

2226 of the relevant debhelper build system for details. 

2227 """), 

2228 ), 

2229 documented_attr( 

2230 "configure_args", 

2231 textwrap.dedent("""\ 

2232 Arguments to be passed to underlying configuration command 

2233 (via `dh_auto_configure -- <configure-args`). 

2234 """), 

2235 ), 

2236 *docs_from( 

2237 DebputyParsedContentStandardConditional, 

2238 OptionalInstallDirectly, 

2239 OptionalBuildDirectory, 

2240 OptionalTestRule, 

2241 BuildRuleParsedFormat, 

2242 ), 

2243 ], 

2244 ), 

2245) 

2246class ParsedDebhelperBuildRuleDefinition( 

2247 OptionalInstallDirectly, 

2248 OptionalBuildDirectory, 

2249 OptionalTestRule, 

2250): 

2251 configure_args: NotRequired[list[str]] 

2252 dh_build_system: NotRequired[str] 

2253 

2254 

2255@debputy_build_system( 

2256 "perl-makemaker", 

2257 PerlMakeMakerBuildSystemRule, 

2258 auto_detection_shadows_build_systems=[ 

2259 "debhelper", 

2260 "make", 

2261 ], 

2262 online_reference_documentation=reference_documentation( 

2263 title='Perl "MakeMaker" Build System (`perl-makemaker`)', 

2264 description=textwrap.dedent("""\ 

2265 Build using the "MakeMaker" Build system used by some Perl packages. 

2266 

2267 This build rule will attempt to use the `Makefile.PL` script to build the 

2268 upstream code. 

2269 """), 

2270 attributes=[ 

2271 documented_attr( 

2272 "configure_args", 

2273 textwrap.dedent("""\ 

2274 Arguments to be passed to the `Makefile.PL` script. 

2275 """), 

2276 ), 

2277 *docs_from( 

2278 DebputyParsedContentStandardConditional, 

2279 OptionalInstallDirectly, 

2280 OptionalTestRule, 

2281 BuildRuleParsedFormat, 

2282 ), 

2283 ], 

2284 ), 

2285) 

2286class ParsedPerlMakeMakerBuildRuleDefinition( 

2287 OptionalInstallDirectly, 

2288 OptionalTestRule, 

2289): 

2290 configure_args: NotRequired[list[str]] 

2291 

2292 

2293@debputy_build_system( 

2294 "qmake", 

2295 QmakeBuildSystemRule, 

2296 auto_detection_shadows_build_systems=[ 

2297 "debhelper", 

2298 "make", 

2299 # Open question, should this shadow "qmake6" and later? 

2300 ], 

2301 online_reference_documentation=reference_documentation( 

2302 title='QT "qmake" Build System (`qmake`)', 

2303 description=textwrap.dedent("""\ 

2304 Build using the "qmake" by QT. 

2305 """), 

2306 attributes=[ 

2307 documented_attr( 

2308 "configure_args", 

2309 textwrap.dedent("""\ 

2310 Arguments to be passed to the `qmake` command. 

2311 """), 

2312 ), 

2313 *docs_from( 

2314 DebputyParsedContentStandardConditional, 

2315 OptionalInstallDirectly, 

2316 OptionalInSourceBuild, 

2317 OptionalBuildDirectory, 

2318 OptionalTestRule, 

2319 BuildRuleParsedFormat, 

2320 ), 

2321 ], 

2322 ), 

2323) 

2324class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2325 pass 

2326 

2327 

2328@debputy_build_system( 

2329 "qmake6", 

2330 Qmake6BuildSystemRule, 

2331 auto_detection_shadows_build_systems=[ 

2332 "debhelper", 

2333 "make", 

2334 ], 

2335 online_reference_documentation=reference_documentation( 

2336 title='QT "qmake6" Build System (`qmake6`)', 

2337 description=textwrap.dedent("""\ 

2338 Build using the "qmake6" from the `qmake6` package. This is like the `qmake` system 

2339 but is specifically for QT6. 

2340 """), 

2341 attributes=[ 

2342 documented_attr( 

2343 "configure_args", 

2344 textwrap.dedent("""\ 

2345 Arguments to be passed to the `qmake6` command. 

2346 """), 

2347 ), 

2348 *docs_from( 

2349 DebputyParsedContentStandardConditional, 

2350 OptionalInstallDirectly, 

2351 OptionalInSourceBuild, 

2352 OptionalBuildDirectory, 

2353 OptionalTestRule, 

2354 BuildRuleParsedFormat, 

2355 ), 

2356 ], 

2357 ), 

2358) 

2359class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2360 pass 

2361 

2362 

2363def _parse_default_environment( 

2364 _name: str, 

2365 parsed_data: EnvironmentSourceFormat, 

2366 attribute_path: AttributePath, 

2367 parser_context: ParserContextData, 

2368) -> ManifestProvidedBuildEnvironment: 

2369 return ManifestProvidedBuildEnvironment.from_environment_definition( 

2370 parsed_data, 

2371 attribute_path, 

2372 parser_context, 

2373 is_default=True, 

2374 ) 

2375 

2376 

2377def _parse_build_environments( 

2378 _name: str, 

2379 parsed_data: list[NamedEnvironmentSourceFormat], 

2380 attribute_path: AttributePath, 

2381 parser_context: ParserContextData, 

2382) -> list[ManifestProvidedBuildEnvironment]: 

2383 return [ 

2384 ManifestProvidedBuildEnvironment.from_environment_definition( 

2385 value, 

2386 attribute_path[idx], 

2387 parser_context, 

2388 is_default=False, 

2389 ) 

2390 for idx, value in enumerate(parsed_data) 

2391 ] 

2392 

2393 

2394def _handle_build_rules( 

2395 _name: str, 

2396 parsed_data: list[BuildRule], 

2397 _attribute_path: AttributePath, 

2398 _parser_context: ParserContextData, 

2399) -> list[BuildRule]: 

2400 return parsed_data