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

672 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-02-14 10:41 +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 """\ 

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

105 a non-default environment. 

106 

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

108 build commands. An example: 

109 

110 build-environments: 

111 - name: custom-env 

112 set: 

113 ENV_VAR: foo 

114 ANOTHER_ENV_VAR: bar 

115 builds: 

116 - autoconf: 

117 environment: custom-env 

118 

119 The environment definition has multiple attributes for setting environment variables 

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

121 result of the following order of operations. 

122 

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

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

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

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

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

128 

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

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

131 

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

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

134 """ 

135 ), 

136 attributes=[ 

137 documented_attr( 

138 "name", 

139 textwrap.dedent( 

140 """\ 

141 The name of the environment 

142 

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

144 """ 

145 ), 

146 ), 

147 documented_attr( 

148 "set", 

149 textwrap.dedent( 

150 """\ 

151 A mapping of environment variables to be set. 

152 

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

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

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

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

157 """ 

158 ), 

159 ), 

160 documented_attr( 

161 "override", 

162 textwrap.dedent( 

163 """\ 

164 A mapping of environment variables to set. 

165 

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

167 `dpkg-buildflags`. 

168 """ 

169 ), 

170 ), 

171 documented_attr( 

172 "unset", 

173 textwrap.dedent( 

174 """\ 

175 A list of environment variables to unset. 

176 

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

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

179 """ 

180 ), 

181 ), 

182 ], 

183 reference_documentation_url=manifest_format_doc( 

184 "build-environment-build-environment" 

185 ), 

186 ), 

187 ) 

188 api.pluggable_manifest_rule( 

189 OPARSER_MANIFEST_ROOT, 

190 "default-build-environment", 

191 EnvironmentSourceFormat, 

192 _parse_default_environment, 

193 register_value=False, 

194 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

195 inline_reference_documentation=reference_documentation( 

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

197 description=textwrap.dedent( 

198 """\ 

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

200 environment. 

201 

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

203 build commands. An example: 

204 

205 default-build-environment: 

206 set: 

207 ENV_VAR: foo 

208 ANOTHER_ENV_VAR: bar 

209 

210 The environment definition has multiple attributes for setting environment variables 

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

212 result of the following order of operations. 

213 

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

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

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

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

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

219 

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

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

222 

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

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

225 """ 

226 ), 

227 attributes=[ 

228 documented_attr( 

229 "set", 

230 textwrap.dedent( 

231 """\ 

232 A mapping of environment variables to be set. 

233 

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

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

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

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

238 """ 

239 ), 

240 ), 

241 documented_attr( 

242 "override", 

243 textwrap.dedent( 

244 """\ 

245 A mapping of environment variables to set. 

246 

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

248 `dpkg-buildflags`. 

249 """ 

250 ), 

251 ), 

252 documented_attr( 

253 "unset", 

254 textwrap.dedent( 

255 """\ 

256 A list of environment variables to unset. 

257 

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

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

260 """ 

261 ), 

262 ), 

263 ], 

264 reference_documentation_url=manifest_format_doc( 

265 "build-environment-build-environment" 

266 ), 

267 ), 

268 ) 

269 api.pluggable_manifest_rule( 

270 OPARSER_MANIFEST_ROOT, 

271 MK_BUILDS, 

272 list[BuildRule], 

273 _handle_build_rules, 

274 register_value=False, 

275 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

276 inline_reference_documentation=reference_documentation( 

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

278 description=textwrap.dedent( 

279 """\ 

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

281 which also defines the clean rules. 

282 

283 A simple example is: 

284 

285 ```yaml 

286 builds: 

287 - autoconf: 

288 configure-args: 

289 - "--enable-foo" 

290 - "--without=bar" 

291 ``` 

292 

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

294 for `libpam-krb5` might look. 

295 

296 ```yaml 

297 builds: 

298 - autoconf: 

299 for: libpam-krb5 

300 install-directly-to-package: true 

301 configure-args: 

302 - "--enable-reduced-depends" 

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

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

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

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

307 - autoconf: 

308 for: libpam-heimdal 

309 install-directly-to-package: true 

310 configure-args: 

311 - "--enable-reduced-depends" 

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

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

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

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

316 ``` 

317 """ 

318 ), 

319 ), 

320 ) 

321 

322 api.provide_manifest_keyword( 

323 TestRule, 

324 "skip-tests", 

325 lambda n, ap, pc: TestRule( 

326 ap.path, 

327 ManifestCondition.literal_bool(False), 

328 ManifestCondition.literal_bool(True), 

329 ), 

330 inline_reference_documentation=reference_documentation( 

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

332 description=textwrap.dedent( 

333 """\ 

334 Skip all build time tests. 

335 

336 Example: 

337 

338 ```yaml 

339 ...: 

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

341 test-rule: skip-tests 

342 ``` 

343 

344 """ 

345 ), 

346 ), 

347 ) 

348 

349 api.pluggable_manifest_rule( 

350 TestRule, 

351 "skip-tests-when", 

352 Conditional, 

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

354 ap.path, 

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

356 ManifestCondition.literal_bool(False), 

357 ), 

358 source_format=ManifestCondition, 

359 inline_reference_documentation=reference_documentation( 

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

361 description=textwrap.dedent( 

362 """\ 

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

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

365 

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

367 then wrap the conditional with `not:`. 

368 

369 Example: 

370 

371 ```yaml 

372 ...: 

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

374 test-rule: 

375 skip-tests-when: 

376 not: 

377 arch-matches: "linux-any" 

378 ``` 

379 

380 """ 

381 ), 

382 ), 

383 ) 

384 

385 

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

387 api.register_build_system(ParsedAutoconfBuildRuleDefinition) 

388 api.register_build_system(ParsedMakeBuildRuleDefinition) 

389 

390 api.register_build_system(ParsedPerlBuildBuildRuleDefinition) 

391 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition) 

392 api.register_build_system(ParsedDebhelperBuildRuleDefinition) 

393 

394 api.register_build_system(ParsedCMakeBuildRuleDefinition) 

395 api.register_build_system(ParsedMesonBuildRuleDefinition) 

396 

397 api.register_build_system(ParsedQmakeBuildRuleDefinition) 

398 api.register_build_system(ParsedQmake6BuildRuleDefinition) 

399 

400 

401class EnvironmentSourceFormat(TypedDict): 

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

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

404 unset: NotRequired[list[str]] 

405 

406 

407class NamedEnvironmentSourceFormat(EnvironmentSourceFormat): 

408 name: str 

409 

410 

411_READ_ONLY_ENV_VARS = { 

412 "DEB_CHECK_COMMAND": None, 

413 "DEB_SIGN_KEYID": None, 

414 "DEB_SIGN_KEYFILE": None, 

415 "DEB_BUILD_OPTIONS": "DEB_BUILD_MAINT_OPTIONS", 

416 "DEB_BUILD_PROFILES": None, 

417 "DEB_RULES_REQUIRES_ROOT": None, 

418 "DEB_GAIN_ROOT_COMMAND": None, 

419 "DH_EXTRA_ADDONS": None, 

420 "DH_NO_ACT": None, 

421 "XDG_RUNTIME_DIR": None, 

422 "HOME": None, 

423} 

424 

425 

426def _check_variables( 

427 env_vars: Iterable[str], 

428 attribute_path: AttributePath, 

429) -> None: 

430 for env_var in env_vars: 

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

432 continue 

433 alt = _READ_ONLY_ENV_VARS.get(env_var) 

434 var_path = attribute_path[env_var].path_key_lc 

435 if alt is None: 

436 raise ManifestParseException( 

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

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

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

440 ) 

441 else: 

442 raise ManifestParseException( 

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

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

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

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

447 f" The problematic definition was {var_path}" 

448 ) 

449 

450 

451def _no_overlap( 

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

453 rhs: Container[str], 

454 lhs_key: str, 

455 rhs_key: str, 

456 redundant_key: str, 

457 attribute_path: AttributePath, 

458) -> None: 

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

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

461 if var not in rhs: 

462 continue 

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

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

465 r_path = lhs_path if redundant_key == rhs_key else rhs_path 

466 raise ManifestParseException( 

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

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

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

470 f" the two definitions." 

471 ) 

472 

473 

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

475class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition): 

476 

477 name: str 

478 is_default: bool 

479 attribute_path: AttributePath 

480 parser_context: ParserContextData 

481 

482 set_vars: Mapping[str, str] 

483 override_vars: Mapping[str, str] 

484 unset_vars: Sequence[str] 

485 

486 @classmethod 

487 def from_environment_definition( 

488 cls, 

489 env: EnvironmentSourceFormat, 

490 attribute_path: AttributePath, 

491 parser_context: ParserContextData, 

492 is_default: bool = False, 

493 ) -> Self: 

494 reference_name: str | None 

495 if is_default: 

496 name = "default-env" 

497 reference_name = None 

498 else: 

499 named_env = cast("NamedEnvironmentSourceFormat", env) 

500 name = named_env["name"] 

501 reference_name = name 

502 

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

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

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

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

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

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

509 

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

511 raise ManifestParseException( 

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

513 " some content or delete the definition." 

514 ) 

515 

516 _no_overlap( 

517 enumerate(unset_vars), 

518 set_vars, 

519 "unset", 

520 "set", 

521 "set", 

522 attribute_path, 

523 ) 

524 _no_overlap( 

525 enumerate(unset_vars), 

526 override_vars, 

527 "unset", 

528 "override", 

529 "override", 

530 attribute_path, 

531 ) 

532 _no_overlap( 

533 override_vars, 

534 set_vars, 

535 "override", 

536 "set", 

537 "set", 

538 attribute_path, 

539 ) 

540 

541 r = cls( 

542 name, 

543 is_default, 

544 attribute_path, 

545 parser_context, 

546 set_vars, 

547 override_vars, 

548 unset_vars, 

549 ) 

550 parser_context._register_build_environment( 

551 reference_name, 

552 r, 

553 attribute_path, 

554 is_default, 

555 ) 

556 

557 return r 

558 

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

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

561 env.update(set_vars) 

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

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

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

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

566 for var in overlapping_env: 

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

568 _warn( 

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

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

571 f" `set`." 

572 ) 

573 env.update(dpkg_env) 

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

575 env.update(override_vars) 

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

577 for var in unset_vars: 

578 try: 

579 del env[var] 

580 except KeyError: 

581 pass 

582 

583 

584_MAKE_DEFAULT_TOOLS = [ 

585 ("CC", "gcc"), 

586 ("CXX", "g++"), 

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

588] 

589 

590 

591class MakefileBuildSystemRule(StepBasedBuildSystemRule): 

592 

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

594 

595 def __init__( 

596 self, 

597 attributes: "ParsedMakeBuildRuleDefinition", 

598 attribute_path: AttributePath, 

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

600 ) -> None: 

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

602 directory = attributes.get("directory") 

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

604 self._make_support = MakefileSupport.from_build_system(self) 

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

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

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

608 

609 @classmethod 

610 def auto_detect_build_system( 

611 cls, 

612 source_root: VirtualPath, 

613 *args, 

614 **kwargs, 

615 ) -> bool: 

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

617 

618 @classmethod 

619 def characteristics(cls) -> BuildSystemCharacteristics: 

620 return BuildSystemCharacteristics( 

621 out_of_source_builds="not-supported", 

622 ) 

623 

624 def configure_impl( 

625 self, 

626 context: "BuildContext", 

627 manifest: "HighLevelManifest", 

628 **kwargs, 

629 ) -> None: 

630 # No configure step 

631 pass 

632 

633 def build_impl( 

634 self, 

635 context: "BuildContext", 

636 manifest: "HighLevelManifest", 

637 **kwargs, 

638 ) -> None: 

639 extra_vars = [] 

640 build_target = self._build_target 

641 if build_target is not None: 

642 extra_vars.append(build_target) 

643 if context.is_cross_compiling: 

644 for envvar, tool in _MAKE_DEFAULT_TOOLS: 

645 cross_tool = os.environ.get(envvar) 

646 if cross_tool is None: 

647 cross_tool = context.cross_tool(tool) 

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

649 self._make_support.run_make( 

650 context, 

651 *extra_vars, 

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

653 directory=self._directory, 

654 ) 

655 

656 def test_impl( 

657 self, 

658 context: "BuildContext", 

659 manifest: "HighLevelManifest", 

660 *, 

661 should_ignore_test_errors: bool = False, 

662 **kwargs, 

663 ) -> None: 

664 self._run_make_maybe_explicit_target( 

665 context, 

666 self._test_target, 

667 ["test", "check"], 

668 ) 

669 

670 def install_impl( 

671 self, 

672 context: "BuildContext", 

673 manifest: "HighLevelManifest", 

674 dest_dir: str, 

675 **kwargs, 

676 ) -> None: 

677 self._run_make_maybe_explicit_target( 

678 context, 

679 self._install_target, 

680 ["install"], 

681 f"DESTDIR={dest_dir}", 

682 "AM_UPDATE_INFO_DIR=no", 

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

684 ) 

685 

686 def _run_make_maybe_explicit_target( 

687 self, 

688 context: "BuildContext", 

689 provided_target: str | None, 

690 fallback_targets: Sequence[str], 

691 *make_args: str, 

692 ) -> None: 

693 make_support = self._make_support 

694 if provided_target is not None: 

695 make_support.run_make( 

696 context, 

697 provided_target, 

698 *make_args, 

699 directory=self._directory, 

700 ) 

701 else: 

702 make_support.run_first_existing_target_if_any( 

703 context, 

704 fallback_targets, 

705 *make_args, 

706 directory=self._directory, 

707 ) 

708 

709 def clean_impl( 

710 self, 

711 context: "BuildContext", 

712 manifest: "HighLevelManifest", 

713 clean_helper: "CleanHelper", 

714 **kwargs, 

715 ) -> None: 

716 self._make_support.run_first_existing_target_if_any( 

717 context, 

718 ["distclean", "realclean", "clean"], 

719 ) 

720 

721 

722class PerlBuildBuildSystemRule(StepBasedBuildSystemRule): 

723 

724 __slots__ = "configure_args" 

725 

726 def __init__( 

727 self, 

728 attributes: "ParsedPerlBuildBuildRuleDefinition", 

729 attribute_path: AttributePath, 

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

731 ) -> None: 

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

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

734 

735 @classmethod 

736 def auto_detect_build_system( 

737 cls, 

738 source_root: VirtualPath, 

739 *args, 

740 **kwargs, 

741 ) -> bool: 

742 return "Build.PL" in source_root 

743 

744 @classmethod 

745 def characteristics(cls) -> BuildSystemCharacteristics: 

746 return BuildSystemCharacteristics( 

747 out_of_source_builds="not-supported", 

748 ) 

749 

750 @staticmethod 

751 def _perl_cross_build_env( 

752 context: "BuildContext", 

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

754 perl_config_data = resolve_perl_config( 

755 context.dpkg_architecture_variables, 

756 None, 

757 ) 

758 if context.is_cross_compiling: 

759 perl5lib_dir = perl_config_data.cross_inc_dir 

760 if perl5lib_dir is not None: 

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

762 if env_perl5lib is not None: 

763 perl5lib_dir = ( 

764 perl5lib_dir + perl_config_data.path_sep + env_perl5lib 

765 ) 

766 env_mod = EnvironmentModification( 

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

768 ) 

769 return perl_config_data, env_mod 

770 return perl_config_data, None 

771 

772 def configure_impl( 

773 self, 

774 context: "BuildContext", 

775 manifest: "HighLevelManifest", 

776 **kwargs, 

777 ) -> None: 

778 perl_config_data, cross_env_mod = self._perl_cross_build_env(context) 

779 configure_env = EnvironmentModification( 

780 replacements=( 

781 ("PERL_MM_USE_DEFAULT", "1"), 

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

783 ) 

784 ) 

785 if cross_env_mod is not None: 

786 configure_env = configure_env.combine(cross_env_mod) 

787 

788 configure_cmd = [ 

789 PERL_CMD, 

790 "Build.PL", 

791 "--installdirs", 

792 "vendor", 

793 ] 

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

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

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

797 

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

799 configure_cmd.append("--config") 

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

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

802 

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

804 configure_cmd.append("--config") 

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

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

807 if self.configure_args: 

808 substitution = self.substitution 

809 attr_path = self.attribute_path["configure_args"] 

810 configure_cmd.extend( 

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

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

813 ) 

814 run_build_system_command(*configure_cmd, env_mod=configure_env) 

815 

816 def build_impl( 

817 self, 

818 context: "BuildContext", 

819 manifest: "HighLevelManifest", 

820 **kwargs, 

821 ) -> None: 

822 _, cross_env_mod = self._perl_cross_build_env(context) 

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

824 

825 def test_impl( 

826 self, 

827 context: "BuildContext", 

828 manifest: "HighLevelManifest", 

829 *, 

830 should_ignore_test_errors: bool = False, 

831 **kwargs, 

832 ) -> None: 

833 _, cross_env_mod = self._perl_cross_build_env(context) 

834 run_build_system_command( 

835 PERL_CMD, 

836 "Build", 

837 "test", 

838 "--verbose", 

839 "1", 

840 env_mod=cross_env_mod, 

841 ) 

842 

843 def install_impl( 

844 self, 

845 context: "BuildContext", 

846 manifest: "HighLevelManifest", 

847 dest_dir: str, 

848 **kwargs, 

849 ) -> None: 

850 _, cross_env_mod = self._perl_cross_build_env(context) 

851 run_build_system_command( 

852 PERL_CMD, 

853 "Build", 

854 "install", 

855 "--destdir", 

856 dest_dir, 

857 "--create_packlist", 

858 "0", 

859 env_mod=cross_env_mod, 

860 ) 

861 

862 def clean_impl( 

863 self, 

864 context: "BuildContext", 

865 manifest: "HighLevelManifest", 

866 clean_helper: "CleanHelper", 

867 **kwargs, 

868 ) -> None: 

869 _, cross_env_mod = self._perl_cross_build_env(context) 

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

871 run_build_system_command( 

872 PERL_CMD, 

873 "Build", 

874 "realclean", 

875 "--allow_mb_mismatch", 

876 "1", 

877 env_mod=cross_env_mod, 

878 ) 

879 

880 

881class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule): 

882 

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

884 

885 def __init__( 

886 self, 

887 attributes: "ParsedPerlBuildBuildRuleDefinition", 

888 attribute_path: AttributePath, 

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

890 ) -> None: 

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

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

893 self._make_support = MakefileSupport.from_build_system(self) 

894 

895 @classmethod 

896 def auto_detect_build_system( 

897 cls, 

898 source_root: VirtualPath, 

899 *args, 

900 **kwargs, 

901 ) -> bool: 

902 return "Makefile.PL" in source_root 

903 

904 @classmethod 

905 def characteristics(cls) -> BuildSystemCharacteristics: 

906 return BuildSystemCharacteristics( 

907 out_of_source_builds="not-supported", 

908 ) 

909 

910 def configure_impl( 

911 self, 

912 context: "BuildContext", 

913 manifest: "HighLevelManifest", 

914 **kwargs, 

915 ) -> None: 

916 configure_env = EnvironmentModification( 

917 replacements=( 

918 ("PERL_MM_USE_DEFAULT", "1"), 

919 ("PERL_AUTOINSTALL", "--skipdeps"), 

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

921 ) 

922 ) 

923 perl_args = [] 

924 mm_args = ["INSTALLDIRS=vendor"] 

925 if "CFLAGS" in os.environ: 

926 mm_args.append( 

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

928 ) 

929 

930 perl_config_data = resolve_perl_config( 

931 context.dpkg_architecture_variables, 

932 None, 

933 ) 

934 

935 if "LDFLAGS" in os.environ: 

936 mm_args.append( 

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

938 ) 

939 

940 if context.is_cross_compiling: 

941 perl5lib_dir = perl_config_data.cross_inc_dir 

942 if perl5lib_dir is not None: 

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

944 

945 if self.configure_args: 

946 substitution = self.substitution 

947 attr_path = self.attribute_path["configure_args"] 

948 mm_args.extend( 

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

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

951 ) 

952 run_build_system_command( 

953 PERL_CMD, 

954 *perl_args, 

955 "Makefile.PL", 

956 *mm_args, 

957 env_mod=configure_env, 

958 ) 

959 

960 def build_impl( 

961 self, 

962 context: "BuildContext", 

963 manifest: "HighLevelManifest", 

964 **kwargs, 

965 ) -> None: 

966 self._make_support.run_make(context) 

967 

968 def test_impl( 

969 self, 

970 context: "BuildContext", 

971 manifest: "HighLevelManifest", 

972 *, 

973 should_ignore_test_errors: bool = False, 

974 **kwargs, 

975 ) -> None: 

976 self._make_support.run_first_existing_target_if_any( 

977 context, 

978 ["check", "test"], 

979 "TEST_VERBOSE=1", 

980 ) 

981 

982 def install_impl( 

983 self, 

984 context: "BuildContext", 

985 manifest: "HighLevelManifest", 

986 dest_dir: str, 

987 **kwargs, 

988 ) -> None: 

989 is_mm_makefile = False 

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

991 for line in fd: 

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

993 is_mm_makefile = True 

994 break 

995 

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

997 

998 # Special case for Makefile.PL that uses 

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

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

1001 if is_mm_makefile: 

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

1003 

1004 self._make_support.run_first_existing_target_if_any( 

1005 context, 

1006 ["install"], 

1007 *install_args, 

1008 ) 

1009 

1010 def clean_impl( 

1011 self, 

1012 context: "BuildContext", 

1013 manifest: "HighLevelManifest", 

1014 clean_helper: "CleanHelper", 

1015 **kwargs, 

1016 ) -> None: 

1017 self._make_support.run_first_existing_target_if_any( 

1018 context, 

1019 ["distclean", "realclean", "clean"], 

1020 ) 

1021 

1022 

1023class DebhelperBuildSystemRule(StepBasedBuildSystemRule): 

1024 

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

1026 

1027 def __init__( 

1028 self, 

1029 parsed_data: "ParsedDebhelperBuildRuleDefinition", 

1030 attribute_path: AttributePath, 

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

1032 ) -> None: 

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

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

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

1036 

1037 @classmethod 

1038 def auto_detect_build_system( 

1039 cls, 

1040 source_root: VirtualPath, 

1041 *args, 

1042 **kwargs, 

1043 ) -> bool: 

1044 try: 

1045 v = subprocess.check_output( 

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

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

1048 stderr=subprocess.DEVNULL, 

1049 cwd=source_root.fs_path, 

1050 ) 

1051 except subprocess.CalledProcessError: 

1052 return False 

1053 d = json.loads(v) 

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

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

1056 

1057 @classmethod 

1058 def characteristics(cls) -> BuildSystemCharacteristics: 

1059 return BuildSystemCharacteristics( 

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

1061 ) 

1062 

1063 def before_first_impl_step( 

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

1065 ) -> None: 

1066 dh_build_system = self.dh_build_system 

1067 if dh_build_system is None: 

1068 return 

1069 try: 

1070 subprocess.check_call( 

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

1072 ) 

1073 except FileNotFoundError: 

1074 _error( 

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

1076 ) 

1077 except subprocess.SubprocessError: 

1078 raise ManifestInvalidUserDataException( 

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

1080 f" be available according to" 

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

1082 ) from None 

1083 

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

1085 default_options = [] 

1086 if self.dh_build_system is not None: 

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

1088 if self.build_directory is not None: 

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

1090 

1091 return default_options 

1092 

1093 def configure_impl( 

1094 self, 

1095 context: "BuildContext", 

1096 manifest: "HighLevelManifest", 

1097 **kwargs, 

1098 ) -> None: 

1099 if ( 

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

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

1102 run_build_system_command("dh_update_autotools_config") 

1103 run_build_system_command("dh_autoreconf") 

1104 

1105 default_options = self._default_options() 

1106 configure_args = default_options.copy() 

1107 if self.configure_args: 

1108 configure_args.append("--") 

1109 substitution = self.substitution 

1110 attr_path = self.attribute_path["configure_args"] 

1111 configure_args.extend( 

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

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

1114 ) 

1115 run_build_system_command("dh_auto_configure", *configure_args) 

1116 

1117 def build_impl( 

1118 self, 

1119 context: "BuildContext", 

1120 manifest: "HighLevelManifest", 

1121 **kwargs, 

1122 ) -> None: 

1123 default_options = self._default_options() 

1124 run_build_system_command("dh_auto_build", *default_options) 

1125 

1126 def test_impl( 

1127 self, 

1128 context: "BuildContext", 

1129 manifest: "HighLevelManifest", 

1130 *, 

1131 should_ignore_test_errors: bool = False, 

1132 **kwargs, 

1133 ) -> None: 

1134 default_options = self._default_options() 

1135 run_build_system_command("dh_auto_test", *default_options) 

1136 

1137 def install_impl( 

1138 self, 

1139 context: "BuildContext", 

1140 manifest: "HighLevelManifest", 

1141 dest_dir: str, 

1142 **kwargs, 

1143 ) -> None: 

1144 default_options = self._default_options() 

1145 run_build_system_command( 

1146 "dh_auto_install", 

1147 *default_options, 

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

1149 ) 

1150 

1151 def clean_impl( 

1152 self, 

1153 context: "BuildContext", 

1154 manifest: "HighLevelManifest", 

1155 clean_helper: "CleanHelper", 

1156 **kwargs, 

1157 ) -> None: 

1158 default_options = self._default_options() 

1159 run_build_system_command("dh_auto_clean", *default_options) 

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

1161 

1162 

1163class AutoconfBuildSystemRule(StepBasedBuildSystemRule): 

1164 

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

1166 

1167 def __init__( 

1168 self, 

1169 parsed_data: "ParsedAutoconfBuildRuleDefinition", 

1170 attribute_path: AttributePath, 

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

1172 ) -> None: 

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

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

1175 self._make_support = MakefileSupport.from_build_system(self) 

1176 

1177 @classmethod 

1178 def characteristics(cls) -> BuildSystemCharacteristics: 

1179 return BuildSystemCharacteristics( 

1180 out_of_source_builds="supported-and-default", 

1181 ) 

1182 

1183 @classmethod 

1184 def auto_detect_build_system( 

1185 cls, 

1186 source_root: VirtualPath, 

1187 *args, 

1188 **kwargs, 

1189 ) -> bool: 

1190 if "configure.ac" in source_root: 

1191 return True 

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

1193 if configure_in is not None and configure_in.is_file: 

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

1195 for no, line in enumerate(fd): 

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

1197 break 

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

1199 return True 

1200 configure = source_root.get("configure") 

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

1202 return False 

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

1204 for no, line in enumerate(fd): 

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

1206 break 

1207 if b"GNU Autoconf" in line: 

1208 return True 

1209 return False 

1210 

1211 def configure_impl( 

1212 self, 

1213 context: "BuildContext", 

1214 manifest: "HighLevelManifest", 

1215 **kwargs, 

1216 ) -> None: 

1217 if ( 

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

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

1220 run_build_system_command("dh_update_autotools_config") 

1221 run_build_system_command("dh_autoreconf") 

1222 

1223 dpkg_architecture_variables = context.dpkg_architecture_variables 

1224 multi_arch = dpkg_architecture_variables.current_host_multiarch 

1225 silent_rules = ( 

1226 "--enable-silent-rules" 

1227 if context.is_terse_build 

1228 else "--disable-silent-rules" 

1229 ) 

1230 

1231 configure_args = [ 

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

1233 "--prefix=/usr", 

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

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

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

1237 "--sysconfdir=/etc", 

1238 "--localstatedir=/var", 

1239 "--disable-option-checking", 

1240 silent_rules, 

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

1242 "--runstatedir=/run", 

1243 "--disable-maintainer-mode", 

1244 "--disable-dependency-tracking", 

1245 ] 

1246 if dpkg_architecture_variables.is_cross_compiling: 

1247 configure_args.append( 

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

1249 ) 

1250 if self.configure_args: 

1251 substitution = self.substitution 

1252 attr_path = self.attribute_path["configure_args"] 

1253 configure_args.extend( 

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

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

1256 ) 

1257 self.ensure_build_dir_exists() 

1258 configure_script = self.relative_from_builddir_to_source("configure") 

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

1260 run_build_system_command( 

1261 configure_script, 

1262 *configure_args, 

1263 cwd=self.build_directory, 

1264 ) 

1265 

1266 def build_impl( 

1267 self, 

1268 context: "BuildContext", 

1269 manifest: "HighLevelManifest", 

1270 **kwargs, 

1271 ) -> None: 

1272 self._make_support.run_make(context) 

1273 

1274 def test_impl( 

1275 self, 

1276 context: "BuildContext", 

1277 manifest: "HighLevelManifest", 

1278 *, 

1279 should_ignore_test_errors: bool = False, 

1280 **kwargs, 

1281 ) -> None: 

1282 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1284 

1285 if not context.is_terse_build: 

1286 testsuite_flags.append("--verbose") 

1287 self._make_support.run_first_existing_target_if_any( 

1288 context, 

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

1290 ["check", "test"], 

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

1292 "VERBOSE=1", 

1293 ) 

1294 

1295 def install_impl( 

1296 self, 

1297 context: "BuildContext", 

1298 manifest: "HighLevelManifest", 

1299 dest_dir: str, 

1300 **kwargs, 

1301 ) -> None: 

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

1303 self._make_support.run_first_existing_target_if_any( 

1304 context, 

1305 ["install"], 

1306 f"DESTDIR={dest_dir}", 

1307 "AM_UPDATE_INFO_DIR=no", 

1308 enable_parallelization=enable_parallelization, 

1309 ) 

1310 

1311 def clean_impl( 

1312 self, 

1313 context: "BuildContext", 

1314 manifest: "HighLevelManifest", 

1315 clean_helper: "CleanHelper", 

1316 **kwargs, 

1317 ) -> None: 

1318 if self.out_of_source_build: 

1319 return 

1320 self._make_support.run_first_existing_target_if_any( 

1321 context, 

1322 ["distclean", "realclean", "clean"], 

1323 ) 

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

1325 

1326 

1327class CMakeBuildSystemRule(StepBasedBuildSystemRule): 

1328 

1329 __slots__ = ( 

1330 "configure_args", 

1331 "target_build_system", 

1332 "_make_support", 

1333 "_ninja_support", 

1334 ) 

1335 

1336 def __init__( 

1337 self, 

1338 parsed_data: "ParsedCMakeBuildRuleDefinition", 

1339 attribute_path: AttributePath, 

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

1341 ) -> None: 

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

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

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

1345 "target_build_system", "make" 

1346 ) 

1347 self._make_support = MakefileSupport.from_build_system(self) 

1348 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1349 

1350 @classmethod 

1351 def characteristics(cls) -> BuildSystemCharacteristics: 

1352 return BuildSystemCharacteristics( 

1353 out_of_source_builds="required", 

1354 ) 

1355 

1356 @classmethod 

1357 def auto_detect_build_system( 

1358 cls, 

1359 source_root: VirtualPath, 

1360 *args, 

1361 **kwargs, 

1362 ) -> bool: 

1363 return "CMakeLists.txt" in source_root 

1364 

1365 @staticmethod 

1366 def _default_cmake_env( 

1367 build_context: "BuildContext", 

1368 ) -> EnvironmentModification: 

1369 replacements = {} 

1370 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1371 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1372 if "PKG_CONFIG" not in os.environ: 

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

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

1375 return EnvironmentModification( 

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

1377 ) 

1378 

1379 @classmethod 

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

1381 cmake_generators = { 

1382 "make": "Unix Makefiles", 

1383 "ninja": "Ninja", 

1384 } 

1385 return cmake_generators[target_build_system] 

1386 

1387 @staticmethod 

1388 def _compiler_and_cross_flags( 

1389 context: "BuildContext", 

1390 cmake_flags: list[str], 

1391 ) -> None: 

1392 

1393 if "CC" in os.environ: 

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

1395 elif context.is_cross_compiling: 

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

1397 

1398 if "CXX" in os.environ: 

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

1400 elif context.is_cross_compiling: 

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

1402 

1403 if context.is_cross_compiling: 

1404 deb_host2cmake_system = { 

1405 "linux": "Linux", 

1406 "kfreebsd": "kFreeBSD", 

1407 "hurd": "GNU", 

1408 } 

1409 

1410 gnu_cpu2system_processor = { 

1411 "arm": "armv7l", 

1412 "misp64el": "mips64", 

1413 "powerpc64le": "ppc64le", 

1414 } 

1415 dpkg_architecture_variables = context.dpkg_architecture_variables 

1416 

1417 try: 

1418 system_name = deb_host2cmake_system[ 

1419 dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1420 ] 

1421 except KeyError as e: 

1422 name = e.args[0] 

1423 _error( 

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

1425 ) 

1426 

1427 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"] 

1428 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu) 

1429 

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

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

1432 

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

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

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

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

1437 cmake_flags.append( 

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

1439 ) 

1440 

1441 def configure_impl( 

1442 self, 

1443 context: "BuildContext", 

1444 manifest: "HighLevelManifest", 

1445 **kwargs, 

1446 ) -> None: 

1447 cmake_flags = [ 

1448 "-DCMAKE_INSTALL_PREFIX=/usr", 

1449 "-DCMAKE_BUILD_TYPE=None", 

1450 "-DCMAKE_INSTALL_SYSCONFDIR=/etc", 

1451 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var", 

1452 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON", 

1453 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", 

1454 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON", 

1455 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON", 

1456 "-DCMAKE_INSTALL_RUNSTATEDIR=/run", 

1457 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON", 

1458 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON", 

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

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

1461 ] 

1462 if not context.is_terse_build: 

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

1464 if not context.should_run_tests: 

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

1466 

1467 self._compiler_and_cross_flags(context, cmake_flags) 

1468 

1469 if self.configure_args: 

1470 substitution = self.substitution 

1471 attr_path = self.attribute_path["configure_args"] 

1472 cmake_flags.extend( 

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

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

1475 ) 

1476 

1477 env_mod = self._default_cmake_env(context) 

1478 if "CPPFLAGS" in os.environ: 

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

1480 cppflags = os.environ["CPPFLAGS"] 

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

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

1483 env_mod = env_mod.combine( 

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

1485 EnvironmentModification( 

1486 replacements=( 

1487 ("CFLAGS", cflags), 

1488 ("CXXFLAGS", cxxflags), 

1489 ) 

1490 ) 

1491 ) 

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

1493 env_mod = env_mod.combine( 

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

1495 EnvironmentModification( 

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

1497 ) 

1498 ) 

1499 self.ensure_build_dir_exists() 

1500 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1501 

1502 with self.dump_logs_on_error( 

1503 "CMakeCache.txt", 

1504 "CMakeFiles/CMakeOutput.log", 

1505 "CMakeFiles/CMakeError.log", 

1506 ): 

1507 run_build_system_command( 

1508 "cmake", 

1509 *cmake_flags, 

1510 source_dir_from_build_dir, 

1511 cwd=self.build_directory, 

1512 env_mod=env_mod, 

1513 ) 

1514 

1515 def build_impl( 

1516 self, 

1517 context: "BuildContext", 

1518 manifest: "HighLevelManifest", 

1519 **kwargs, 

1520 ) -> None: 

1521 if self.target_build_system == "make": 

1522 make_flags = [] 

1523 if not context.is_terse_build: 

1524 make_flags.append("VERBOSE=1") 

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

1526 else: 

1527 self._ninja_support.run_ninja_build(context) 

1528 

1529 def test_impl( 

1530 self, 

1531 context: "BuildContext", 

1532 manifest: "HighLevelManifest", 

1533 *, 

1534 should_ignore_test_errors: bool = False, 

1535 **kwargs, 

1536 ) -> None: 

1537 env_mod = EnvironmentModification( 

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

1539 ) 

1540 if self.target_build_system == "make": 

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

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

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

1544 if not context.is_terse_build: 

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

1546 self._make_support.run_first_existing_target_if_any( 

1547 context, 

1548 ["check", "test"], 

1549 *make_flags, 

1550 env_mod=env_mod, 

1551 ) 

1552 else: 

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

1554 

1555 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1557 

1558 if not context.is_terse_build: 

1559 testsuite_flags.append("--verbose") 

1560 self._make_support.run_first_existing_target_if_any( 

1561 context, 

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

1563 ["check", "test"], 

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

1565 "VERBOSE=1", 

1566 ) 

1567 

1568 def install_impl( 

1569 self, 

1570 context: "BuildContext", 

1571 manifest: "HighLevelManifest", 

1572 dest_dir: str, 

1573 **kwargs, 

1574 ) -> None: 

1575 env_mod = EnvironmentModification( 

1576 replacements=( 

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

1578 ("DESTDIR", dest_dir), 

1579 ) 

1580 ).combine(self._default_cmake_env(context)) 

1581 run_build_system_command( 

1582 "cmake", 

1583 "--install", 

1584 self.build_directory, 

1585 env_mod=env_mod, 

1586 ) 

1587 

1588 def clean_impl( 

1589 self, 

1590 context: "BuildContext", 

1591 manifest: "HighLevelManifest", 

1592 clean_helper: "CleanHelper", 

1593 **kwargs, 

1594 ) -> None: 

1595 if self.out_of_source_build: 

1596 return 

1597 if self.target_build_system == "make": 

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

1599 self._make_support.run_first_existing_target_if_any( 

1600 context, 

1601 ["distclean", "realclean", "clean"], 

1602 ) 

1603 else: 

1604 self._ninja_support.run_ninja_clean(context) 

1605 

1606 

1607class MesonBuildSystemRule(StepBasedBuildSystemRule): 

1608 

1609 __slots__ = ( 

1610 "configure_args", 

1611 "_ninja_support", 

1612 ) 

1613 

1614 def __init__( 

1615 self, 

1616 parsed_data: "ParsedMesonBuildRuleDefinition", 

1617 attribute_path: AttributePath, 

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

1619 ) -> None: 

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

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

1622 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1623 

1624 @classmethod 

1625 def characteristics(cls) -> BuildSystemCharacteristics: 

1626 return BuildSystemCharacteristics( 

1627 out_of_source_builds="required", 

1628 ) 

1629 

1630 @classmethod 

1631 def auto_detect_build_system( 

1632 cls, 

1633 source_root: VirtualPath, 

1634 *args, 

1635 **kwargs, 

1636 ) -> bool: 

1637 return "meson.build" in source_root 

1638 

1639 @staticmethod 

1640 def _default_meson_env() -> EnvironmentModification: 

1641 replacements = { 

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

1643 } 

1644 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1645 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1646 return EnvironmentModification( 

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

1648 ) 

1649 

1650 @classmethod 

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

1652 cmake_generators = { 

1653 "make": "Unix Makefiles", 

1654 "ninja": "Ninja", 

1655 } 

1656 return cmake_generators[target_build_system] 

1657 

1658 @staticmethod 

1659 def _cross_flags( 

1660 context: "BuildContext", 

1661 meson_flags: list[str], 

1662 ) -> None: 

1663 if not context.is_cross_compiling: 

1664 return 

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

1666 cross_files_dir = os.path.abspath( 

1667 generated_content_dir( 

1668 subdir_key="meson-cross-files", 

1669 ) 

1670 ) 

1671 host_arch = context.dpkg_architecture_variables.current_host_arch 

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

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

1674 subprocess.check_call( 

1675 [ 

1676 "/usr/share/meson/debcrossgen", 

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

1678 f"-o{cross_file}", 

1679 ], 

1680 stdout=subprocess.DEVNULL, 

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

1682 ) 

1683 

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

1685 meson_flags.append(cross_file) 

1686 

1687 def configure_impl( 

1688 self, 

1689 context: "BuildContext", 

1690 manifest: "HighLevelManifest", 

1691 **kwargs, 

1692 ) -> None: 

1693 meson_version = Version( 

1694 subprocess.check_output( 

1695 ["meson", "--version"], 

1696 encoding="utf-8", 

1697 ).strip() 

1698 ) 

1699 dpkg_architecture_variables = context.dpkg_architecture_variables 

1700 

1701 meson_flags = [ 

1702 "--wrap-mode=nodownload", 

1703 "--buildtype=plain", 

1704 "--prefix=/usr", 

1705 "--sysconfdir=/etc", 

1706 "--localstatedir=/var", 

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

1708 "--auto-features=enabled", 

1709 ] 

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

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

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

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

1714 # where the option exists. 

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

1716 

1717 self._cross_flags(context, meson_flags) 

1718 

1719 if self.configure_args: 

1720 substitution = self.substitution 

1721 attr_path = self.attribute_path["configure_args"] 

1722 meson_flags.extend( 

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

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

1725 ) 

1726 

1727 env_mod = self._default_meson_env() 

1728 

1729 self.ensure_build_dir_exists() 

1730 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1731 

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

1733 run_build_system_command( 

1734 "meson", 

1735 "setup", 

1736 source_dir_from_build_dir, 

1737 *meson_flags, 

1738 cwd=self.build_directory, 

1739 env_mod=env_mod, 

1740 ) 

1741 

1742 def build_impl( 

1743 self, 

1744 context: "BuildContext", 

1745 manifest: "HighLevelManifest", 

1746 **kwargs, 

1747 ) -> None: 

1748 self._ninja_support.run_ninja_build(context) 

1749 

1750 def test_impl( 

1751 self, 

1752 context: "BuildContext", 

1753 manifest: "HighLevelManifest", 

1754 *, 

1755 should_ignore_test_errors: bool = False, 

1756 **kwargs, 

1757 ) -> None: 

1758 env_mod = EnvironmentModification( 

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

1760 ).combine(self._default_meson_env()) 

1761 meson_args = [] 

1762 if not context.is_terse_build: 

1763 meson_args.append("--verbose") 

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

1765 run_build_system_command( 

1766 "meson", 

1767 "test", 

1768 *meson_args, 

1769 env_mod=env_mod, 

1770 cwd=self.build_directory, 

1771 ) 

1772 

1773 def install_impl( 

1774 self, 

1775 context: "BuildContext", 

1776 manifest: "HighLevelManifest", 

1777 dest_dir: str, 

1778 **kwargs, 

1779 ) -> None: 

1780 run_build_system_command( 

1781 "meson", 

1782 "install", 

1783 "--destdir", 

1784 dest_dir, 

1785 cwd=self.build_directory, 

1786 env_mod=self._default_meson_env(), 

1787 ) 

1788 

1789 def clean_impl( 

1790 self, 

1791 context: "BuildContext", 

1792 manifest: "HighLevelManifest", 

1793 clean_helper: "CleanHelper", 

1794 **kwargs, 

1795 ) -> None: 

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

1797 assert self.out_of_source_build 

1798 

1799 

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

1801 value = os.environ.get(envvar) 

1802 if value is None: 

1803 return 

1804 if include_cppflags: 

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

1806 if cppflags: 

1807 value = f"{value} {cppflags}" 

1808 

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

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

1811 

1812 

1813class ParsedGenericQmakeBuildRuleDefinition( 

1814 OptionalInstallDirectly, 

1815 OptionalInSourceBuild, 

1816 OptionalBuildDirectory, 

1817 OptionalTestRule, 

1818): 

1819 configure_args: NotRequired[list[str]] 

1820 

1821 

1822class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule): 

1823 

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

1825 

1826 def __init__( 

1827 self, 

1828 parsed_data: "ParsedGenericQmakeBuildRuleDefinition", 

1829 attribute_path: AttributePath, 

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

1831 ) -> None: 

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

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

1834 self._make_support = MakefileSupport.from_build_system(self) 

1835 

1836 @classmethod 

1837 def characteristics(cls) -> BuildSystemCharacteristics: 

1838 return BuildSystemCharacteristics( 

1839 out_of_source_builds="supported-and-default", 

1840 ) 

1841 

1842 @classmethod 

1843 def auto_detect_build_system( 

1844 cls, 

1845 source_root: VirtualPath, 

1846 *args, 

1847 **kwargs, 

1848 ) -> bool: 

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

1850 

1851 @classmethod 

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

1853 return { 

1854 "linux": "linux-g++", 

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

1856 "hurd": "hurd-g++", 

1857 } 

1858 

1859 def qmake_command(self) -> str: 

1860 raise NotImplementedError 

1861 

1862 def configure_impl( 

1863 self, 

1864 context: "BuildContext", 

1865 manifest: "HighLevelManifest", 

1866 **kwargs, 

1867 ) -> None: 

1868 

1869 configure_args = [ 

1870 "-makefile", 

1871 ] 

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

1873 

1874 if context.is_cross_compiling: 

1875 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1876 os2mkspec = self.os_mkspec_mapping() 

1877 try: 

1878 spec = os2mkspec[host_os] 

1879 except KeyError: 

1880 _error( 

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

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

1883 ) 

1884 configure_args.append("-spec") 

1885 configure_args.append(spec) 

1886 

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

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

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

1890 

1891 configure_args.append("QMAKE_STRIP=:") 

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

1893 

1894 if self.configure_args: 

1895 substitution = self.substitution 

1896 attr_path = self.attribute_path["configure_args"] 

1897 configure_args.extend( 

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

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

1900 ) 

1901 

1902 self.ensure_build_dir_exists() 

1903 if not self.out_of_source_build: 

1904 configure_args.append(self.relative_from_builddir_to_source()) 

1905 

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

1907 run_build_system_command( 

1908 qmake_cmd, 

1909 *configure_args, 

1910 cwd=self.build_directory, 

1911 ) 

1912 

1913 def build_impl( 

1914 self, 

1915 context: "BuildContext", 

1916 manifest: "HighLevelManifest", 

1917 **kwargs, 

1918 ) -> None: 

1919 self._make_support.run_make(context) 

1920 

1921 def test_impl( 

1922 self, 

1923 context: "BuildContext", 

1924 manifest: "HighLevelManifest", 

1925 *, 

1926 should_ignore_test_errors: bool = False, 

1927 **kwargs, 

1928 ) -> None: 

1929 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1931 

1932 if not context.is_terse_build: 

1933 testsuite_flags.append("--verbose") 

1934 self._make_support.run_first_existing_target_if_any( 

1935 context, 

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

1937 ["check", "test"], 

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

1939 "VERBOSE=1", 

1940 ) 

1941 

1942 def install_impl( 

1943 self, 

1944 context: "BuildContext", 

1945 manifest: "HighLevelManifest", 

1946 dest_dir: str, 

1947 **kwargs, 

1948 ) -> None: 

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

1950 self._make_support.run_first_existing_target_if_any( 

1951 context, 

1952 ["install"], 

1953 f"DESTDIR={dest_dir}", 

1954 "AM_UPDATE_INFO_DIR=no", 

1955 enable_parallelization=enable_parallelization, 

1956 ) 

1957 

1958 def clean_impl( 

1959 self, 

1960 context: "BuildContext", 

1961 manifest: "HighLevelManifest", 

1962 clean_helper: "CleanHelper", 

1963 **kwargs, 

1964 ) -> None: 

1965 if self.out_of_source_build: 

1966 return 

1967 self._make_support.run_first_existing_target_if_any( 

1968 context, 

1969 ["distclean", "realclean", "clean"], 

1970 ) 

1971 

1972 

1973class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule): 

1974 

1975 def qmake_command(self) -> str: 

1976 return "qmake" 

1977 

1978 

1979class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule): 

1980 

1981 def qmake_command(self) -> str: 

1982 return "qmake6" 

1983 

1984 

1985@debputy_build_system( 

1986 "make", 

1987 MakefileBuildSystemRule, 

1988 auto_detection_shadows_build_systems="debhelper", 

1989 online_reference_documentation=reference_documentation( 

1990 title="Make Build System", 

1991 description=textwrap.dedent( 

1992 """\ 

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

1994 

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

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

1997 

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

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

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

2001 some other directory. 

2002 """ 

2003 ), 

2004 attributes=[ 

2005 documented_attr( 

2006 "directory", 

2007 textwrap.dedent( 

2008 """\ 

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

2010 

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

2012 """ 

2013 ), 

2014 ), 

2015 documented_attr( 

2016 "build_target", 

2017 textwrap.dedent( 

2018 """\ 

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

2020 

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

2022 the default. 

2023 """ 

2024 ), 

2025 ), 

2026 documented_attr( 

2027 "test_target", 

2028 textwrap.dedent( 

2029 """\ 

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

2031 

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

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

2034 """ 

2035 ), 

2036 ), 

2037 documented_attr( 

2038 "install_target", 

2039 textwrap.dedent( 

2040 """\ 

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

2042 

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

2044 Otherwise, the step will be skipped. 

2045 """ 

2046 ), 

2047 ), 

2048 *docs_from( 

2049 DebputyParsedContentStandardConditional, 

2050 OptionalInstallDirectly, 

2051 OptionalTestRule, 

2052 BuildRuleParsedFormat, 

2053 ), 

2054 ], 

2055 ), 

2056) 

2057class ParsedMakeBuildRuleDefinition( 

2058 OptionalInstallDirectly, 

2059 OptionalTestRule, 

2060): 

2061 directory: NotRequired[FileSystemExactMatchRule] 

2062 build_target: NotRequired[str] 

2063 test_target: NotRequired[str] 

2064 install_target: NotRequired[str] 

2065 

2066 

2067@debputy_build_system( 

2068 "autoconf", 

2069 AutoconfBuildSystemRule, 

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

2071 online_reference_documentation=reference_documentation( 

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

2073 description=textwrap.dedent( 

2074 """\ 

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

2076 

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

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

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

2080 

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

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

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

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

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

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

2087 """ 

2088 ), 

2089 attributes=[ 

2090 documented_attr( 

2091 "configure_args", 

2092 textwrap.dedent( 

2093 """\ 

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

2095 """ 

2096 ), 

2097 ), 

2098 *docs_from( 

2099 DebputyParsedContentStandardConditional, 

2100 OptionalInstallDirectly, 

2101 OptionalInSourceBuild, 

2102 OptionalBuildDirectory, 

2103 OptionalTestRule, 

2104 BuildRuleParsedFormat, 

2105 ), 

2106 ], 

2107 ), 

2108) 

2109class ParsedAutoconfBuildRuleDefinition( 

2110 OptionalInstallDirectly, 

2111 OptionalInSourceBuild, 

2112 OptionalBuildDirectory, 

2113 OptionalTestRule, 

2114): 

2115 configure_args: NotRequired[list[str]] 

2116 

2117 

2118@debputy_build_system( 

2119 "cmake", 

2120 CMakeBuildSystemRule, 

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

2122 online_reference_documentation=reference_documentation( 

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

2124 description=textwrap.dedent( 

2125 """\ 

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

2127 

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

2129 """ 

2130 ), 

2131 attributes=[ 

2132 documented_attr( 

2133 "configure_args", 

2134 textwrap.dedent( 

2135 """\ 

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

2137 """ 

2138 ), 

2139 ), 

2140 documented_attr( 

2141 "target_build_system", 

2142 textwrap.dedent( 

2143 """\ 

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

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

2146 

2147 Supported options are: 

2148 * `make` - GNU Make 

2149 * `ninja` - Ninja 

2150 """ 

2151 ), 

2152 ), 

2153 *docs_from( 

2154 DebputyParsedContentStandardConditional, 

2155 OptionalInstallDirectly, 

2156 OptionalBuildDirectory, 

2157 OptionalTestRule, 

2158 BuildRuleParsedFormat, 

2159 ), 

2160 ], 

2161 ), 

2162) 

2163class ParsedCMakeBuildRuleDefinition( 

2164 OptionalInstallDirectly, 

2165 OptionalBuildDirectory, 

2166 OptionalTestRule, 

2167): 

2168 configure_args: NotRequired[list[str]] 

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

2170 

2171 

2172@debputy_build_system( 

2173 "meson", 

2174 MesonBuildSystemRule, 

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

2176 online_reference_documentation=reference_documentation( 

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

2178 description=textwrap.dedent( 

2179 """\ 

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

2181 

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

2183 """ 

2184 ), 

2185 attributes=[ 

2186 documented_attr( 

2187 "configure_args", 

2188 textwrap.dedent( 

2189 """\ 

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

2191 """ 

2192 ), 

2193 ), 

2194 *docs_from( 

2195 DebputyParsedContentStandardConditional, 

2196 OptionalInstallDirectly, 

2197 OptionalBuildDirectory, 

2198 OptionalTestRule, 

2199 BuildRuleParsedFormat, 

2200 ), 

2201 ], 

2202 ), 

2203) 

2204class ParsedMesonBuildRuleDefinition( 

2205 OptionalInstallDirectly, 

2206 OptionalBuildDirectory, 

2207 OptionalTestRule, 

2208): 

2209 configure_args: NotRequired[list[str]] 

2210 

2211 

2212@debputy_build_system( 

2213 "perl-build", 

2214 PerlBuildBuildSystemRule, 

2215 auto_detection_shadows_build_systems=[ 

2216 "debhelper", 

2217 "make", 

2218 "perl-makemaker", 

2219 ], 

2220 online_reference_documentation=reference_documentation( 

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

2222 description=textwrap.dedent( 

2223 """\ 

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

2225 

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

2227 upstream code. 

2228 """ 

2229 ), 

2230 attributes=[ 

2231 documented_attr( 

2232 "configure_args", 

2233 textwrap.dedent( 

2234 """\ 

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

2236 """ 

2237 ), 

2238 ), 

2239 *docs_from( 

2240 DebputyParsedContentStandardConditional, 

2241 OptionalInstallDirectly, 

2242 OptionalTestRule, 

2243 BuildRuleParsedFormat, 

2244 ), 

2245 ], 

2246 ), 

2247) 

2248class ParsedPerlBuildBuildRuleDefinition( 

2249 OptionalInstallDirectly, 

2250 OptionalTestRule, 

2251): 

2252 configure_args: NotRequired[list[str]] 

2253 

2254 

2255@debputy_build_system( 

2256 "debhelper", 

2257 DebhelperBuildSystemRule, 

2258 online_reference_documentation=reference_documentation( 

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

2260 description=textwrap.dedent( 

2261 """\ 

2262 Delegate to a debhelper provided build system 

2263 

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

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

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

2267 `dh-build-system` attribute. 

2268 """ 

2269 ), 

2270 attributes=[ 

2271 documented_attr( 

2272 "dh_build_system", 

2273 textwrap.dedent( 

2274 """\ 

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

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

2277 for that will be accepted. 

2278 

2279 Note that many debhelper build systems require extra build 

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

2281 of the relevant debhelper build system for details. 

2282 """ 

2283 ), 

2284 ), 

2285 documented_attr( 

2286 "configure_args", 

2287 textwrap.dedent( 

2288 """\ 

2289 Arguments to be passed to underlying configuration command 

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

2291 """ 

2292 ), 

2293 ), 

2294 *docs_from( 

2295 DebputyParsedContentStandardConditional, 

2296 OptionalInstallDirectly, 

2297 OptionalBuildDirectory, 

2298 OptionalTestRule, 

2299 BuildRuleParsedFormat, 

2300 ), 

2301 ], 

2302 ), 

2303) 

2304class ParsedDebhelperBuildRuleDefinition( 

2305 OptionalInstallDirectly, 

2306 OptionalBuildDirectory, 

2307 OptionalTestRule, 

2308): 

2309 configure_args: NotRequired[list[str]] 

2310 dh_build_system: NotRequired[str] 

2311 

2312 

2313@debputy_build_system( 

2314 "perl-makemaker", 

2315 PerlMakeMakerBuildSystemRule, 

2316 auto_detection_shadows_build_systems=[ 

2317 "debhelper", 

2318 "make", 

2319 ], 

2320 online_reference_documentation=reference_documentation( 

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

2322 description=textwrap.dedent( 

2323 """\ 

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

2325 

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

2327 upstream code. 

2328 """ 

2329 ), 

2330 attributes=[ 

2331 documented_attr( 

2332 "configure_args", 

2333 textwrap.dedent( 

2334 """\ 

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

2336 """ 

2337 ), 

2338 ), 

2339 *docs_from( 

2340 DebputyParsedContentStandardConditional, 

2341 OptionalInstallDirectly, 

2342 OptionalTestRule, 

2343 BuildRuleParsedFormat, 

2344 ), 

2345 ], 

2346 ), 

2347) 

2348class ParsedPerlMakeMakerBuildRuleDefinition( 

2349 OptionalInstallDirectly, 

2350 OptionalTestRule, 

2351): 

2352 configure_args: NotRequired[list[str]] 

2353 

2354 

2355@debputy_build_system( 

2356 "qmake", 

2357 QmakeBuildSystemRule, 

2358 auto_detection_shadows_build_systems=[ 

2359 "debhelper", 

2360 "make", 

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

2362 ], 

2363 online_reference_documentation=reference_documentation( 

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

2365 description=textwrap.dedent( 

2366 """\ 

2367 Build using the "qmake" by QT. 

2368 """ 

2369 ), 

2370 attributes=[ 

2371 documented_attr( 

2372 "configure_args", 

2373 textwrap.dedent( 

2374 """\ 

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

2376 """ 

2377 ), 

2378 ), 

2379 *docs_from( 

2380 DebputyParsedContentStandardConditional, 

2381 OptionalInstallDirectly, 

2382 OptionalInSourceBuild, 

2383 OptionalBuildDirectory, 

2384 OptionalTestRule, 

2385 BuildRuleParsedFormat, 

2386 ), 

2387 ], 

2388 ), 

2389) 

2390class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2391 pass 

2392 

2393 

2394@debputy_build_system( 

2395 "qmake6", 

2396 Qmake6BuildSystemRule, 

2397 auto_detection_shadows_build_systems=[ 

2398 "debhelper", 

2399 "make", 

2400 ], 

2401 online_reference_documentation=reference_documentation( 

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

2403 description=textwrap.dedent( 

2404 """\ 

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

2406 but is specifically for QT6. 

2407 """ 

2408 ), 

2409 attributes=[ 

2410 documented_attr( 

2411 "configure_args", 

2412 textwrap.dedent( 

2413 """\ 

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

2415 """ 

2416 ), 

2417 ), 

2418 *docs_from( 

2419 DebputyParsedContentStandardConditional, 

2420 OptionalInstallDirectly, 

2421 OptionalInSourceBuild, 

2422 OptionalBuildDirectory, 

2423 OptionalTestRule, 

2424 BuildRuleParsedFormat, 

2425 ), 

2426 ], 

2427 ), 

2428) 

2429class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2430 pass 

2431 

2432 

2433def _parse_default_environment( 

2434 _name: str, 

2435 parsed_data: EnvironmentSourceFormat, 

2436 attribute_path: AttributePath, 

2437 parser_context: ParserContextData, 

2438) -> ManifestProvidedBuildEnvironment: 

2439 return ManifestProvidedBuildEnvironment.from_environment_definition( 

2440 parsed_data, 

2441 attribute_path, 

2442 parser_context, 

2443 is_default=True, 

2444 ) 

2445 

2446 

2447def _parse_build_environments( 

2448 _name: str, 

2449 parsed_data: list[NamedEnvironmentSourceFormat], 

2450 attribute_path: AttributePath, 

2451 parser_context: ParserContextData, 

2452) -> list[ManifestProvidedBuildEnvironment]: 

2453 return [ 

2454 ManifestProvidedBuildEnvironment.from_environment_definition( 

2455 value, 

2456 attribute_path[idx], 

2457 parser_context, 

2458 is_default=False, 

2459 ) 

2460 for idx, value in enumerate(parsed_data) 

2461 ] 

2462 

2463 

2464def _handle_build_rules( 

2465 _name: str, 

2466 parsed_data: list[BuildRule], 

2467 _attribute_path: AttributePath, 

2468 _parser_context: ParserContextData, 

2469) -> list[BuildRule]: 

2470 return parsed_data