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

680 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-01-16 17:20 +0000

1import dataclasses 

2import json 

3import os 

4import subprocess 

5import textwrap 

6from typing import ( 

7 NotRequired, 

8 TypedDict, 

9 Self, 

10 cast, 

11 Union, 

12 TYPE_CHECKING, 

13 Literal, 

14 Required, 

15) 

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

17 

18from debian.debian_support import Version 

19 

20from debputy._manifest_constants import MK_BUILDS 

21from debputy.manifest_conditions import ManifestCondition 

22from debputy.manifest_parser.base_types import ( 

23 BuildEnvironmentDefinition, 

24 DebputyParsedContentStandardConditional, 

25 FileSystemExactMatchRule, 

26) 

27from debputy.manifest_parser.exceptions import ( 

28 ManifestParseException, 

29 ManifestInvalidUserDataException, 

30) 

31from debputy.manifest_parser.parser_data import ParserContextData 

32from debputy.manifest_parser.tagging_types import DebputyParsedContent 

33from debputy.manifest_parser.util import AttributePath 

34from debputy.plugin.api import reference_documentation 

35from debputy.plugin.api.impl import ( 

36 DebputyPluginInitializerProvider, 

37) 

38from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT 

39from debputy.plugin.api.spec import ( 

40 documented_attr, 

41 INTEGRATION_MODE_FULL, 

42 only_integrations, 

43 VirtualPath, 

44) 

45from debputy.plugin.api.std_docs import docs_from 

46from debputy.plugins.debputy.to_be_api_types import ( 

47 BuildRule, 

48 StepBasedBuildSystemRule, 

49 OptionalInstallDirectly, 

50 BuildSystemCharacteristics, 

51 OptionalBuildDirectory, 

52 OptionalInSourceBuild, 

53 MakefileSupport, 

54 BuildRuleParsedFormat, 

55 debputy_build_system, 

56 CleanHelper, 

57 NinjaBuildSupport, 

58 TestRule, 

59 OptionalTestRule, 

60) 

61from debputy.types import EnvironmentModification 

62from debputy.util import ( 

63 _warn, 

64 run_build_system_command, 

65 _error, 

66 PerlConfigVars, 

67 resolve_perl_config, 

68 generated_content_dir, 

69 manifest_format_doc, 

70 _is_debug_log_enabled, 

71) 

72 

73if TYPE_CHECKING: 

74 from debputy.build_support.build_context import BuildContext 

75 from debputy.highlevel_manifest import HighLevelManifest 

76 

77 

78PERL_CMD = "perl" 

79 

80 

81class Conditional(DebputyParsedContent): 

82 when: Required[ManifestCondition] 

83 

84 

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

86 register_build_keywords(api) 

87 register_build_rules(api) 

88 

89 

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

91 

92 api.pluggable_manifest_rule( 

93 OPARSER_MANIFEST_ROOT, 

94 "build-environments", 

95 list[NamedEnvironmentSourceFormat], 

96 _parse_build_environments, 

97 register_value=False, 

98 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

99 inline_reference_documentation=reference_documentation( 

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

101 description=textwrap.dedent( 

102 """\ 

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 only 

132 visible to build commands). They are only available to build commands. 

133 """ 

134 ), 

135 attributes=[ 

136 documented_attr( 

137 "name", 

138 textwrap.dedent( 

139 """\ 

140 The name of the environment 

141 

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

143 """ 

144 ), 

145 ), 

146 documented_attr( 

147 "set", 

148 textwrap.dedent( 

149 """\ 

150 A mapping of environment variables to be set. 

151 

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

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

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

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

156 """ 

157 ), 

158 ), 

159 documented_attr( 

160 "override", 

161 textwrap.dedent( 

162 """\ 

163 A mapping of environment variables to set. 

164 

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

166 `dpkg-buildflags`. 

167 """ 

168 ), 

169 ), 

170 documented_attr( 

171 "unset", 

172 textwrap.dedent( 

173 """\ 

174 A list of environment variables to unset. 

175 

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

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

178 """ 

179 ), 

180 ), 

181 ], 

182 reference_documentation_url=manifest_format_doc( 

183 "build-environment-build-environment" 

184 ), 

185 ), 

186 ) 

187 api.pluggable_manifest_rule( 

188 OPARSER_MANIFEST_ROOT, 

189 "default-build-environment", 

190 EnvironmentSourceFormat, 

191 _parse_default_environment, 

192 register_value=False, 

193 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

194 inline_reference_documentation=reference_documentation( 

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

196 description=textwrap.dedent( 

197 """\ 

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

199 environment. 

200 

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

202 build commands. An example: 

203 

204 default-build-environment: 

205 set: 

206 ENV_VAR: foo 

207 ANOTHER_ENV_VAR: bar 

208 

209 The environment definition has multiple attributes for setting environment variables 

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

211 result of the following order of operations. 

212 

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

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

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

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

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

218 

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

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

221 

222 Note that these variables are not available via manifest substitution (they are only 

223 visible to build commands). They are only available to build commands. 

224 """ 

225 ), 

226 attributes=[ 

227 documented_attr( 

228 "set", 

229 textwrap.dedent( 

230 """\ 

231 A mapping of environment variables to be set. 

232 

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

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

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

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

237 """ 

238 ), 

239 ), 

240 documented_attr( 

241 "override", 

242 textwrap.dedent( 

243 """\ 

244 A mapping of environment variables to set. 

245 

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

247 `dpkg-buildflags`. 

248 """ 

249 ), 

250 ), 

251 documented_attr( 

252 "unset", 

253 textwrap.dedent( 

254 """\ 

255 A list of environment variables to unset. 

256 

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

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

259 """ 

260 ), 

261 ), 

262 ], 

263 reference_documentation_url=manifest_format_doc( 

264 "build-environment-build-environment" 

265 ), 

266 ), 

267 ) 

268 api.pluggable_manifest_rule( 

269 OPARSER_MANIFEST_ROOT, 

270 MK_BUILDS, 

271 list[BuildRule], 

272 _handle_build_rules, 

273 register_value=False, 

274 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

275 inline_reference_documentation=reference_documentation( 

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

277 description=textwrap.dedent( 

278 """\ 

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

280 which also defines the clean rules. 

281 

282 A simple example is: 

283 

284 ```yaml 

285 builds: 

286 - autoconf: 

287 configure-args: 

288 - "--enable-foo" 

289 - "--without=bar" 

290 ``` 

291 

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

293 for `libpam-krb5` might look. 

294 

295 ```yaml 

296 builds: 

297 - autoconf: 

298 for: libpam-krb5 

299 install-directly-to-package: true 

300 configure-args: 

301 - "--enable-reduced-depends" 

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

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

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

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

306 - autoconf: 

307 for: libpam-heimdal 

308 install-directly-to-package: true 

309 configure-args: 

310 - "--enable-reduced-depends" 

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

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

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

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

315 ``` 

316 """ 

317 ), 

318 ), 

319 ) 

320 

321 api.provide_manifest_keyword( 

322 TestRule, 

323 "skip-tests", 

324 lambda n, ap, pc: TestRule( 

325 ap.path, 

326 ManifestCondition.literal_bool(False), 

327 ManifestCondition.literal_bool(True), 

328 ), 

329 inline_reference_documentation=reference_documentation( 

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

331 description=textwrap.dedent( 

332 """\ 

333 Skip all build time tests. 

334 

335 Example: 

336 

337 ```yaml 

338 ...: 

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

340 test-rule: skip-tests 

341 ``` 

342 

343 """ 

344 ), 

345 ), 

346 ) 

347 

348 api.pluggable_manifest_rule( 

349 TestRule, 

350 "skip-tests-when", 

351 Conditional, 

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

353 ap.path, 

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

355 ManifestCondition.literal_bool(False), 

356 ), 

357 source_format=ManifestCondition, 

358 inline_reference_documentation=reference_documentation( 

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

360 description=textwrap.dedent( 

361 """\ 

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

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

364 

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

366 then wrap the conditional with `not:`. 

367 

368 Example: 

369 

370 ```yaml 

371 ...: 

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

373 test-rule: 

374 skip-tests-when: 

375 not: 

376 arch-matches: "linux-any" 

377 ``` 

378 

379 """ 

380 ), 

381 ), 

382 ) 

383 

384 

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

386 api.register_build_system(ParsedAutoconfBuildRuleDefinition) 

387 api.register_build_system(ParsedMakeBuildRuleDefinition) 

388 

389 api.register_build_system(ParsedPerlBuildBuildRuleDefinition) 

390 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition) 

391 api.register_build_system(ParsedDebhelperBuildRuleDefinition) 

392 

393 api.register_build_system(ParsedCMakeBuildRuleDefinition) 

394 api.register_build_system(ParsedMesonBuildRuleDefinition) 

395 

396 api.register_build_system(ParsedQmakeBuildRuleDefinition) 

397 api.register_build_system(ParsedQmake6BuildRuleDefinition) 

398 

399 

400class EnvironmentSourceFormat(TypedDict): 

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

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

403 unset: NotRequired[list[str]] 

404 

405 

406class NamedEnvironmentSourceFormat(EnvironmentSourceFormat): 

407 name: str 

408 

409 

410_READ_ONLY_ENV_VARS = { 

411 "DEB_CHECK_COMMAND": None, 

412 "DEB_SIGN_KEYID": None, 

413 "DEB_SIGN_KEYFILE": None, 

414 "DEB_BUILD_OPTIONS": "DEB_BUILD_MAINT_OPTIONS", 

415 "DEB_BUILD_PROFILES": None, 

416 "DEB_RULES_REQUIRES_ROOT": None, 

417 "DEB_GAIN_ROOT_COMMAND": None, 

418 "DH_EXTRA_ADDONS": None, 

419 "DH_NO_ACT": None, 

420 "XDG_RUNTIME_DIR": None, 

421 "HOME": None, 

422} 

423 

424 

425def _check_variables( 

426 env_vars: Iterable[str], 

427 attribute_path: AttributePath, 

428) -> None: 

429 for env_var in env_vars: 

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

431 continue 

432 alt = _READ_ONLY_ENV_VARS.get(env_var) 

433 var_path = attribute_path[env_var].path_key_lc 

434 if alt is None: 

435 raise ManifestParseException( 

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

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

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

439 ) 

440 else: 

441 raise ManifestParseException( 

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

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

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

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

446 f" The problematic definition was {var_path}" 

447 ) 

448 

449 

450def _no_overlap( 

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

452 rhs: Container[str], 

453 lhs_key: str, 

454 rhs_key: str, 

455 redundant_key: str, 

456 attribute_path: AttributePath, 

457) -> None: 

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

459 if isinstance(kt, tuple): 

460 lhs_path_key, var = kt 

461 else: 

462 lhs_path_key = var = kt 

463 if var not in rhs: 

464 continue 

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

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

467 r_path = lhs_path if redundant_key == rhs_key else rhs_path 

468 raise ManifestParseException( 

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

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

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

472 f" the two definitions." 

473 ) 

474 

475 

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

477class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition): 

478 

479 name: str 

480 is_default: bool 

481 attribute_path: AttributePath 

482 parser_context: ParserContextData 

483 

484 set_vars: Mapping[str, str] 

485 override_vars: Mapping[str, str] 

486 unset_vars: Sequence[str] 

487 

488 @classmethod 

489 def from_environment_definition( 

490 cls, 

491 env: EnvironmentSourceFormat, 

492 attribute_path: AttributePath, 

493 parser_context: ParserContextData, 

494 is_default: bool = False, 

495 ) -> Self: 

496 reference_name: str | None 

497 if is_default: 

498 name = "default-env" 

499 reference_name = None 

500 else: 

501 named_env = cast("NamedEnvironmentSourceFormat", env) 

502 name = named_env["name"] 

503 reference_name = name 

504 

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

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

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

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

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

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

511 

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

513 raise ManifestParseException( 

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

515 " some content or delete the definition." 

516 ) 

517 

518 _no_overlap( 

519 enumerate(unset_vars), 

520 set_vars, 

521 "unset", 

522 "set", 

523 "set", 

524 attribute_path, 

525 ) 

526 _no_overlap( 

527 enumerate(unset_vars), 

528 override_vars, 

529 "unset", 

530 "override", 

531 "override", 

532 attribute_path, 

533 ) 

534 _no_overlap( 

535 override_vars, 

536 set_vars, 

537 "override", 

538 "set", 

539 "set", 

540 attribute_path, 

541 ) 

542 

543 r = cls( 

544 name, 

545 is_default, 

546 attribute_path, 

547 parser_context, 

548 set_vars, 

549 override_vars, 

550 unset_vars, 

551 ) 

552 parser_context._register_build_environment( 

553 reference_name, 

554 r, 

555 attribute_path, 

556 is_default, 

557 ) 

558 

559 return r 

560 

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

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

563 env.update(set_vars) 

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

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

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

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

568 for var in overlapping_env: 

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

570 _warn( 

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

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

573 f" `set`." 

574 ) 

575 env.update(dpkg_env) 

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

577 env.update(override_vars) 

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

579 for var in unset_vars: 

580 try: 

581 del env[var] 

582 except KeyError: 

583 pass 

584 

585 

586_MAKE_DEFAULT_TOOLS = [ 

587 ("CC", "gcc"), 

588 ("CXX", "g++"), 

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

590] 

591 

592 

593class MakefileBuildSystemRule(StepBasedBuildSystemRule): 

594 

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

596 

597 def __init__( 

598 self, 

599 attributes: "ParsedMakeBuildRuleDefinition", 

600 attribute_path: AttributePath, 

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

602 ) -> None: 

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

604 directory = attributes.get("directory") 

605 if directory is not None: 

606 self._directory = directory.match_rule.path 

607 else: 

608 self._directory = None 

609 self._make_support = MakefileSupport.from_build_system(self) 

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

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

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

613 

614 @classmethod 

615 def auto_detect_build_system( 

616 cls, 

617 source_root: VirtualPath, 

618 *args, 

619 **kwargs, 

620 ) -> bool: 

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

622 

623 @classmethod 

624 def characteristics(cls) -> BuildSystemCharacteristics: 

625 return BuildSystemCharacteristics( 

626 out_of_source_builds="not-supported", 

627 ) 

628 

629 def configure_impl( 

630 self, 

631 context: "BuildContext", 

632 manifest: "HighLevelManifest", 

633 **kwargs, 

634 ) -> None: 

635 # No configure step 

636 pass 

637 

638 def build_impl( 

639 self, 

640 context: "BuildContext", 

641 manifest: "HighLevelManifest", 

642 **kwargs, 

643 ) -> None: 

644 extra_vars = [] 

645 build_target = self._build_target 

646 if build_target is not None: 

647 extra_vars.append(build_target) 

648 if context.is_cross_compiling: 

649 for envvar, tool in _MAKE_DEFAULT_TOOLS: 

650 cross_tool = os.environ.get(envvar) 

651 if cross_tool is None: 

652 cross_tool = context.cross_tool(tool) 

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

654 self._make_support.run_make( 

655 context, 

656 *extra_vars, 

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

658 directory=self._directory, 

659 ) 

660 

661 def test_impl( 

662 self, 

663 context: "BuildContext", 

664 manifest: "HighLevelManifest", 

665 *, 

666 should_ignore_test_errors: bool = False, 

667 **kwargs, 

668 ) -> None: 

669 self._run_make_maybe_explicit_target( 

670 context, 

671 self._test_target, 

672 ["test", "check"], 

673 ) 

674 

675 def install_impl( 

676 self, 

677 context: "BuildContext", 

678 manifest: "HighLevelManifest", 

679 dest_dir: str, 

680 **kwargs, 

681 ) -> None: 

682 self._run_make_maybe_explicit_target( 

683 context, 

684 self._install_target, 

685 ["install"], 

686 f"DESTDIR={dest_dir}", 

687 "AM_UPDATE_INFO_DIR=no", 

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

689 ) 

690 

691 def _run_make_maybe_explicit_target( 

692 self, 

693 context: "BuildContext", 

694 provided_target: str | None, 

695 fallback_targets: Sequence[str], 

696 *make_args: str, 

697 ) -> None: 

698 make_support = self._make_support 

699 if provided_target is not None: 

700 make_support.run_make( 

701 context, 

702 provided_target, 

703 *make_args, 

704 directory=self._directory, 

705 ) 

706 else: 

707 make_support.run_first_existing_target_if_any( 

708 context, 

709 fallback_targets, 

710 *make_args, 

711 directory=self._directory, 

712 ) 

713 

714 def clean_impl( 

715 self, 

716 context: "BuildContext", 

717 manifest: "HighLevelManifest", 

718 clean_helper: "CleanHelper", 

719 **kwargs, 

720 ) -> None: 

721 self._make_support.run_first_existing_target_if_any( 

722 context, 

723 ["distclean", "realclean", "clean"], 

724 ) 

725 

726 

727class PerlBuildBuildSystemRule(StepBasedBuildSystemRule): 

728 

729 __slots__ = "configure_args" 

730 

731 def __init__( 

732 self, 

733 attributes: "ParsedPerlBuildBuildRuleDefinition", 

734 attribute_path: AttributePath, 

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

736 ) -> None: 

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

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

739 

740 @classmethod 

741 def auto_detect_build_system( 

742 cls, 

743 source_root: VirtualPath, 

744 *args, 

745 **kwargs, 

746 ) -> bool: 

747 return "Build.PL" in source_root 

748 

749 @classmethod 

750 def characteristics(cls) -> BuildSystemCharacteristics: 

751 return BuildSystemCharacteristics( 

752 out_of_source_builds="not-supported", 

753 ) 

754 

755 @staticmethod 

756 def _perl_cross_build_env( 

757 context: "BuildContext", 

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

759 perl_config_data = resolve_perl_config( 

760 context.dpkg_architecture_variables, 

761 None, 

762 ) 

763 if context.is_cross_compiling: 

764 perl5lib_dir = perl_config_data.cross_inc_dir 

765 if perl5lib_dir is not None: 

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

767 if env_perl5lib is not None: 

768 perl5lib_dir = ( 

769 perl5lib_dir + perl_config_data.path_sep + env_perl5lib 

770 ) 

771 env_mod = EnvironmentModification( 

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

773 ) 

774 return perl_config_data, env_mod 

775 return perl_config_data, None 

776 

777 def configure_impl( 

778 self, 

779 context: "BuildContext", 

780 manifest: "HighLevelManifest", 

781 **kwargs, 

782 ) -> None: 

783 perl_config_data, cross_env_mod = self._perl_cross_build_env(context) 

784 configure_env = EnvironmentModification( 

785 replacements=( 

786 ("PERL_MM_USE_DEFAULT", "1"), 

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

788 ) 

789 ) 

790 if cross_env_mod is not None: 

791 configure_env = configure_env.combine(cross_env_mod) 

792 

793 configure_cmd = [ 

794 PERL_CMD, 

795 "Build.PL", 

796 "--installdirs", 

797 "vendor", 

798 ] 

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

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

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

802 

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

804 configure_cmd.append("--config") 

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

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

807 

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

809 configure_cmd.append("--config") 

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

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

812 if self.configure_args: 

813 substitution = self.substitution 

814 attr_path = self.attribute_path["configure_args"] 

815 configure_cmd.extend( 

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

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

818 ) 

819 run_build_system_command(*configure_cmd, env_mod=configure_env) 

820 

821 def build_impl( 

822 self, 

823 context: "BuildContext", 

824 manifest: "HighLevelManifest", 

825 **kwargs, 

826 ) -> None: 

827 _, cross_env_mod = self._perl_cross_build_env(context) 

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

829 

830 def test_impl( 

831 self, 

832 context: "BuildContext", 

833 manifest: "HighLevelManifest", 

834 *, 

835 should_ignore_test_errors: bool = False, 

836 **kwargs, 

837 ) -> None: 

838 _, cross_env_mod = self._perl_cross_build_env(context) 

839 run_build_system_command( 

840 PERL_CMD, 

841 "Build", 

842 "test", 

843 "--verbose", 

844 "1", 

845 env_mod=cross_env_mod, 

846 ) 

847 

848 def install_impl( 

849 self, 

850 context: "BuildContext", 

851 manifest: "HighLevelManifest", 

852 dest_dir: str, 

853 **kwargs, 

854 ) -> None: 

855 _, cross_env_mod = self._perl_cross_build_env(context) 

856 run_build_system_command( 

857 PERL_CMD, 

858 "Build", 

859 "install", 

860 "--destdir", 

861 dest_dir, 

862 "--create_packlist", 

863 "0", 

864 env_mod=cross_env_mod, 

865 ) 

866 

867 def clean_impl( 

868 self, 

869 context: "BuildContext", 

870 manifest: "HighLevelManifest", 

871 clean_helper: "CleanHelper", 

872 **kwargs, 

873 ) -> None: 

874 _, cross_env_mod = self._perl_cross_build_env(context) 

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

876 run_build_system_command( 

877 PERL_CMD, 

878 "Build", 

879 "realclean", 

880 "--allow_mb_mismatch", 

881 "1", 

882 env_mod=cross_env_mod, 

883 ) 

884 

885 

886class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule): 

887 

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

889 

890 def __init__( 

891 self, 

892 attributes: "ParsedPerlBuildBuildRuleDefinition", 

893 attribute_path: AttributePath, 

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

895 ) -> None: 

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

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

898 self._make_support = MakefileSupport.from_build_system(self) 

899 

900 @classmethod 

901 def auto_detect_build_system( 

902 cls, 

903 source_root: VirtualPath, 

904 *args, 

905 **kwargs, 

906 ) -> bool: 

907 return "Makefile.PL" in source_root 

908 

909 @classmethod 

910 def characteristics(cls) -> BuildSystemCharacteristics: 

911 return BuildSystemCharacteristics( 

912 out_of_source_builds="not-supported", 

913 ) 

914 

915 def configure_impl( 

916 self, 

917 context: "BuildContext", 

918 manifest: "HighLevelManifest", 

919 **kwargs, 

920 ) -> None: 

921 configure_env = EnvironmentModification( 

922 replacements=( 

923 ("PERL_MM_USE_DEFAULT", "1"), 

924 ("PERL_AUTOINSTALL", "--skipdeps"), 

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

926 ) 

927 ) 

928 perl_args = [] 

929 mm_args = ["INSTALLDIRS=vendor"] 

930 if "CFLAGS" in os.environ: 

931 mm_args.append( 

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

933 ) 

934 

935 perl_config_data = resolve_perl_config( 

936 context.dpkg_architecture_variables, 

937 None, 

938 ) 

939 

940 if "LDFLAGS" in os.environ: 

941 mm_args.append( 

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

943 ) 

944 

945 if context.is_cross_compiling: 

946 perl5lib_dir = perl_config_data.cross_inc_dir 

947 if perl5lib_dir is not None: 

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

949 

950 if self.configure_args: 

951 substitution = self.substitution 

952 attr_path = self.attribute_path["configure_args"] 

953 mm_args.extend( 

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

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

956 ) 

957 run_build_system_command( 

958 PERL_CMD, 

959 *perl_args, 

960 "Makefile.PL", 

961 *mm_args, 

962 env_mod=configure_env, 

963 ) 

964 

965 def build_impl( 

966 self, 

967 context: "BuildContext", 

968 manifest: "HighLevelManifest", 

969 **kwargs, 

970 ) -> None: 

971 self._make_support.run_make(context) 

972 

973 def test_impl( 

974 self, 

975 context: "BuildContext", 

976 manifest: "HighLevelManifest", 

977 *, 

978 should_ignore_test_errors: bool = False, 

979 **kwargs, 

980 ) -> None: 

981 self._make_support.run_first_existing_target_if_any( 

982 context, 

983 ["check", "test"], 

984 "TEST_VERBOSE=1", 

985 ) 

986 

987 def install_impl( 

988 self, 

989 context: "BuildContext", 

990 manifest: "HighLevelManifest", 

991 dest_dir: str, 

992 **kwargs, 

993 ) -> None: 

994 is_mm_makefile = False 

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

996 for line in fd: 

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

998 is_mm_makefile = True 

999 break 

1000 

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

1002 

1003 # Special case for Makefile.PL that uses 

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

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

1006 if is_mm_makefile: 

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

1008 

1009 self._make_support.run_first_existing_target_if_any( 

1010 context, 

1011 ["install"], 

1012 *install_args, 

1013 ) 

1014 

1015 def clean_impl( 

1016 self, 

1017 context: "BuildContext", 

1018 manifest: "HighLevelManifest", 

1019 clean_helper: "CleanHelper", 

1020 **kwargs, 

1021 ) -> None: 

1022 self._make_support.run_first_existing_target_if_any( 

1023 context, 

1024 ["distclean", "realclean", "clean"], 

1025 ) 

1026 

1027 

1028class DebhelperBuildSystemRule(StepBasedBuildSystemRule): 

1029 

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

1031 

1032 def __init__( 

1033 self, 

1034 parsed_data: "ParsedDebhelperBuildRuleDefinition", 

1035 attribute_path: AttributePath, 

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

1037 ) -> None: 

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

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

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

1041 

1042 @classmethod 

1043 def auto_detect_build_system( 

1044 cls, 

1045 source_root: VirtualPath, 

1046 *args, 

1047 **kwargs, 

1048 ) -> bool: 

1049 try: 

1050 v = subprocess.check_output( 

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

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

1053 stderr=subprocess.DEVNULL, 

1054 cwd=source_root.fs_path, 

1055 ) 

1056 except subprocess.CalledProcessError: 

1057 return False 

1058 d = json.loads(v) 

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

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

1061 

1062 @classmethod 

1063 def characteristics(cls) -> BuildSystemCharacteristics: 

1064 return BuildSystemCharacteristics( 

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

1066 ) 

1067 

1068 def before_first_impl_step( 

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

1070 ) -> None: 

1071 dh_build_system = self.dh_build_system 

1072 if dh_build_system is None: 

1073 return 

1074 try: 

1075 subprocess.check_call( 

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

1077 ) 

1078 except FileNotFoundError: 

1079 _error( 

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

1081 ) 

1082 except subprocess.SubprocessError: 

1083 raise ManifestInvalidUserDataException( 

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

1085 f" be available according to" 

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

1087 ) from None 

1088 

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

1090 default_options = [] 

1091 if self.dh_build_system is not None: 

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

1093 if self.build_directory is not None: 

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

1095 

1096 return default_options 

1097 

1098 def configure_impl( 

1099 self, 

1100 context: "BuildContext", 

1101 manifest: "HighLevelManifest", 

1102 **kwargs, 

1103 ) -> None: 

1104 if ( 

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

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

1107 run_build_system_command("dh_update_autotools_config") 

1108 run_build_system_command("dh_autoreconf") 

1109 

1110 default_options = self._default_options() 

1111 configure_args = default_options.copy() 

1112 if self.configure_args: 

1113 configure_args.append("--") 

1114 substitution = self.substitution 

1115 attr_path = self.attribute_path["configure_args"] 

1116 configure_args.extend( 

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

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

1119 ) 

1120 run_build_system_command("dh_auto_configure", *configure_args) 

1121 

1122 def build_impl( 

1123 self, 

1124 context: "BuildContext", 

1125 manifest: "HighLevelManifest", 

1126 **kwargs, 

1127 ) -> None: 

1128 default_options = self._default_options() 

1129 run_build_system_command("dh_auto_build", *default_options) 

1130 

1131 def test_impl( 

1132 self, 

1133 context: "BuildContext", 

1134 manifest: "HighLevelManifest", 

1135 *, 

1136 should_ignore_test_errors: bool = False, 

1137 **kwargs, 

1138 ) -> None: 

1139 default_options = self._default_options() 

1140 run_build_system_command("dh_auto_test", *default_options) 

1141 

1142 def install_impl( 

1143 self, 

1144 context: "BuildContext", 

1145 manifest: "HighLevelManifest", 

1146 dest_dir: str, 

1147 **kwargs, 

1148 ) -> None: 

1149 default_options = self._default_options() 

1150 run_build_system_command( 

1151 "dh_auto_install", 

1152 *default_options, 

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

1154 ) 

1155 

1156 def clean_impl( 

1157 self, 

1158 context: "BuildContext", 

1159 manifest: "HighLevelManifest", 

1160 clean_helper: "CleanHelper", 

1161 **kwargs, 

1162 ) -> None: 

1163 default_options = self._default_options() 

1164 run_build_system_command("dh_auto_clean", *default_options) 

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

1166 

1167 

1168class AutoconfBuildSystemRule(StepBasedBuildSystemRule): 

1169 

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

1171 

1172 def __init__( 

1173 self, 

1174 parsed_data: "ParsedAutoconfBuildRuleDefinition", 

1175 attribute_path: AttributePath, 

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

1177 ) -> None: 

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

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

1180 self._make_support = MakefileSupport.from_build_system(self) 

1181 

1182 @classmethod 

1183 def characteristics(cls) -> BuildSystemCharacteristics: 

1184 return BuildSystemCharacteristics( 

1185 out_of_source_builds="supported-and-default", 

1186 ) 

1187 

1188 @classmethod 

1189 def auto_detect_build_system( 

1190 cls, 

1191 source_root: VirtualPath, 

1192 *args, 

1193 **kwargs, 

1194 ) -> bool: 

1195 if "configure.ac" in source_root: 

1196 return True 

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

1198 if configure_in is not None and configure_in.is_file: 

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

1200 for no, line in enumerate(fd): 

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

1202 break 

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

1204 return True 

1205 configure = source_root.get("configure") 

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

1207 return False 

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

1209 for no, line in enumerate(fd): 

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

1211 break 

1212 if b"GNU Autoconf" in line: 

1213 return True 

1214 return False 

1215 

1216 def configure_impl( 

1217 self, 

1218 context: "BuildContext", 

1219 manifest: "HighLevelManifest", 

1220 **kwargs, 

1221 ) -> None: 

1222 if ( 

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

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

1225 run_build_system_command("dh_update_autotools_config") 

1226 run_build_system_command("dh_autoreconf") 

1227 

1228 dpkg_architecture_variables = context.dpkg_architecture_variables 

1229 multi_arch = dpkg_architecture_variables.current_host_multiarch 

1230 silent_rules = ( 

1231 "--enable-silent-rules" 

1232 if context.is_terse_build 

1233 else "--disable-silent-rules" 

1234 ) 

1235 

1236 configure_args = [ 

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

1238 "--prefix=/usr", 

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

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

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

1242 "--sysconfdir=/etc", 

1243 "--localstatedir=/var", 

1244 "--disable-option-checking", 

1245 silent_rules, 

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

1247 "--runstatedir=/run", 

1248 "--disable-maintainer-mode", 

1249 "--disable-dependency-tracking", 

1250 ] 

1251 if dpkg_architecture_variables.is_cross_compiling: 

1252 configure_args.append( 

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

1254 ) 

1255 if self.configure_args: 

1256 substitution = self.substitution 

1257 attr_path = self.attribute_path["configure_args"] 

1258 configure_args.extend( 

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

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

1261 ) 

1262 self.ensure_build_dir_exists() 

1263 configure_script = self.relative_from_builddir_to_source("configure") 

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

1265 run_build_system_command( 

1266 configure_script, 

1267 *configure_args, 

1268 cwd=self.build_directory, 

1269 ) 

1270 

1271 def build_impl( 

1272 self, 

1273 context: "BuildContext", 

1274 manifest: "HighLevelManifest", 

1275 **kwargs, 

1276 ) -> None: 

1277 self._make_support.run_make(context) 

1278 

1279 def test_impl( 

1280 self, 

1281 context: "BuildContext", 

1282 manifest: "HighLevelManifest", 

1283 *, 

1284 should_ignore_test_errors: bool = False, 

1285 **kwargs, 

1286 ) -> None: 

1287 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1289 

1290 if not context.is_terse_build: 

1291 testsuite_flags.append("--verbose") 

1292 self._make_support.run_first_existing_target_if_any( 

1293 context, 

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

1295 ["check", "test"], 

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

1297 "VERBOSE=1", 

1298 ) 

1299 

1300 def install_impl( 

1301 self, 

1302 context: "BuildContext", 

1303 manifest: "HighLevelManifest", 

1304 dest_dir: str, 

1305 **kwargs, 

1306 ) -> None: 

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

1308 self._make_support.run_first_existing_target_if_any( 

1309 context, 

1310 ["install"], 

1311 f"DESTDIR={dest_dir}", 

1312 "AM_UPDATE_INFO_DIR=no", 

1313 enable_parallelization=enable_parallelization, 

1314 ) 

1315 

1316 def clean_impl( 

1317 self, 

1318 context: "BuildContext", 

1319 manifest: "HighLevelManifest", 

1320 clean_helper: "CleanHelper", 

1321 **kwargs, 

1322 ) -> None: 

1323 if self.out_of_source_build: 

1324 return 

1325 self._make_support.run_first_existing_target_if_any( 

1326 context, 

1327 ["distclean", "realclean", "clean"], 

1328 ) 

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

1330 

1331 

1332class CMakeBuildSystemRule(StepBasedBuildSystemRule): 

1333 

1334 __slots__ = ( 

1335 "configure_args", 

1336 "target_build_system", 

1337 "_make_support", 

1338 "_ninja_support", 

1339 ) 

1340 

1341 def __init__( 

1342 self, 

1343 parsed_data: "ParsedCMakeBuildRuleDefinition", 

1344 attribute_path: AttributePath, 

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

1346 ) -> None: 

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

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

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

1350 "target_build_system", "make" 

1351 ) 

1352 self._make_support = MakefileSupport.from_build_system(self) 

1353 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1354 

1355 @classmethod 

1356 def characteristics(cls) -> BuildSystemCharacteristics: 

1357 return BuildSystemCharacteristics( 

1358 out_of_source_builds="required", 

1359 ) 

1360 

1361 @classmethod 

1362 def auto_detect_build_system( 

1363 cls, 

1364 source_root: VirtualPath, 

1365 *args, 

1366 **kwargs, 

1367 ) -> bool: 

1368 return "CMakeLists.txt" in source_root 

1369 

1370 @staticmethod 

1371 def _default_cmake_env( 

1372 build_context: "BuildContext", 

1373 ) -> EnvironmentModification: 

1374 replacements = {} 

1375 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1376 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1377 if "PKG_CONFIG" not in os.environ: 

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

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

1380 return EnvironmentModification( 

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

1382 ) 

1383 

1384 @classmethod 

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

1386 cmake_generators = { 

1387 "make": "Unix Makefiles", 

1388 "ninja": "Ninja", 

1389 } 

1390 return cmake_generators[target_build_system] 

1391 

1392 @staticmethod 

1393 def _compiler_and_cross_flags( 

1394 context: "BuildContext", 

1395 cmake_flags: list[str], 

1396 ) -> None: 

1397 

1398 if "CC" in os.environ: 

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

1400 elif context.is_cross_compiling: 

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

1402 

1403 if "CXX" in os.environ: 

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

1405 elif context.is_cross_compiling: 

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

1407 

1408 if context.is_cross_compiling: 

1409 deb_host2cmake_system = { 

1410 "linux": "Linux", 

1411 "kfreebsd": "kFreeBSD", 

1412 "hurd": "GNU", 

1413 } 

1414 

1415 gnu_cpu2system_processor = { 

1416 "arm": "armv7l", 

1417 "misp64el": "mips64", 

1418 "powerpc64le": "ppc64le", 

1419 } 

1420 dpkg_architecture_variables = context.dpkg_architecture_variables 

1421 

1422 try: 

1423 system_name = deb_host2cmake_system[ 

1424 dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1425 ] 

1426 except KeyError as e: 

1427 name = e.args[0] 

1428 _error( 

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

1430 ) 

1431 

1432 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"] 

1433 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu) 

1434 

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

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

1437 

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

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

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

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

1442 cmake_flags.append( 

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

1444 ) 

1445 

1446 def configure_impl( 

1447 self, 

1448 context: "BuildContext", 

1449 manifest: "HighLevelManifest", 

1450 **kwargs, 

1451 ) -> None: 

1452 cmake_flags = [ 

1453 "-DCMAKE_INSTALL_PREFIX=/usr", 

1454 "-DCMAKE_BUILD_TYPE=None", 

1455 "-DCMAKE_INSTALL_SYSCONFDIR=/etc", 

1456 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var", 

1457 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON", 

1458 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", 

1459 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON", 

1460 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON", 

1461 "-DCMAKE_INSTALL_RUNSTATEDIR=/run", 

1462 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON", 

1463 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON", 

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

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

1466 ] 

1467 if not context.is_terse_build: 

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

1469 if not context.should_run_tests: 

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

1471 

1472 self._compiler_and_cross_flags(context, cmake_flags) 

1473 

1474 if self.configure_args: 

1475 substitution = self.substitution 

1476 attr_path = self.attribute_path["configure_args"] 

1477 cmake_flags.extend( 

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

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

1480 ) 

1481 

1482 env_mod = self._default_cmake_env(context) 

1483 if "CPPFLAGS" in os.environ: 

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

1485 cppflags = os.environ["CPPFLAGS"] 

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

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

1488 env_mod = env_mod.combine( 

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

1490 EnvironmentModification( 

1491 replacements=( 

1492 ("CFLAGS", cflags), 

1493 ("CXXFLAGS", cxxflags), 

1494 ) 

1495 ) 

1496 ) 

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

1498 env_mod = env_mod.combine( 

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

1500 EnvironmentModification( 

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

1502 ) 

1503 ) 

1504 self.ensure_build_dir_exists() 

1505 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1506 

1507 with self.dump_logs_on_error( 

1508 "CMakeCache.txt", 

1509 "CMakeFiles/CMakeOutput.log", 

1510 "CMakeFiles/CMakeError.log", 

1511 ): 

1512 run_build_system_command( 

1513 "cmake", 

1514 *cmake_flags, 

1515 source_dir_from_build_dir, 

1516 cwd=self.build_directory, 

1517 env_mod=env_mod, 

1518 ) 

1519 

1520 def build_impl( 

1521 self, 

1522 context: "BuildContext", 

1523 manifest: "HighLevelManifest", 

1524 **kwargs, 

1525 ) -> None: 

1526 if self.target_build_system == "make": 

1527 make_flags = [] 

1528 if not context.is_terse_build: 

1529 make_flags.append("VERBOSE=1") 

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

1531 else: 

1532 self._ninja_support.run_ninja_build(context) 

1533 

1534 def test_impl( 

1535 self, 

1536 context: "BuildContext", 

1537 manifest: "HighLevelManifest", 

1538 *, 

1539 should_ignore_test_errors: bool = False, 

1540 **kwargs, 

1541 ) -> None: 

1542 env_mod = EnvironmentModification( 

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

1544 ) 

1545 if self.target_build_system == "make": 

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

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

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

1549 if not context.is_terse_build: 

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

1551 self._make_support.run_first_existing_target_if_any( 

1552 context, 

1553 ["check", "test"], 

1554 *make_flags, 

1555 env_mod=env_mod, 

1556 ) 

1557 else: 

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

1559 

1560 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1562 

1563 if not context.is_terse_build: 

1564 testsuite_flags.append("--verbose") 

1565 self._make_support.run_first_existing_target_if_any( 

1566 context, 

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

1568 ["check", "test"], 

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

1570 "VERBOSE=1", 

1571 ) 

1572 

1573 def install_impl( 

1574 self, 

1575 context: "BuildContext", 

1576 manifest: "HighLevelManifest", 

1577 dest_dir: str, 

1578 **kwargs, 

1579 ) -> None: 

1580 env_mod = EnvironmentModification( 

1581 replacements=( 

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

1583 ("DESTDIR", dest_dir), 

1584 ) 

1585 ).combine(self._default_cmake_env(context)) 

1586 run_build_system_command( 

1587 "cmake", 

1588 "--install", 

1589 self.build_directory, 

1590 env_mod=env_mod, 

1591 ) 

1592 

1593 def clean_impl( 

1594 self, 

1595 context: "BuildContext", 

1596 manifest: "HighLevelManifest", 

1597 clean_helper: "CleanHelper", 

1598 **kwargs, 

1599 ) -> None: 

1600 if self.out_of_source_build: 

1601 return 

1602 if self.target_build_system == "make": 

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

1604 self._make_support.run_first_existing_target_if_any( 

1605 context, 

1606 ["distclean", "realclean", "clean"], 

1607 ) 

1608 else: 

1609 self._ninja_support.run_ninja_clean(context) 

1610 

1611 

1612class MesonBuildSystemRule(StepBasedBuildSystemRule): 

1613 

1614 __slots__ = ( 

1615 "configure_args", 

1616 "_ninja_support", 

1617 ) 

1618 

1619 def __init__( 

1620 self, 

1621 parsed_data: "ParsedMesonBuildRuleDefinition", 

1622 attribute_path: AttributePath, 

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

1624 ) -> None: 

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

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

1627 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1628 

1629 @classmethod 

1630 def characteristics(cls) -> BuildSystemCharacteristics: 

1631 return BuildSystemCharacteristics( 

1632 out_of_source_builds="required", 

1633 ) 

1634 

1635 @classmethod 

1636 def auto_detect_build_system( 

1637 cls, 

1638 source_root: VirtualPath, 

1639 *args, 

1640 **kwargs, 

1641 ) -> bool: 

1642 return "meson.build" in source_root 

1643 

1644 @staticmethod 

1645 def _default_meson_env() -> EnvironmentModification: 

1646 replacements = { 

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

1648 } 

1649 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1650 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1651 return EnvironmentModification( 

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

1653 ) 

1654 

1655 @classmethod 

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

1657 cmake_generators = { 

1658 "make": "Unix Makefiles", 

1659 "ninja": "Ninja", 

1660 } 

1661 return cmake_generators[target_build_system] 

1662 

1663 @staticmethod 

1664 def _cross_flags( 

1665 context: "BuildContext", 

1666 meson_flags: list[str], 

1667 ) -> None: 

1668 if not context.is_cross_compiling: 

1669 return 

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

1671 cross_files_dir = os.path.abspath( 

1672 generated_content_dir( 

1673 subdir_key="meson-cross-files", 

1674 ) 

1675 ) 

1676 host_arch = context.dpkg_architecture_variables.current_host_arch 

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

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

1679 env = os.environ 

1680 if env.get("LC_ALL") != "C.UTF-8": 

1681 env = dict(env) 

1682 env["LC_ALL"] = "C.UTF-8" 

1683 else: 

1684 env = None 

1685 subprocess.check_call( 

1686 [ 

1687 "/usr/share/meson/debcrossgen", 

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

1689 f"-o{cross_file}", 

1690 ], 

1691 stdout=subprocess.DEVNULL, 

1692 env=env, 

1693 ) 

1694 

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

1696 meson_flags.append(cross_file) 

1697 

1698 def configure_impl( 

1699 self, 

1700 context: "BuildContext", 

1701 manifest: "HighLevelManifest", 

1702 **kwargs, 

1703 ) -> None: 

1704 meson_version = Version( 

1705 subprocess.check_output( 

1706 ["meson", "--version"], 

1707 encoding="utf-8", 

1708 ).strip() 

1709 ) 

1710 dpkg_architecture_variables = context.dpkg_architecture_variables 

1711 

1712 meson_flags = [ 

1713 "--wrap-mode=nodownload", 

1714 "--buildtype=plain", 

1715 "--prefix=/usr", 

1716 "--sysconfdir=/etc", 

1717 "--localstatedir=/var", 

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

1719 "--auto-features=enabled", 

1720 ] 

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

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

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

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

1725 # where the option exists. 

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

1727 

1728 self._cross_flags(context, meson_flags) 

1729 

1730 if self.configure_args: 

1731 substitution = self.substitution 

1732 attr_path = self.attribute_path["configure_args"] 

1733 meson_flags.extend( 

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

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

1736 ) 

1737 

1738 env_mod = self._default_meson_env() 

1739 

1740 self.ensure_build_dir_exists() 

1741 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1742 

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

1744 run_build_system_command( 

1745 "meson", 

1746 "setup", 

1747 source_dir_from_build_dir, 

1748 *meson_flags, 

1749 cwd=self.build_directory, 

1750 env_mod=env_mod, 

1751 ) 

1752 

1753 def build_impl( 

1754 self, 

1755 context: "BuildContext", 

1756 manifest: "HighLevelManifest", 

1757 **kwargs, 

1758 ) -> None: 

1759 self._ninja_support.run_ninja_build(context) 

1760 

1761 def test_impl( 

1762 self, 

1763 context: "BuildContext", 

1764 manifest: "HighLevelManifest", 

1765 *, 

1766 should_ignore_test_errors: bool = False, 

1767 **kwargs, 

1768 ) -> None: 

1769 env_mod = EnvironmentModification( 

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

1771 ).combine(self._default_meson_env()) 

1772 meson_args = [] 

1773 if not context.is_terse_build: 

1774 meson_args.append("--verbose") 

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

1776 run_build_system_command( 

1777 "meson", 

1778 "test", 

1779 *meson_args, 

1780 env_mod=env_mod, 

1781 cwd=self.build_directory, 

1782 ) 

1783 

1784 def install_impl( 

1785 self, 

1786 context: "BuildContext", 

1787 manifest: "HighLevelManifest", 

1788 dest_dir: str, 

1789 **kwargs, 

1790 ) -> None: 

1791 run_build_system_command( 

1792 "meson", 

1793 "install", 

1794 "--destdir", 

1795 dest_dir, 

1796 cwd=self.build_directory, 

1797 env_mod=self._default_meson_env(), 

1798 ) 

1799 

1800 def clean_impl( 

1801 self, 

1802 context: "BuildContext", 

1803 manifest: "HighLevelManifest", 

1804 clean_helper: "CleanHelper", 

1805 **kwargs, 

1806 ) -> None: 

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

1808 assert self.out_of_source_build 

1809 

1810 

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

1812 value = os.environ.get(envvar) 

1813 if value is None: 

1814 return 

1815 if include_cppflags: 

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

1817 if cppflags: 

1818 value = f"{value} {cppflags}" 

1819 

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

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

1822 

1823 

1824class ParsedGenericQmakeBuildRuleDefinition( 

1825 OptionalInstallDirectly, 

1826 OptionalInSourceBuild, 

1827 OptionalBuildDirectory, 

1828 OptionalTestRule, 

1829): 

1830 configure_args: NotRequired[list[str]] 

1831 

1832 

1833class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule): 

1834 

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

1836 

1837 def __init__( 

1838 self, 

1839 parsed_data: "ParsedGenericQmakeBuildRuleDefinition", 

1840 attribute_path: AttributePath, 

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

1842 ) -> None: 

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

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

1845 self._make_support = MakefileSupport.from_build_system(self) 

1846 

1847 @classmethod 

1848 def characteristics(cls) -> BuildSystemCharacteristics: 

1849 return BuildSystemCharacteristics( 

1850 out_of_source_builds="supported-and-default", 

1851 ) 

1852 

1853 @classmethod 

1854 def auto_detect_build_system( 

1855 cls, 

1856 source_root: VirtualPath, 

1857 *args, 

1858 **kwargs, 

1859 ) -> bool: 

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

1861 

1862 @classmethod 

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

1864 return { 

1865 "linux": "linux-g++", 

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

1867 "hurd": "hurd-g++", 

1868 } 

1869 

1870 def qmake_command(self) -> str: 

1871 raise NotImplementedError 

1872 

1873 def configure_impl( 

1874 self, 

1875 context: "BuildContext", 

1876 manifest: "HighLevelManifest", 

1877 **kwargs, 

1878 ) -> None: 

1879 

1880 configure_args = [ 

1881 "-makefile", 

1882 ] 

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

1884 

1885 if context.is_cross_compiling: 

1886 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1887 os2mkspec = self.os_mkspec_mapping() 

1888 try: 

1889 spec = os2mkspec[host_os] 

1890 except KeyError: 

1891 _error( 

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

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

1894 ) 

1895 configure_args.append("-spec") 

1896 configure_args.append(spec) 

1897 

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

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

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

1901 

1902 configure_args.append("QMAKE_STRIP=:") 

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

1904 

1905 if self.configure_args: 

1906 substitution = self.substitution 

1907 attr_path = self.attribute_path["configure_args"] 

1908 configure_args.extend( 

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

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

1911 ) 

1912 

1913 self.ensure_build_dir_exists() 

1914 if not self.out_of_source_build: 

1915 configure_args.append(self.relative_from_builddir_to_source()) 

1916 

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

1918 run_build_system_command( 

1919 qmake_cmd, 

1920 *configure_args, 

1921 cwd=self.build_directory, 

1922 ) 

1923 

1924 def build_impl( 

1925 self, 

1926 context: "BuildContext", 

1927 manifest: "HighLevelManifest", 

1928 **kwargs, 

1929 ) -> None: 

1930 self._make_support.run_make(context) 

1931 

1932 def test_impl( 

1933 self, 

1934 context: "BuildContext", 

1935 manifest: "HighLevelManifest", 

1936 *, 

1937 should_ignore_test_errors: bool = False, 

1938 **kwargs, 

1939 ) -> None: 

1940 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1942 

1943 if not context.is_terse_build: 

1944 testsuite_flags.append("--verbose") 

1945 self._make_support.run_first_existing_target_if_any( 

1946 context, 

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

1948 ["check", "test"], 

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

1950 "VERBOSE=1", 

1951 ) 

1952 

1953 def install_impl( 

1954 self, 

1955 context: "BuildContext", 

1956 manifest: "HighLevelManifest", 

1957 dest_dir: str, 

1958 **kwargs, 

1959 ) -> None: 

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

1961 self._make_support.run_first_existing_target_if_any( 

1962 context, 

1963 ["install"], 

1964 f"DESTDIR={dest_dir}", 

1965 "AM_UPDATE_INFO_DIR=no", 

1966 enable_parallelization=enable_parallelization, 

1967 ) 

1968 

1969 def clean_impl( 

1970 self, 

1971 context: "BuildContext", 

1972 manifest: "HighLevelManifest", 

1973 clean_helper: "CleanHelper", 

1974 **kwargs, 

1975 ) -> None: 

1976 if self.out_of_source_build: 

1977 return 

1978 self._make_support.run_first_existing_target_if_any( 

1979 context, 

1980 ["distclean", "realclean", "clean"], 

1981 ) 

1982 

1983 

1984class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule): 

1985 

1986 def qmake_command(self) -> str: 

1987 return "qmake" 

1988 

1989 

1990class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule): 

1991 

1992 def qmake_command(self) -> str: 

1993 return "qmake6" 

1994 

1995 

1996@debputy_build_system( 

1997 "make", 

1998 MakefileBuildSystemRule, 

1999 auto_detection_shadows_build_systems="debhelper", 

2000 online_reference_documentation=reference_documentation( 

2001 title="Make Build System", 

2002 description=textwrap.dedent( 

2003 """\ 

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

2005 

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

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

2008 

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

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

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

2012 some other directory. 

2013 """ 

2014 ), 

2015 attributes=[ 

2016 documented_attr( 

2017 "directory", 

2018 textwrap.dedent( 

2019 """\ 

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

2021 

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

2023 """ 

2024 ), 

2025 ), 

2026 documented_attr( 

2027 "build_target", 

2028 textwrap.dedent( 

2029 """\ 

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

2031 

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

2033 the default. 

2034 """ 

2035 ), 

2036 ), 

2037 documented_attr( 

2038 "test_target", 

2039 textwrap.dedent( 

2040 """\ 

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

2042 

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

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

2045 """ 

2046 ), 

2047 ), 

2048 documented_attr( 

2049 "install_target", 

2050 textwrap.dedent( 

2051 """\ 

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

2053 

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

2055 Otherwise, the step will be skipped. 

2056 """ 

2057 ), 

2058 ), 

2059 *docs_from( 

2060 DebputyParsedContentStandardConditional, 

2061 OptionalInstallDirectly, 

2062 OptionalTestRule, 

2063 BuildRuleParsedFormat, 

2064 ), 

2065 ], 

2066 ), 

2067) 

2068class ParsedMakeBuildRuleDefinition( 

2069 OptionalInstallDirectly, 

2070 OptionalTestRule, 

2071): 

2072 directory: NotRequired[FileSystemExactMatchRule] 

2073 build_target: NotRequired[str] 

2074 test_target: NotRequired[str] 

2075 install_target: NotRequired[str] 

2076 

2077 

2078@debputy_build_system( 

2079 "autoconf", 

2080 AutoconfBuildSystemRule, 

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

2082 online_reference_documentation=reference_documentation( 

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

2084 description=textwrap.dedent( 

2085 """\ 

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

2087 

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

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

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

2091 

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

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

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

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

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

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

2098 """ 

2099 ), 

2100 attributes=[ 

2101 documented_attr( 

2102 "configure_args", 

2103 textwrap.dedent( 

2104 """\ 

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

2106 """ 

2107 ), 

2108 ), 

2109 *docs_from( 

2110 DebputyParsedContentStandardConditional, 

2111 OptionalInstallDirectly, 

2112 OptionalInSourceBuild, 

2113 OptionalBuildDirectory, 

2114 OptionalTestRule, 

2115 BuildRuleParsedFormat, 

2116 ), 

2117 ], 

2118 ), 

2119) 

2120class ParsedAutoconfBuildRuleDefinition( 

2121 OptionalInstallDirectly, 

2122 OptionalInSourceBuild, 

2123 OptionalBuildDirectory, 

2124 OptionalTestRule, 

2125): 

2126 configure_args: NotRequired[list[str]] 

2127 

2128 

2129@debputy_build_system( 

2130 "cmake", 

2131 CMakeBuildSystemRule, 

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

2133 online_reference_documentation=reference_documentation( 

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

2135 description=textwrap.dedent( 

2136 """\ 

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

2138 

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

2140 """ 

2141 ), 

2142 attributes=[ 

2143 documented_attr( 

2144 "configure_args", 

2145 textwrap.dedent( 

2146 """\ 

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

2148 """ 

2149 ), 

2150 ), 

2151 documented_attr( 

2152 "target_build_system", 

2153 textwrap.dedent( 

2154 """\ 

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

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

2157 

2158 Supported options are: 

2159 * `make` - GNU Make 

2160 * `ninja` - Ninja 

2161 """ 

2162 ), 

2163 ), 

2164 *docs_from( 

2165 DebputyParsedContentStandardConditional, 

2166 OptionalInstallDirectly, 

2167 OptionalBuildDirectory, 

2168 OptionalTestRule, 

2169 BuildRuleParsedFormat, 

2170 ), 

2171 ], 

2172 ), 

2173) 

2174class ParsedCMakeBuildRuleDefinition( 

2175 OptionalInstallDirectly, 

2176 OptionalBuildDirectory, 

2177 OptionalTestRule, 

2178): 

2179 configure_args: NotRequired[list[str]] 

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

2181 

2182 

2183@debputy_build_system( 

2184 "meson", 

2185 MesonBuildSystemRule, 

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

2187 online_reference_documentation=reference_documentation( 

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

2189 description=textwrap.dedent( 

2190 """\ 

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

2192 

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

2194 """ 

2195 ), 

2196 attributes=[ 

2197 documented_attr( 

2198 "configure_args", 

2199 textwrap.dedent( 

2200 """\ 

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

2202 """ 

2203 ), 

2204 ), 

2205 *docs_from( 

2206 DebputyParsedContentStandardConditional, 

2207 OptionalInstallDirectly, 

2208 OptionalBuildDirectory, 

2209 OptionalTestRule, 

2210 BuildRuleParsedFormat, 

2211 ), 

2212 ], 

2213 ), 

2214) 

2215class ParsedMesonBuildRuleDefinition( 

2216 OptionalInstallDirectly, 

2217 OptionalBuildDirectory, 

2218 OptionalTestRule, 

2219): 

2220 configure_args: NotRequired[list[str]] 

2221 

2222 

2223@debputy_build_system( 

2224 "perl-build", 

2225 PerlBuildBuildSystemRule, 

2226 auto_detection_shadows_build_systems=[ 

2227 "debhelper", 

2228 "make", 

2229 "perl-makemaker", 

2230 ], 

2231 online_reference_documentation=reference_documentation( 

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

2233 description=textwrap.dedent( 

2234 """\ 

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

2236 

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

2238 upstream code. 

2239 """ 

2240 ), 

2241 attributes=[ 

2242 documented_attr( 

2243 "configure_args", 

2244 textwrap.dedent( 

2245 """\ 

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

2247 """ 

2248 ), 

2249 ), 

2250 *docs_from( 

2251 DebputyParsedContentStandardConditional, 

2252 OptionalInstallDirectly, 

2253 OptionalTestRule, 

2254 BuildRuleParsedFormat, 

2255 ), 

2256 ], 

2257 ), 

2258) 

2259class ParsedPerlBuildBuildRuleDefinition( 

2260 OptionalInstallDirectly, 

2261 OptionalTestRule, 

2262): 

2263 configure_args: NotRequired[list[str]] 

2264 

2265 

2266@debputy_build_system( 

2267 "debhelper", 

2268 DebhelperBuildSystemRule, 

2269 online_reference_documentation=reference_documentation( 

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

2271 description=textwrap.dedent( 

2272 """\ 

2273 Delegate to a debhelper provided build system 

2274 

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

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

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

2278 `dh-build-system` attribute. 

2279 """ 

2280 ), 

2281 attributes=[ 

2282 documented_attr( 

2283 "dh_build_system", 

2284 textwrap.dedent( 

2285 """\ 

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

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

2288 for that will be accepted. 

2289 

2290 Note that many debhelper build systems require extra build 

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

2292 of the relevant debhelper build system for details. 

2293 """ 

2294 ), 

2295 ), 

2296 documented_attr( 

2297 "configure_args", 

2298 textwrap.dedent( 

2299 """\ 

2300 Arguments to be passed to underlying configuration command 

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

2302 """ 

2303 ), 

2304 ), 

2305 *docs_from( 

2306 DebputyParsedContentStandardConditional, 

2307 OptionalInstallDirectly, 

2308 OptionalBuildDirectory, 

2309 OptionalTestRule, 

2310 BuildRuleParsedFormat, 

2311 ), 

2312 ], 

2313 ), 

2314) 

2315class ParsedDebhelperBuildRuleDefinition( 

2316 OptionalInstallDirectly, 

2317 OptionalBuildDirectory, 

2318 OptionalTestRule, 

2319): 

2320 configure_args: NotRequired[list[str]] 

2321 dh_build_system: NotRequired[str] 

2322 

2323 

2324@debputy_build_system( 

2325 "perl-makemaker", 

2326 PerlMakeMakerBuildSystemRule, 

2327 auto_detection_shadows_build_systems=[ 

2328 "debhelper", 

2329 "make", 

2330 ], 

2331 online_reference_documentation=reference_documentation( 

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

2333 description=textwrap.dedent( 

2334 """\ 

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

2336 

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

2338 upstream code. 

2339 """ 

2340 ), 

2341 attributes=[ 

2342 documented_attr( 

2343 "configure_args", 

2344 textwrap.dedent( 

2345 """\ 

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

2347 """ 

2348 ), 

2349 ), 

2350 *docs_from( 

2351 DebputyParsedContentStandardConditional, 

2352 OptionalInstallDirectly, 

2353 OptionalTestRule, 

2354 BuildRuleParsedFormat, 

2355 ), 

2356 ], 

2357 ), 

2358) 

2359class ParsedPerlMakeMakerBuildRuleDefinition( 

2360 OptionalInstallDirectly, 

2361 OptionalTestRule, 

2362): 

2363 configure_args: NotRequired[list[str]] 

2364 

2365 

2366@debputy_build_system( 

2367 "qmake", 

2368 QmakeBuildSystemRule, 

2369 auto_detection_shadows_build_systems=[ 

2370 "debhelper", 

2371 "make", 

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

2373 ], 

2374 online_reference_documentation=reference_documentation( 

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

2376 description=textwrap.dedent( 

2377 """\ 

2378 Build using the "qmake" by QT. 

2379 """ 

2380 ), 

2381 attributes=[ 

2382 documented_attr( 

2383 "configure_args", 

2384 textwrap.dedent( 

2385 """\ 

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

2387 """ 

2388 ), 

2389 ), 

2390 *docs_from( 

2391 DebputyParsedContentStandardConditional, 

2392 OptionalInstallDirectly, 

2393 OptionalInSourceBuild, 

2394 OptionalBuildDirectory, 

2395 OptionalTestRule, 

2396 BuildRuleParsedFormat, 

2397 ), 

2398 ], 

2399 ), 

2400) 

2401class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2402 pass 

2403 

2404 

2405@debputy_build_system( 

2406 "qmake6", 

2407 Qmake6BuildSystemRule, 

2408 auto_detection_shadows_build_systems=[ 

2409 "debhelper", 

2410 "make", 

2411 ], 

2412 online_reference_documentation=reference_documentation( 

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

2414 description=textwrap.dedent( 

2415 """\ 

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

2417 but is specifically for QT6. 

2418 """ 

2419 ), 

2420 attributes=[ 

2421 documented_attr( 

2422 "configure_args", 

2423 textwrap.dedent( 

2424 """\ 

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

2426 """ 

2427 ), 

2428 ), 

2429 *docs_from( 

2430 DebputyParsedContentStandardConditional, 

2431 OptionalInstallDirectly, 

2432 OptionalInSourceBuild, 

2433 OptionalBuildDirectory, 

2434 OptionalTestRule, 

2435 BuildRuleParsedFormat, 

2436 ), 

2437 ], 

2438 ), 

2439) 

2440class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2441 pass 

2442 

2443 

2444def _parse_default_environment( 

2445 _name: str, 

2446 parsed_data: EnvironmentSourceFormat, 

2447 attribute_path: AttributePath, 

2448 parser_context: ParserContextData, 

2449) -> ManifestProvidedBuildEnvironment: 

2450 return ManifestProvidedBuildEnvironment.from_environment_definition( 

2451 parsed_data, 

2452 attribute_path, 

2453 parser_context, 

2454 is_default=True, 

2455 ) 

2456 

2457 

2458def _parse_build_environments( 

2459 _name: str, 

2460 parsed_data: list[NamedEnvironmentSourceFormat], 

2461 attribute_path: AttributePath, 

2462 parser_context: ParserContextData, 

2463) -> list[ManifestProvidedBuildEnvironment]: 

2464 return [ 

2465 ManifestProvidedBuildEnvironment.from_environment_definition( 

2466 value, 

2467 attribute_path[idx], 

2468 parser_context, 

2469 is_default=False, 

2470 ) 

2471 for idx, value in enumerate(parsed_data) 

2472 ] 

2473 

2474 

2475def _handle_build_rules( 

2476 _name: str, 

2477 parsed_data: list[BuildRule], 

2478 _attribute_path: AttributePath, 

2479 _parser_context: ParserContextData, 

2480) -> list[BuildRule]: 

2481 return parsed_data