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

684 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-10-12 15:06 +0000

1import dataclasses 

2import json 

3import os 

4import subprocess 

5import textwrap 

6from typing import ( 

7 NotRequired, 

8 TypedDict, 

9 Self, 

10 cast, 

11 Dict, 

12 List, 

13 Tuple, 

14 Union, 

15 Optional, 

16 TYPE_CHECKING, 

17 Literal, 

18 Required, 

19) 

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

21 

22from debian.debian_support import Version 

23 

24from debputy._manifest_constants import MK_BUILDS 

25from debputy.manifest_conditions import ManifestCondition 

26from debputy.manifest_parser.base_types import ( 

27 BuildEnvironmentDefinition, 

28 DebputyParsedContentStandardConditional, 

29 FileSystemExactMatchRule, 

30) 

31from debputy.manifest_parser.exceptions import ( 

32 ManifestParseException, 

33 ManifestInvalidUserDataException, 

34) 

35from debputy.manifest_parser.parser_data import ParserContextData 

36from debputy.manifest_parser.tagging_types import DebputyParsedContent 

37from debputy.manifest_parser.util import AttributePath 

38from debputy.plugin.api import reference_documentation 

39from debputy.plugin.api.impl import ( 

40 DebputyPluginInitializerProvider, 

41) 

42from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT 

43from debputy.plugin.api.spec import ( 

44 documented_attr, 

45 INTEGRATION_MODE_FULL, 

46 only_integrations, 

47 VirtualPath, 

48) 

49from debputy.plugin.api.std_docs import docs_from 

50from debputy.plugins.debputy.to_be_api_types import ( 

51 BuildRule, 

52 StepBasedBuildSystemRule, 

53 OptionalInstallDirectly, 

54 BuildSystemCharacteristics, 

55 OptionalBuildDirectory, 

56 OptionalInSourceBuild, 

57 MakefileSupport, 

58 BuildRuleParsedFormat, 

59 debputy_build_system, 

60 CleanHelper, 

61 NinjaBuildSupport, 

62 TestRule, 

63 OptionalTestRule, 

64) 

65from debputy.types import EnvironmentModification 

66from debputy.util import ( 

67 _warn, 

68 run_build_system_command, 

69 _error, 

70 PerlConfigVars, 

71 resolve_perl_config, 

72 generated_content_dir, 

73 manifest_format_doc, 

74 _is_debug_log_enabled, 

75) 

76 

77if TYPE_CHECKING: 

78 from debputy.build_support.build_context import BuildContext 

79 from debputy.highlevel_manifest import HighLevelManifest 

80 

81 

82PERL_CMD = "perl" 

83 

84 

85class Conditional(DebputyParsedContent): 

86 when: Required[ManifestCondition] 

87 

88 

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

90 register_build_keywords(api) 

91 register_build_rules(api) 

92 

93 

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

95 

96 api.pluggable_manifest_rule( 

97 OPARSER_MANIFEST_ROOT, 

98 "build-environments", 

99 list[NamedEnvironmentSourceFormat], 

100 _parse_build_environments, 

101 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

102 inline_reference_documentation=reference_documentation( 

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

104 description=textwrap.dedent( 

105 """\ 

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

107 a non-default environment. 

108 

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

110 build commands. An example: 

111 

112 build-environments: 

113 - name: custom-env 

114 set: 

115 ENV_VAR: foo 

116 ANOTHER_ENV_VAR: bar 

117 builds: 

118 - autoconf: 

119 environment: custom-env 

120 

121 The environment definition has multiple attributes for setting environment variables 

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

123 result of the following order of operations. 

124 

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

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

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

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

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

130 

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

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

133 

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

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

136 """ 

137 ), 

138 attributes=[ 

139 documented_attr( 

140 "name", 

141 textwrap.dedent( 

142 """\ 

143 The name of the environment 

144 

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

146 """ 

147 ), 

148 ), 

149 documented_attr( 

150 "set", 

151 textwrap.dedent( 

152 """\ 

153 A mapping of environment variables to be set. 

154 

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

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

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

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

159 """ 

160 ), 

161 ), 

162 documented_attr( 

163 "override", 

164 textwrap.dedent( 

165 """\ 

166 A mapping of environment variables to set. 

167 

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

169 `dpkg-buildflags`. 

170 """ 

171 ), 

172 ), 

173 documented_attr( 

174 "unset", 

175 textwrap.dedent( 

176 """\ 

177 A list of environment variables to unset. 

178 

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

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

181 """ 

182 ), 

183 ), 

184 ], 

185 reference_documentation_url=manifest_format_doc( 

186 "build-environment-build-environment" 

187 ), 

188 ), 

189 ) 

190 api.pluggable_manifest_rule( 

191 OPARSER_MANIFEST_ROOT, 

192 "default-build-environment", 

193 EnvironmentSourceFormat, 

194 _parse_default_environment, 

195 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

196 inline_reference_documentation=reference_documentation( 

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

198 description=textwrap.dedent( 

199 """\ 

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

201 environment. 

202 

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

204 build commands. An example: 

205  

206 default-build-environment: 

207 set: 

208 ENV_VAR: foo 

209 ANOTHER_ENV_VAR: bar 

210 

211 The environment definition has multiple attributes for setting environment variables 

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

213 result of the following order of operations. 

214  

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

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

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

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

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

220 

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

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

223 

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

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

226 """ 

227 ), 

228 attributes=[ 

229 documented_attr( 

230 "set", 

231 textwrap.dedent( 

232 """\ 

233 A mapping of environment variables to be set. 

234 

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

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

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

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

239 """ 

240 ), 

241 ), 

242 documented_attr( 

243 "override", 

244 textwrap.dedent( 

245 """\ 

246 A mapping of environment variables to set. 

247 

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

249 `dpkg-buildflags`. 

250 """ 

251 ), 

252 ), 

253 documented_attr( 

254 "unset", 

255 textwrap.dedent( 

256 """\ 

257 A list of environment variables to unset. 

258 

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

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

261 """ 

262 ), 

263 ), 

264 ], 

265 reference_documentation_url=manifest_format_doc( 

266 "build-environment-build-environment" 

267 ), 

268 ), 

269 ) 

270 api.pluggable_manifest_rule( 

271 OPARSER_MANIFEST_ROOT, 

272 MK_BUILDS, 

273 list[BuildRule], 

274 _handle_build_rules, 

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 if isinstance(kt, tuple): 

461 lhs_path_key, var = kt 

462 else: 

463 lhs_path_key = var = kt 

464 if var not in rhs: 

465 continue 

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

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

468 r_path = lhs_path if redundant_key == rhs_key else rhs_path 

469 raise ManifestParseException( 

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

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

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

473 f" the two definitions." 

474 ) 

475 

476 

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

478class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition): 

479 

480 name: str 

481 is_default: bool 

482 attribute_path: AttributePath 

483 parser_context: ParserContextData 

484 

485 set_vars: Mapping[str, str] 

486 override_vars: Mapping[str, str] 

487 unset_vars: Sequence[str] 

488 

489 @classmethod 

490 def from_environment_definition( 

491 cls, 

492 env: EnvironmentSourceFormat, 

493 attribute_path: AttributePath, 

494 parser_context: ParserContextData, 

495 is_default: bool = False, 

496 ) -> Self: 

497 reference_name: str | None 

498 if is_default: 

499 name = "default-env" 

500 reference_name = None 

501 else: 

502 named_env = cast("NamedEnvironmentSourceFormat", env) 

503 name = named_env["name"] 

504 reference_name = name 

505 

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

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

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

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

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

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

512 

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

514 raise ManifestParseException( 

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

516 " some content or delete the definition." 

517 ) 

518 

519 _no_overlap( 

520 enumerate(unset_vars), 

521 set_vars, 

522 "unset", 

523 "set", 

524 "set", 

525 attribute_path, 

526 ) 

527 _no_overlap( 

528 enumerate(unset_vars), 

529 override_vars, 

530 "unset", 

531 "override", 

532 "override", 

533 attribute_path, 

534 ) 

535 _no_overlap( 

536 override_vars, 

537 set_vars, 

538 "override", 

539 "set", 

540 "set", 

541 attribute_path, 

542 ) 

543 

544 r = cls( 

545 name, 

546 is_default, 

547 attribute_path, 

548 parser_context, 

549 set_vars, 

550 override_vars, 

551 unset_vars, 

552 ) 

553 parser_context._register_build_environment( 

554 reference_name, 

555 r, 

556 attribute_path, 

557 is_default, 

558 ) 

559 

560 return r 

561 

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

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

564 env.update(set_vars) 

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

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

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

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

569 for var in overlapping_env: 

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

571 _warn( 

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

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

574 f" `set`." 

575 ) 

576 env.update(dpkg_env) 

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

578 env.update(override_vars) 

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

580 for var in unset_vars: 

581 try: 

582 del env[var] 

583 except KeyError: 

584 pass 

585 

586 

587_MAKE_DEFAULT_TOOLS = [ 

588 ("CC", "gcc"), 

589 ("CXX", "g++"), 

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

591] 

592 

593 

594class MakefileBuildSystemRule(StepBasedBuildSystemRule): 

595 

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

597 

598 def __init__( 

599 self, 

600 attributes: "ParsedMakeBuildRuleDefinition", 

601 attribute_path: AttributePath, 

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

603 ) -> None: 

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

605 directory = attributes.get("directory") 

606 if directory is not None: 

607 self._directory = directory.match_rule.path 

608 else: 

609 self._directory = None 

610 self._make_support = MakefileSupport.from_build_system(self) 

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

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

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

614 

615 @classmethod 

616 def auto_detect_build_system( 

617 cls, 

618 source_root: VirtualPath, 

619 *args, 

620 **kwargs, 

621 ) -> bool: 

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

623 

624 @classmethod 

625 def characteristics(cls) -> BuildSystemCharacteristics: 

626 return BuildSystemCharacteristics( 

627 out_of_source_builds="not-supported", 

628 ) 

629 

630 def configure_impl( 

631 self, 

632 context: "BuildContext", 

633 manifest: "HighLevelManifest", 

634 **kwargs, 

635 ) -> None: 

636 # No configure step 

637 pass 

638 

639 def build_impl( 

640 self, 

641 context: "BuildContext", 

642 manifest: "HighLevelManifest", 

643 **kwargs, 

644 ) -> None: 

645 extra_vars = [] 

646 build_target = self._build_target 

647 if build_target is not None: 

648 extra_vars.append(build_target) 

649 if context.is_cross_compiling: 

650 for envvar, tool in _MAKE_DEFAULT_TOOLS: 

651 cross_tool = os.environ.get(envvar) 

652 if cross_tool is None: 

653 cross_tool = context.cross_tool(tool) 

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

655 self._make_support.run_make( 

656 context, 

657 *extra_vars, 

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

659 directory=self._directory, 

660 ) 

661 

662 def test_impl( 

663 self, 

664 context: "BuildContext", 

665 manifest: "HighLevelManifest", 

666 *, 

667 should_ignore_test_errors: bool = False, 

668 **kwargs, 

669 ) -> None: 

670 self._run_make_maybe_explicit_target( 

671 context, 

672 self._test_target, 

673 ["test", "check"], 

674 ) 

675 

676 def install_impl( 

677 self, 

678 context: "BuildContext", 

679 manifest: "HighLevelManifest", 

680 dest_dir: str, 

681 **kwargs, 

682 ) -> None: 

683 self._run_make_maybe_explicit_target( 

684 context, 

685 self._install_target, 

686 ["install"], 

687 f"DESTDIR={dest_dir}", 

688 "AM_UPDATE_INFO_DIR=no", 

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

690 ) 

691 

692 def _run_make_maybe_explicit_target( 

693 self, 

694 context: "BuildContext", 

695 provided_target: str | None, 

696 fallback_targets: Sequence[str], 

697 *make_args: str, 

698 ) -> None: 

699 make_support = self._make_support 

700 if provided_target is not None: 

701 make_support.run_make( 

702 context, 

703 provided_target, 

704 *make_args, 

705 directory=self._directory, 

706 ) 

707 else: 

708 make_support.run_first_existing_target_if_any( 

709 context, 

710 fallback_targets, 

711 *make_args, 

712 directory=self._directory, 

713 ) 

714 

715 def clean_impl( 

716 self, 

717 context: "BuildContext", 

718 manifest: "HighLevelManifest", 

719 clean_helper: "CleanHelper", 

720 **kwargs, 

721 ) -> None: 

722 self._make_support.run_first_existing_target_if_any( 

723 context, 

724 ["distclean", "realclean", "clean"], 

725 ) 

726 

727 

728class PerlBuildBuildSystemRule(StepBasedBuildSystemRule): 

729 

730 __slots__ = "configure_args" 

731 

732 def __init__( 

733 self, 

734 attributes: "ParsedPerlBuildBuildRuleDefinition", 

735 attribute_path: AttributePath, 

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

737 ) -> None: 

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

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

740 

741 @classmethod 

742 def auto_detect_build_system( 

743 cls, 

744 source_root: VirtualPath, 

745 *args, 

746 **kwargs, 

747 ) -> bool: 

748 return "Build.PL" in source_root 

749 

750 @classmethod 

751 def characteristics(cls) -> BuildSystemCharacteristics: 

752 return BuildSystemCharacteristics( 

753 out_of_source_builds="not-supported", 

754 ) 

755 

756 @staticmethod 

757 def _perl_cross_build_env( 

758 context: "BuildContext", 

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

760 perl_config_data = resolve_perl_config( 

761 context.dpkg_architecture_variables, 

762 None, 

763 ) 

764 if context.is_cross_compiling: 

765 perl5lib_dir = perl_config_data.cross_inc_dir 

766 if perl5lib_dir is not None: 

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

768 if env_perl5lib is not None: 

769 perl5lib_dir = ( 

770 perl5lib_dir + perl_config_data.path_sep + env_perl5lib 

771 ) 

772 env_mod = EnvironmentModification( 

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

774 ) 

775 return perl_config_data, env_mod 

776 return perl_config_data, None 

777 

778 def configure_impl( 

779 self, 

780 context: "BuildContext", 

781 manifest: "HighLevelManifest", 

782 **kwargs, 

783 ) -> None: 

784 perl_config_data, cross_env_mod = self._perl_cross_build_env(context) 

785 configure_env = EnvironmentModification( 

786 replacements=( 

787 ("PERL_MM_USE_DEFAULT", "1"), 

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

789 ) 

790 ) 

791 if cross_env_mod is not None: 

792 configure_env = configure_env.combine(cross_env_mod) 

793 

794 configure_cmd = [ 

795 PERL_CMD, 

796 "Build.PL", 

797 "--installdirs", 

798 "vendor", 

799 ] 

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

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

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

803 

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

805 configure_cmd.append("--config") 

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

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

808 

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

810 configure_cmd.append("--config") 

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

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

813 if self.configure_args: 

814 substitution = self.substitution 

815 attr_path = self.attribute_path["configure_args"] 

816 configure_cmd.extend( 

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

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

819 ) 

820 run_build_system_command(*configure_cmd, env_mod=configure_env) 

821 

822 def build_impl( 

823 self, 

824 context: "BuildContext", 

825 manifest: "HighLevelManifest", 

826 **kwargs, 

827 ) -> None: 

828 _, cross_env_mod = self._perl_cross_build_env(context) 

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

830 

831 def test_impl( 

832 self, 

833 context: "BuildContext", 

834 manifest: "HighLevelManifest", 

835 *, 

836 should_ignore_test_errors: bool = False, 

837 **kwargs, 

838 ) -> None: 

839 _, cross_env_mod = self._perl_cross_build_env(context) 

840 run_build_system_command( 

841 PERL_CMD, 

842 "Build", 

843 "test", 

844 "--verbose", 

845 "1", 

846 env_mod=cross_env_mod, 

847 ) 

848 

849 def install_impl( 

850 self, 

851 context: "BuildContext", 

852 manifest: "HighLevelManifest", 

853 dest_dir: str, 

854 **kwargs, 

855 ) -> None: 

856 _, cross_env_mod = self._perl_cross_build_env(context) 

857 run_build_system_command( 

858 PERL_CMD, 

859 "Build", 

860 "install", 

861 "--destdir", 

862 dest_dir, 

863 "--create_packlist", 

864 "0", 

865 env_mod=cross_env_mod, 

866 ) 

867 

868 def clean_impl( 

869 self, 

870 context: "BuildContext", 

871 manifest: "HighLevelManifest", 

872 clean_helper: "CleanHelper", 

873 **kwargs, 

874 ) -> None: 

875 _, cross_env_mod = self._perl_cross_build_env(context) 

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

877 run_build_system_command( 

878 PERL_CMD, 

879 "Build", 

880 "realclean", 

881 "--allow_mb_mismatch", 

882 "1", 

883 env_mod=cross_env_mod, 

884 ) 

885 

886 

887class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule): 

888 

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

890 

891 def __init__( 

892 self, 

893 attributes: "ParsedPerlBuildBuildRuleDefinition", 

894 attribute_path: AttributePath, 

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

896 ) -> None: 

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

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

899 self._make_support = MakefileSupport.from_build_system(self) 

900 

901 @classmethod 

902 def auto_detect_build_system( 

903 cls, 

904 source_root: VirtualPath, 

905 *args, 

906 **kwargs, 

907 ) -> bool: 

908 return "Makefile.PL" in source_root 

909 

910 @classmethod 

911 def characteristics(cls) -> BuildSystemCharacteristics: 

912 return BuildSystemCharacteristics( 

913 out_of_source_builds="not-supported", 

914 ) 

915 

916 def configure_impl( 

917 self, 

918 context: "BuildContext", 

919 manifest: "HighLevelManifest", 

920 **kwargs, 

921 ) -> None: 

922 configure_env = EnvironmentModification( 

923 replacements=( 

924 ("PERL_MM_USE_DEFAULT", "1"), 

925 ("PERL_AUTOINSTALL", "--skipdeps"), 

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

927 ) 

928 ) 

929 perl_args = [] 

930 mm_args = ["INSTALLDIRS=vendor"] 

931 if "CFLAGS" in os.environ: 

932 mm_args.append( 

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

934 ) 

935 

936 perl_config_data = resolve_perl_config( 

937 context.dpkg_architecture_variables, 

938 None, 

939 ) 

940 

941 if "LDFLAGS" in os.environ: 

942 mm_args.append( 

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

944 ) 

945 

946 if context.is_cross_compiling: 

947 perl5lib_dir = perl_config_data.cross_inc_dir 

948 if perl5lib_dir is not None: 

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

950 

951 if self.configure_args: 

952 substitution = self.substitution 

953 attr_path = self.attribute_path["configure_args"] 

954 mm_args.extend( 

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

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

957 ) 

958 run_build_system_command( 

959 PERL_CMD, 

960 *perl_args, 

961 "Makefile.PL", 

962 *mm_args, 

963 env_mod=configure_env, 

964 ) 

965 

966 def build_impl( 

967 self, 

968 context: "BuildContext", 

969 manifest: "HighLevelManifest", 

970 **kwargs, 

971 ) -> None: 

972 self._make_support.run_make(context) 

973 

974 def test_impl( 

975 self, 

976 context: "BuildContext", 

977 manifest: "HighLevelManifest", 

978 *, 

979 should_ignore_test_errors: bool = False, 

980 **kwargs, 

981 ) -> None: 

982 self._make_support.run_first_existing_target_if_any( 

983 context, 

984 ["check", "test"], 

985 "TEST_VERBOSE=1", 

986 ) 

987 

988 def install_impl( 

989 self, 

990 context: "BuildContext", 

991 manifest: "HighLevelManifest", 

992 dest_dir: str, 

993 **kwargs, 

994 ) -> None: 

995 is_mm_makefile = False 

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

997 for line in fd: 

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

999 is_mm_makefile = True 

1000 break 

1001 

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

1003 

1004 # Special case for Makefile.PL that uses 

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

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

1007 if is_mm_makefile: 

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

1009 

1010 self._make_support.run_first_existing_target_if_any( 

1011 context, 

1012 ["install"], 

1013 *install_args, 

1014 ) 

1015 

1016 def clean_impl( 

1017 self, 

1018 context: "BuildContext", 

1019 manifest: "HighLevelManifest", 

1020 clean_helper: "CleanHelper", 

1021 **kwargs, 

1022 ) -> None: 

1023 self._make_support.run_first_existing_target_if_any( 

1024 context, 

1025 ["distclean", "realclean", "clean"], 

1026 ) 

1027 

1028 

1029class DebhelperBuildSystemRule(StepBasedBuildSystemRule): 

1030 

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

1032 

1033 def __init__( 

1034 self, 

1035 parsed_data: "ParsedDebhelperBuildRuleDefinition", 

1036 attribute_path: AttributePath, 

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

1038 ) -> None: 

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

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

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

1042 

1043 @classmethod 

1044 def auto_detect_build_system( 

1045 cls, 

1046 source_root: VirtualPath, 

1047 *args, 

1048 **kwargs, 

1049 ) -> bool: 

1050 try: 

1051 v = subprocess.check_output( 

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

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

1054 stderr=subprocess.DEVNULL, 

1055 cwd=source_root.fs_path, 

1056 ) 

1057 except subprocess.CalledProcessError: 

1058 return False 

1059 else: 

1060 d = json.loads(v) 

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

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

1063 

1064 @classmethod 

1065 def characteristics(cls) -> BuildSystemCharacteristics: 

1066 return BuildSystemCharacteristics( 

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

1068 ) 

1069 

1070 def before_first_impl_step( 

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

1072 ) -> None: 

1073 dh_build_system = self.dh_build_system 

1074 if dh_build_system is None: 

1075 return 

1076 try: 

1077 subprocess.check_call( 

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

1079 ) 

1080 except FileNotFoundError: 

1081 _error( 

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

1083 ) 

1084 except subprocess.SubprocessError: 

1085 raise ManifestInvalidUserDataException( 

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

1087 f" be available according to" 

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

1089 ) from None 

1090 

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

1092 default_options = [] 

1093 if self.dh_build_system is not None: 

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

1095 if self.build_directory is not None: 

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

1097 

1098 return default_options 

1099 

1100 def configure_impl( 

1101 self, 

1102 context: "BuildContext", 

1103 manifest: "HighLevelManifest", 

1104 **kwargs, 

1105 ) -> None: 

1106 if ( 

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

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

1109 run_build_system_command("dh_update_autotools_config") 

1110 run_build_system_command("dh_autoreconf") 

1111 

1112 default_options = self._default_options() 

1113 configure_args = default_options.copy() 

1114 if self.configure_args: 

1115 configure_args.append("--") 

1116 substitution = self.substitution 

1117 attr_path = self.attribute_path["configure_args"] 

1118 configure_args.extend( 

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

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

1121 ) 

1122 run_build_system_command("dh_auto_configure", *configure_args) 

1123 

1124 def build_impl( 

1125 self, 

1126 context: "BuildContext", 

1127 manifest: "HighLevelManifest", 

1128 **kwargs, 

1129 ) -> None: 

1130 default_options = self._default_options() 

1131 run_build_system_command("dh_auto_build", *default_options) 

1132 

1133 def test_impl( 

1134 self, 

1135 context: "BuildContext", 

1136 manifest: "HighLevelManifest", 

1137 *, 

1138 should_ignore_test_errors: bool = False, 

1139 **kwargs, 

1140 ) -> None: 

1141 default_options = self._default_options() 

1142 run_build_system_command("dh_auto_test", *default_options) 

1143 

1144 def install_impl( 

1145 self, 

1146 context: "BuildContext", 

1147 manifest: "HighLevelManifest", 

1148 dest_dir: str, 

1149 **kwargs, 

1150 ) -> None: 

1151 default_options = self._default_options() 

1152 run_build_system_command( 

1153 "dh_auto_install", 

1154 *default_options, 

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

1156 ) 

1157 

1158 def clean_impl( 

1159 self, 

1160 context: "BuildContext", 

1161 manifest: "HighLevelManifest", 

1162 clean_helper: "CleanHelper", 

1163 **kwargs, 

1164 ) -> None: 

1165 default_options = self._default_options() 

1166 run_build_system_command("dh_auto_clean", *default_options) 

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

1168 

1169 

1170class AutoconfBuildSystemRule(StepBasedBuildSystemRule): 

1171 

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

1173 

1174 def __init__( 

1175 self, 

1176 parsed_data: "ParsedAutoconfBuildRuleDefinition", 

1177 attribute_path: AttributePath, 

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

1179 ) -> None: 

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

1181 configure_args = [a for a in parsed_data.get("configure_args", [])] 

1182 self.configure_args = configure_args 

1183 self._make_support = MakefileSupport.from_build_system(self) 

1184 

1185 @classmethod 

1186 def characteristics(cls) -> BuildSystemCharacteristics: 

1187 return BuildSystemCharacteristics( 

1188 out_of_source_builds="supported-and-default", 

1189 ) 

1190 

1191 @classmethod 

1192 def auto_detect_build_system( 

1193 cls, 

1194 source_root: VirtualPath, 

1195 *args, 

1196 **kwargs, 

1197 ) -> bool: 

1198 if "configure.ac" in source_root: 

1199 return True 

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

1201 if configure_in is not None and configure_in.is_file: 

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

1203 for no, line in enumerate(fd): 

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

1205 break 

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

1207 return True 

1208 configure = source_root.get("configure") 

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

1210 return False 

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

1212 for no, line in enumerate(fd): 

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

1214 break 

1215 if b"GNU Autoconf" in line: 

1216 return True 

1217 return False 

1218 

1219 def configure_impl( 

1220 self, 

1221 context: "BuildContext", 

1222 manifest: "HighLevelManifest", 

1223 **kwargs, 

1224 ) -> None: 

1225 if ( 

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

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

1228 run_build_system_command("dh_update_autotools_config") 

1229 run_build_system_command("dh_autoreconf") 

1230 

1231 dpkg_architecture_variables = context.dpkg_architecture_variables 

1232 multi_arch = dpkg_architecture_variables.current_host_multiarch 

1233 silent_rules = ( 

1234 "--enable-silent-rules" 

1235 if context.is_terse_build 

1236 else "--disable-silent-rules" 

1237 ) 

1238 

1239 configure_args = [ 

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

1241 "--prefix=/usr", 

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

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

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

1245 "--sysconfdir=/etc", 

1246 "--localstatedir=/var", 

1247 "--disable-option-checking", 

1248 silent_rules, 

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

1250 "--runstatedir=/run", 

1251 "--disable-maintainer-mode", 

1252 "--disable-dependency-tracking", 

1253 ] 

1254 if dpkg_architecture_variables.is_cross_compiling: 

1255 configure_args.append( 

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

1257 ) 

1258 if self.configure_args: 

1259 substitution = self.substitution 

1260 attr_path = self.attribute_path["configure_args"] 

1261 configure_args.extend( 

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

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

1264 ) 

1265 self.ensure_build_dir_exists() 

1266 configure_script = self.relative_from_builddir_to_source("configure") 

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

1268 run_build_system_command( 

1269 configure_script, 

1270 *configure_args, 

1271 cwd=self.build_directory, 

1272 ) 

1273 

1274 def build_impl( 

1275 self, 

1276 context: "BuildContext", 

1277 manifest: "HighLevelManifest", 

1278 **kwargs, 

1279 ) -> None: 

1280 self._make_support.run_make(context) 

1281 

1282 def test_impl( 

1283 self, 

1284 context: "BuildContext", 

1285 manifest: "HighLevelManifest", 

1286 *, 

1287 should_ignore_test_errors: bool = False, 

1288 **kwargs, 

1289 ) -> None: 

1290 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1292 

1293 if not context.is_terse_build: 

1294 testsuite_flags.append("--verbose") 

1295 self._make_support.run_first_existing_target_if_any( 

1296 context, 

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

1298 ["check", "test"], 

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

1300 "VERBOSE=1", 

1301 ) 

1302 

1303 def install_impl( 

1304 self, 

1305 context: "BuildContext", 

1306 manifest: "HighLevelManifest", 

1307 dest_dir: str, 

1308 **kwargs, 

1309 ) -> None: 

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

1311 self._make_support.run_first_existing_target_if_any( 

1312 context, 

1313 ["install"], 

1314 f"DESTDIR={dest_dir}", 

1315 "AM_UPDATE_INFO_DIR=no", 

1316 enable_parallelization=enable_parallelization, 

1317 ) 

1318 

1319 def clean_impl( 

1320 self, 

1321 context: "BuildContext", 

1322 manifest: "HighLevelManifest", 

1323 clean_helper: "CleanHelper", 

1324 **kwargs, 

1325 ) -> None: 

1326 if self.out_of_source_build: 

1327 return 

1328 self._make_support.run_first_existing_target_if_any( 

1329 context, 

1330 ["distclean", "realclean", "clean"], 

1331 ) 

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

1333 

1334 

1335class CMakeBuildSystemRule(StepBasedBuildSystemRule): 

1336 

1337 __slots__ = ( 

1338 "configure_args", 

1339 "target_build_system", 

1340 "_make_support", 

1341 "_ninja_support", 

1342 ) 

1343 

1344 def __init__( 

1345 self, 

1346 parsed_data: "ParsedCMakeBuildRuleDefinition", 

1347 attribute_path: AttributePath, 

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

1349 ) -> None: 

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

1351 configure_args = [a for a in parsed_data.get("configure_args", [])] 

1352 self.configure_args = configure_args 

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

1354 "target_build_system", "make" 

1355 ) 

1356 self._make_support = MakefileSupport.from_build_system(self) 

1357 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1358 

1359 @classmethod 

1360 def characteristics(cls) -> BuildSystemCharacteristics: 

1361 return BuildSystemCharacteristics( 

1362 out_of_source_builds="required", 

1363 ) 

1364 

1365 @classmethod 

1366 def auto_detect_build_system( 

1367 cls, 

1368 source_root: VirtualPath, 

1369 *args, 

1370 **kwargs, 

1371 ) -> bool: 

1372 return "CMakeLists.txt" in source_root 

1373 

1374 @staticmethod 

1375 def _default_cmake_env( 

1376 build_context: "BuildContext", 

1377 ) -> EnvironmentModification: 

1378 replacements = {} 

1379 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1380 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1381 if "PKG_CONFIG" not in os.environ: 

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

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

1384 return EnvironmentModification( 

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

1386 ) 

1387 

1388 @classmethod 

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

1390 cmake_generators = { 

1391 "make": "Unix Makefiles", 

1392 "ninja": "Ninja", 

1393 } 

1394 return cmake_generators[target_build_system] 

1395 

1396 @staticmethod 

1397 def _compiler_and_cross_flags( 

1398 context: "BuildContext", 

1399 cmake_flags: list[str], 

1400 ) -> None: 

1401 

1402 if "CC" in os.environ: 

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

1404 elif context.is_cross_compiling: 

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

1406 

1407 if "CXX" in os.environ: 

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

1409 elif context.is_cross_compiling: 

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

1411 

1412 if context.is_cross_compiling: 

1413 deb_host2cmake_system = { 

1414 "linux": "Linux", 

1415 "kfreebsd": "kFreeBSD", 

1416 "hurd": "GNU", 

1417 } 

1418 

1419 gnu_cpu2system_processor = { 

1420 "arm": "armv7l", 

1421 "misp64el": "mips64", 

1422 "powerpc64le": "ppc64le", 

1423 } 

1424 dpkg_architecture_variables = context.dpkg_architecture_variables 

1425 

1426 try: 

1427 system_name = deb_host2cmake_system[ 

1428 dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1429 ] 

1430 except KeyError as e: 

1431 name = e.args[0] 

1432 _error( 

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

1434 ) 

1435 

1436 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"] 

1437 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu) 

1438 

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

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

1441 

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

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

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

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

1446 cmake_flags.append( 

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

1448 ) 

1449 

1450 def configure_impl( 

1451 self, 

1452 context: "BuildContext", 

1453 manifest: "HighLevelManifest", 

1454 **kwargs, 

1455 ) -> None: 

1456 cmake_flags = [ 

1457 "-DCMAKE_INSTALL_PREFIX=/usr", 

1458 "-DCMAKE_BUILD_TYPE=None", 

1459 "-DCMAKE_INSTALL_SYSCONFDIR=/etc", 

1460 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var", 

1461 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON", 

1462 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", 

1463 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON", 

1464 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON", 

1465 "-DCMAKE_INSTALL_RUNSTATEDIR=/run", 

1466 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON", 

1467 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON", 

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

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

1470 ] 

1471 if not context.is_terse_build: 

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

1473 if not context.should_run_tests: 

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

1475 

1476 self._compiler_and_cross_flags(context, cmake_flags) 

1477 

1478 if self.configure_args: 

1479 substitution = self.substitution 

1480 attr_path = self.attribute_path["configure_args"] 

1481 cmake_flags.extend( 

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

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

1484 ) 

1485 

1486 env_mod = self._default_cmake_env(context) 

1487 if "CPPFLAGS" in os.environ: 

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

1489 cppflags = os.environ["CPPFLAGS"] 

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

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

1492 env_mod = env_mod.combine( 

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

1494 EnvironmentModification( 

1495 replacements=( 

1496 ("CFLAGS", cflags), 

1497 ("CXXFLAGS", cxxflags), 

1498 ) 

1499 ) 

1500 ) 

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

1502 env_mod = env_mod.combine( 

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

1504 EnvironmentModification( 

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

1506 ) 

1507 ) 

1508 self.ensure_build_dir_exists() 

1509 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1510 

1511 with self.dump_logs_on_error( 

1512 "CMakeCache.txt", 

1513 "CMakeFiles/CMakeOutput.log", 

1514 "CMakeFiles/CMakeError.log", 

1515 ): 

1516 run_build_system_command( 

1517 "cmake", 

1518 *cmake_flags, 

1519 source_dir_from_build_dir, 

1520 cwd=self.build_directory, 

1521 env_mod=env_mod, 

1522 ) 

1523 

1524 def build_impl( 

1525 self, 

1526 context: "BuildContext", 

1527 manifest: "HighLevelManifest", 

1528 **kwargs, 

1529 ) -> None: 

1530 if self.target_build_system == "make": 

1531 make_flags = [] 

1532 if not context.is_terse_build: 

1533 make_flags.append("VERBOSE=1") 

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

1535 else: 

1536 self._ninja_support.run_ninja_build(context) 

1537 

1538 def test_impl( 

1539 self, 

1540 context: "BuildContext", 

1541 manifest: "HighLevelManifest", 

1542 *, 

1543 should_ignore_test_errors: bool = False, 

1544 **kwargs, 

1545 ) -> None: 

1546 env_mod = EnvironmentModification( 

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

1548 ) 

1549 if self.target_build_system == "make": 

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

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

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

1553 if not context.is_terse_build: 

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

1555 self._make_support.run_first_existing_target_if_any( 

1556 context, 

1557 ["check", "test"], 

1558 *make_flags, 

1559 env_mod=env_mod, 

1560 ) 

1561 else: 

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

1563 

1564 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1566 

1567 if not context.is_terse_build: 

1568 testsuite_flags.append("--verbose") 

1569 self._make_support.run_first_existing_target_if_any( 

1570 context, 

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

1572 ["check", "test"], 

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

1574 "VERBOSE=1", 

1575 ) 

1576 

1577 def install_impl( 

1578 self, 

1579 context: "BuildContext", 

1580 manifest: "HighLevelManifest", 

1581 dest_dir: str, 

1582 **kwargs, 

1583 ) -> None: 

1584 env_mod = EnvironmentModification( 

1585 replacements=( 

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

1587 ("DESTDIR", dest_dir), 

1588 ) 

1589 ).combine(self._default_cmake_env(context)) 

1590 run_build_system_command( 

1591 "cmake", 

1592 "--install", 

1593 self.build_directory, 

1594 env_mod=env_mod, 

1595 ) 

1596 

1597 def clean_impl( 

1598 self, 

1599 context: "BuildContext", 

1600 manifest: "HighLevelManifest", 

1601 clean_helper: "CleanHelper", 

1602 **kwargs, 

1603 ) -> None: 

1604 if self.out_of_source_build: 

1605 return 

1606 if self.target_build_system == "make": 

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

1608 self._make_support.run_first_existing_target_if_any( 

1609 context, 

1610 ["distclean", "realclean", "clean"], 

1611 ) 

1612 else: 

1613 self._ninja_support.run_ninja_clean(context) 

1614 

1615 

1616class MesonBuildSystemRule(StepBasedBuildSystemRule): 

1617 

1618 __slots__ = ( 

1619 "configure_args", 

1620 "_ninja_support", 

1621 ) 

1622 

1623 def __init__( 

1624 self, 

1625 parsed_data: "ParsedMesonBuildRuleDefinition", 

1626 attribute_path: AttributePath, 

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

1628 ) -> None: 

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

1630 configure_args = [a for a in parsed_data.get("configure_args", [])] 

1631 self.configure_args = configure_args 

1632 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1633 

1634 @classmethod 

1635 def characteristics(cls) -> BuildSystemCharacteristics: 

1636 return BuildSystemCharacteristics( 

1637 out_of_source_builds="required", 

1638 ) 

1639 

1640 @classmethod 

1641 def auto_detect_build_system( 

1642 cls, 

1643 source_root: VirtualPath, 

1644 *args, 

1645 **kwargs, 

1646 ) -> bool: 

1647 return "meson.build" in source_root 

1648 

1649 @staticmethod 

1650 def _default_meson_env() -> EnvironmentModification: 

1651 replacements = { 

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

1653 } 

1654 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1655 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1656 return EnvironmentModification( 

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

1658 ) 

1659 

1660 @classmethod 

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

1662 cmake_generators = { 

1663 "make": "Unix Makefiles", 

1664 "ninja": "Ninja", 

1665 } 

1666 return cmake_generators[target_build_system] 

1667 

1668 @staticmethod 

1669 def _cross_flags( 

1670 context: "BuildContext", 

1671 meson_flags: list[str], 

1672 ) -> None: 

1673 if not context.is_cross_compiling: 

1674 return 

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

1676 cross_files_dir = os.path.abspath( 

1677 generated_content_dir( 

1678 subdir_key="meson-cross-files", 

1679 ) 

1680 ) 

1681 host_arch = context.dpkg_architecture_variables.current_host_arch 

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

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

1684 env = os.environ 

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

1686 env = dict(env) 

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

1688 else: 

1689 env = None 

1690 subprocess.check_call( 

1691 [ 

1692 "/usr/share/meson/debcrossgen", 

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

1694 f"-o{cross_file}", 

1695 ], 

1696 stdout=subprocess.DEVNULL, 

1697 env=env, 

1698 ) 

1699 

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

1701 meson_flags.append(cross_file) 

1702 

1703 def configure_impl( 

1704 self, 

1705 context: "BuildContext", 

1706 manifest: "HighLevelManifest", 

1707 **kwargs, 

1708 ) -> None: 

1709 meson_version = Version( 

1710 subprocess.check_output( 

1711 ["meson", "--version"], 

1712 encoding="utf-8", 

1713 ).strip() 

1714 ) 

1715 dpkg_architecture_variables = context.dpkg_architecture_variables 

1716 

1717 meson_flags = [ 

1718 "--wrap-mode=nodownload", 

1719 "--buildtype=plain", 

1720 "--prefix=/usr", 

1721 "--sysconfdir=/etc", 

1722 "--localstatedir=/var", 

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

1724 "--auto-features=enabled", 

1725 ] 

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

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

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

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

1730 # where the option exists. 

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

1732 

1733 self._cross_flags(context, meson_flags) 

1734 

1735 if self.configure_args: 

1736 substitution = self.substitution 

1737 attr_path = self.attribute_path["configure_args"] 

1738 meson_flags.extend( 

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

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

1741 ) 

1742 

1743 env_mod = self._default_meson_env() 

1744 

1745 self.ensure_build_dir_exists() 

1746 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1747 

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

1749 run_build_system_command( 

1750 "meson", 

1751 "setup", 

1752 source_dir_from_build_dir, 

1753 *meson_flags, 

1754 cwd=self.build_directory, 

1755 env_mod=env_mod, 

1756 ) 

1757 

1758 def build_impl( 

1759 self, 

1760 context: "BuildContext", 

1761 manifest: "HighLevelManifest", 

1762 **kwargs, 

1763 ) -> None: 

1764 self._ninja_support.run_ninja_build(context) 

1765 

1766 def test_impl( 

1767 self, 

1768 context: "BuildContext", 

1769 manifest: "HighLevelManifest", 

1770 *, 

1771 should_ignore_test_errors: bool = False, 

1772 **kwargs, 

1773 ) -> None: 

1774 env_mod = EnvironmentModification( 

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

1776 ).combine(self._default_meson_env()) 

1777 meson_args = [] 

1778 if not context.is_terse_build: 

1779 meson_args.append("--verbose") 

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

1781 run_build_system_command( 

1782 "meson", 

1783 "test", 

1784 *meson_args, 

1785 env_mod=env_mod, 

1786 cwd=self.build_directory, 

1787 ) 

1788 

1789 def install_impl( 

1790 self, 

1791 context: "BuildContext", 

1792 manifest: "HighLevelManifest", 

1793 dest_dir: str, 

1794 **kwargs, 

1795 ) -> None: 

1796 run_build_system_command( 

1797 "meson", 

1798 "install", 

1799 "--destdir", 

1800 dest_dir, 

1801 cwd=self.build_directory, 

1802 env_mod=self._default_meson_env(), 

1803 ) 

1804 

1805 def clean_impl( 

1806 self, 

1807 context: "BuildContext", 

1808 manifest: "HighLevelManifest", 

1809 clean_helper: "CleanHelper", 

1810 **kwargs, 

1811 ) -> None: 

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

1813 assert self.out_of_source_build 

1814 

1815 

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

1817 value = os.environ.get(envvar) 

1818 if value is None: 

1819 return 

1820 if include_cppflags: 

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

1822 if cppflags: 

1823 value = f"{value} {cppflags}" 

1824 

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

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

1827 

1828 

1829class ParsedGenericQmakeBuildRuleDefinition( 

1830 OptionalInstallDirectly, 

1831 OptionalInSourceBuild, 

1832 OptionalBuildDirectory, 

1833 OptionalTestRule, 

1834): 

1835 configure_args: NotRequired[list[str]] 

1836 

1837 

1838class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule): 

1839 

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

1841 

1842 def __init__( 

1843 self, 

1844 parsed_data: "ParsedGenericQmakeBuildRuleDefinition", 

1845 attribute_path: AttributePath, 

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

1847 ) -> None: 

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

1849 configure_args = [a for a in parsed_data.get("configure_args", [])] 

1850 self.configure_args = configure_args 

1851 self._make_support = MakefileSupport.from_build_system(self) 

1852 

1853 @classmethod 

1854 def characteristics(cls) -> BuildSystemCharacteristics: 

1855 return BuildSystemCharacteristics( 

1856 out_of_source_builds="supported-and-default", 

1857 ) 

1858 

1859 @classmethod 

1860 def auto_detect_build_system( 

1861 cls, 

1862 source_root: VirtualPath, 

1863 *args, 

1864 **kwargs, 

1865 ) -> bool: 

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

1867 

1868 @classmethod 

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

1870 return { 

1871 "linux": "linux-g++", 

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

1873 "hurd": "hurd-g++", 

1874 } 

1875 

1876 def qmake_command(self) -> str: 

1877 raise NotImplementedError 

1878 

1879 def configure_impl( 

1880 self, 

1881 context: "BuildContext", 

1882 manifest: "HighLevelManifest", 

1883 **kwargs, 

1884 ) -> None: 

1885 

1886 configure_args = [ 

1887 "-makefile", 

1888 ] 

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

1890 

1891 if context.is_cross_compiling: 

1892 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1893 os2mkspec = self.os_mkspec_mapping() 

1894 try: 

1895 spec = os2mkspec[host_os] 

1896 except KeyError: 

1897 _error( 

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

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

1900 ) 

1901 configure_args.append("-spec") 

1902 configure_args.append(spec) 

1903 

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

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

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

1907 

1908 configure_args.append("QMAKE_STRIP=:") 

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

1910 

1911 if self.configure_args: 

1912 substitution = self.substitution 

1913 attr_path = self.attribute_path["configure_args"] 

1914 configure_args.extend( 

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

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

1917 ) 

1918 

1919 self.ensure_build_dir_exists() 

1920 if not self.out_of_source_build: 

1921 configure_args.append(self.relative_from_builddir_to_source()) 

1922 

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

1924 run_build_system_command( 

1925 qmake_cmd, 

1926 *configure_args, 

1927 cwd=self.build_directory, 

1928 ) 

1929 

1930 def build_impl( 

1931 self, 

1932 context: "BuildContext", 

1933 manifest: "HighLevelManifest", 

1934 **kwargs, 

1935 ) -> None: 

1936 self._make_support.run_make(context) 

1937 

1938 def test_impl( 

1939 self, 

1940 context: "BuildContext", 

1941 manifest: "HighLevelManifest", 

1942 *, 

1943 should_ignore_test_errors: bool = False, 

1944 **kwargs, 

1945 ) -> None: 

1946 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1948 

1949 if not context.is_terse_build: 

1950 testsuite_flags.append("--verbose") 

1951 self._make_support.run_first_existing_target_if_any( 

1952 context, 

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

1954 ["check", "test"], 

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

1956 "VERBOSE=1", 

1957 ) 

1958 

1959 def install_impl( 

1960 self, 

1961 context: "BuildContext", 

1962 manifest: "HighLevelManifest", 

1963 dest_dir: str, 

1964 **kwargs, 

1965 ) -> None: 

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

1967 self._make_support.run_first_existing_target_if_any( 

1968 context, 

1969 ["install"], 

1970 f"DESTDIR={dest_dir}", 

1971 "AM_UPDATE_INFO_DIR=no", 

1972 enable_parallelization=enable_parallelization, 

1973 ) 

1974 

1975 def clean_impl( 

1976 self, 

1977 context: "BuildContext", 

1978 manifest: "HighLevelManifest", 

1979 clean_helper: "CleanHelper", 

1980 **kwargs, 

1981 ) -> None: 

1982 if self.out_of_source_build: 

1983 return 

1984 self._make_support.run_first_existing_target_if_any( 

1985 context, 

1986 ["distclean", "realclean", "clean"], 

1987 ) 

1988 

1989 

1990class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule): 

1991 

1992 def qmake_command(self) -> str: 

1993 return "qmake" 

1994 

1995 

1996class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule): 

1997 

1998 def qmake_command(self) -> str: 

1999 return "qmake6" 

2000 

2001 

2002@debputy_build_system( 

2003 "make", 

2004 MakefileBuildSystemRule, 

2005 auto_detection_shadows_build_systems="debhelper", 

2006 online_reference_documentation=reference_documentation( 

2007 title="Make Build System", 

2008 description=textwrap.dedent( 

2009 """\ 

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

2011 

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

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

2014 

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

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

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

2018 some other directory. 

2019 """ 

2020 ), 

2021 attributes=[ 

2022 documented_attr( 

2023 "directory", 

2024 textwrap.dedent( 

2025 """\ 

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

2027 

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

2029 """ 

2030 ), 

2031 ), 

2032 documented_attr( 

2033 "build_target", 

2034 textwrap.dedent( 

2035 """\ 

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

2037 

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

2039 the default. 

2040 """ 

2041 ), 

2042 ), 

2043 documented_attr( 

2044 "test_target", 

2045 textwrap.dedent( 

2046 """\ 

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

2048 

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

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

2051 """ 

2052 ), 

2053 ), 

2054 documented_attr( 

2055 "install_target", 

2056 textwrap.dedent( 

2057 """\ 

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

2059 

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

2061 Otherwise, the step will be skipped. 

2062 """ 

2063 ), 

2064 ), 

2065 *docs_from( 

2066 DebputyParsedContentStandardConditional, 

2067 OptionalInstallDirectly, 

2068 OptionalTestRule, 

2069 BuildRuleParsedFormat, 

2070 ), 

2071 ], 

2072 ), 

2073) 

2074class ParsedMakeBuildRuleDefinition( 

2075 OptionalInstallDirectly, 

2076 OptionalTestRule, 

2077): 

2078 directory: NotRequired[FileSystemExactMatchRule] 

2079 build_target: NotRequired[str] 

2080 test_target: NotRequired[str] 

2081 install_target: NotRequired[str] 

2082 

2083 

2084@debputy_build_system( 

2085 "autoconf", 

2086 AutoconfBuildSystemRule, 

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

2088 online_reference_documentation=reference_documentation( 

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

2090 description=textwrap.dedent( 

2091 """\ 

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

2093 

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

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

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

2097 

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

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

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

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

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

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

2104 """ 

2105 ), 

2106 attributes=[ 

2107 documented_attr( 

2108 "configure_args", 

2109 textwrap.dedent( 

2110 """\ 

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

2112 """ 

2113 ), 

2114 ), 

2115 *docs_from( 

2116 DebputyParsedContentStandardConditional, 

2117 OptionalInstallDirectly, 

2118 OptionalInSourceBuild, 

2119 OptionalBuildDirectory, 

2120 OptionalTestRule, 

2121 BuildRuleParsedFormat, 

2122 ), 

2123 ], 

2124 ), 

2125) 

2126class ParsedAutoconfBuildRuleDefinition( 

2127 OptionalInstallDirectly, 

2128 OptionalInSourceBuild, 

2129 OptionalBuildDirectory, 

2130 OptionalTestRule, 

2131): 

2132 configure_args: NotRequired[list[str]] 

2133 

2134 

2135@debputy_build_system( 

2136 "cmake", 

2137 CMakeBuildSystemRule, 

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

2139 online_reference_documentation=reference_documentation( 

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

2141 description=textwrap.dedent( 

2142 """\ 

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

2144 

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

2146 """ 

2147 ), 

2148 attributes=[ 

2149 documented_attr( 

2150 "configure_args", 

2151 textwrap.dedent( 

2152 """\ 

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

2154 """ 

2155 ), 

2156 ), 

2157 documented_attr( 

2158 "target_build_system", 

2159 textwrap.dedent( 

2160 """\ 

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

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

2163 

2164 Supported options are: 

2165 * `make` - GNU Make 

2166 * `ninja` - Ninja 

2167 """ 

2168 ), 

2169 ), 

2170 *docs_from( 

2171 DebputyParsedContentStandardConditional, 

2172 OptionalInstallDirectly, 

2173 OptionalBuildDirectory, 

2174 OptionalTestRule, 

2175 BuildRuleParsedFormat, 

2176 ), 

2177 ], 

2178 ), 

2179) 

2180class ParsedCMakeBuildRuleDefinition( 

2181 OptionalInstallDirectly, 

2182 OptionalBuildDirectory, 

2183 OptionalTestRule, 

2184): 

2185 configure_args: NotRequired[list[str]] 

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

2187 

2188 

2189@debputy_build_system( 

2190 "meson", 

2191 MesonBuildSystemRule, 

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

2193 online_reference_documentation=reference_documentation( 

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

2195 description=textwrap.dedent( 

2196 """\ 

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

2198 

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

2200 """ 

2201 ), 

2202 attributes=[ 

2203 documented_attr( 

2204 "configure_args", 

2205 textwrap.dedent( 

2206 """\ 

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

2208 """ 

2209 ), 

2210 ), 

2211 *docs_from( 

2212 DebputyParsedContentStandardConditional, 

2213 OptionalInstallDirectly, 

2214 OptionalBuildDirectory, 

2215 OptionalTestRule, 

2216 BuildRuleParsedFormat, 

2217 ), 

2218 ], 

2219 ), 

2220) 

2221class ParsedMesonBuildRuleDefinition( 

2222 OptionalInstallDirectly, 

2223 OptionalBuildDirectory, 

2224 OptionalTestRule, 

2225): 

2226 configure_args: NotRequired[list[str]] 

2227 

2228 

2229@debputy_build_system( 

2230 "perl-build", 

2231 PerlBuildBuildSystemRule, 

2232 auto_detection_shadows_build_systems=[ 

2233 "debhelper", 

2234 "make", 

2235 "perl-makemaker", 

2236 ], 

2237 online_reference_documentation=reference_documentation( 

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

2239 description=textwrap.dedent( 

2240 """\ 

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

2242 

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

2244 upstream code. 

2245 """ 

2246 ), 

2247 attributes=[ 

2248 documented_attr( 

2249 "configure_args", 

2250 textwrap.dedent( 

2251 """\ 

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

2253 """ 

2254 ), 

2255 ), 

2256 *docs_from( 

2257 DebputyParsedContentStandardConditional, 

2258 OptionalInstallDirectly, 

2259 OptionalTestRule, 

2260 BuildRuleParsedFormat, 

2261 ), 

2262 ], 

2263 ), 

2264) 

2265class ParsedPerlBuildBuildRuleDefinition( 

2266 OptionalInstallDirectly, 

2267 OptionalTestRule, 

2268): 

2269 configure_args: NotRequired[list[str]] 

2270 

2271 

2272@debputy_build_system( 

2273 "debhelper", 

2274 DebhelperBuildSystemRule, 

2275 online_reference_documentation=reference_documentation( 

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

2277 description=textwrap.dedent( 

2278 """\ 

2279 Delegate to a debhelper provided build system 

2280 

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

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

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

2284 `dh-build-system` attribute. 

2285 """ 

2286 ), 

2287 attributes=[ 

2288 documented_attr( 

2289 "dh_build_system", 

2290 textwrap.dedent( 

2291 """\ 

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

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

2294 for that will be accepted. 

2295 

2296 Note that many debhelper build systems require extra build 

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

2298 of the relevant debhelper build system for details. 

2299 """ 

2300 ), 

2301 ), 

2302 documented_attr( 

2303 "configure_args", 

2304 textwrap.dedent( 

2305 """\ 

2306 Arguments to be passed to underlying configuration command 

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

2308 """ 

2309 ), 

2310 ), 

2311 *docs_from( 

2312 DebputyParsedContentStandardConditional, 

2313 OptionalInstallDirectly, 

2314 OptionalBuildDirectory, 

2315 OptionalTestRule, 

2316 BuildRuleParsedFormat, 

2317 ), 

2318 ], 

2319 ), 

2320) 

2321class ParsedDebhelperBuildRuleDefinition( 

2322 OptionalInstallDirectly, 

2323 OptionalBuildDirectory, 

2324 OptionalTestRule, 

2325): 

2326 configure_args: NotRequired[list[str]] 

2327 dh_build_system: NotRequired[str] 

2328 

2329 

2330@debputy_build_system( 

2331 "perl-makemaker", 

2332 PerlMakeMakerBuildSystemRule, 

2333 auto_detection_shadows_build_systems=[ 

2334 "debhelper", 

2335 "make", 

2336 ], 

2337 online_reference_documentation=reference_documentation( 

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

2339 description=textwrap.dedent( 

2340 """\ 

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

2342 

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

2344 upstream code. 

2345 """ 

2346 ), 

2347 attributes=[ 

2348 documented_attr( 

2349 "configure_args", 

2350 textwrap.dedent( 

2351 """\ 

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

2353 """ 

2354 ), 

2355 ), 

2356 *docs_from( 

2357 DebputyParsedContentStandardConditional, 

2358 OptionalInstallDirectly, 

2359 OptionalTestRule, 

2360 BuildRuleParsedFormat, 

2361 ), 

2362 ], 

2363 ), 

2364) 

2365class ParsedPerlMakeMakerBuildRuleDefinition( 

2366 OptionalInstallDirectly, 

2367 OptionalTestRule, 

2368): 

2369 configure_args: NotRequired[list[str]] 

2370 

2371 

2372@debputy_build_system( 

2373 "qmake", 

2374 QmakeBuildSystemRule, 

2375 auto_detection_shadows_build_systems=[ 

2376 "debhelper", 

2377 "make", 

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

2379 ], 

2380 online_reference_documentation=reference_documentation( 

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

2382 description=textwrap.dedent( 

2383 """\ 

2384 Build using the "qmake" by QT. 

2385 """ 

2386 ), 

2387 attributes=[ 

2388 documented_attr( 

2389 "configure_args", 

2390 textwrap.dedent( 

2391 """\ 

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

2393 """ 

2394 ), 

2395 ), 

2396 *docs_from( 

2397 DebputyParsedContentStandardConditional, 

2398 OptionalInstallDirectly, 

2399 OptionalInSourceBuild, 

2400 OptionalBuildDirectory, 

2401 OptionalTestRule, 

2402 BuildRuleParsedFormat, 

2403 ), 

2404 ], 

2405 ), 

2406) 

2407class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2408 pass 

2409 

2410 

2411@debputy_build_system( 

2412 "qmake6", 

2413 Qmake6BuildSystemRule, 

2414 auto_detection_shadows_build_systems=[ 

2415 "debhelper", 

2416 "make", 

2417 ], 

2418 online_reference_documentation=reference_documentation( 

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

2420 description=textwrap.dedent( 

2421 """\ 

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

2423 but is specifically for QT6. 

2424 """ 

2425 ), 

2426 attributes=[ 

2427 documented_attr( 

2428 "configure_args", 

2429 textwrap.dedent( 

2430 """\ 

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

2432 """ 

2433 ), 

2434 ), 

2435 *docs_from( 

2436 DebputyParsedContentStandardConditional, 

2437 OptionalInstallDirectly, 

2438 OptionalInSourceBuild, 

2439 OptionalBuildDirectory, 

2440 OptionalTestRule, 

2441 BuildRuleParsedFormat, 

2442 ), 

2443 ], 

2444 ), 

2445) 

2446class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2447 pass 

2448 

2449 

2450def _parse_default_environment( 

2451 _name: str, 

2452 parsed_data: EnvironmentSourceFormat, 

2453 attribute_path: AttributePath, 

2454 parser_context: ParserContextData, 

2455) -> ManifestProvidedBuildEnvironment: 

2456 return ManifestProvidedBuildEnvironment.from_environment_definition( 

2457 parsed_data, 

2458 attribute_path, 

2459 parser_context, 

2460 is_default=True, 

2461 ) 

2462 

2463 

2464def _parse_build_environments( 

2465 _name: str, 

2466 parsed_data: list[NamedEnvironmentSourceFormat], 

2467 attribute_path: AttributePath, 

2468 parser_context: ParserContextData, 

2469) -> list[ManifestProvidedBuildEnvironment]: 

2470 return [ 

2471 ManifestProvidedBuildEnvironment.from_environment_definition( 

2472 value, 

2473 attribute_path[idx], 

2474 parser_context, 

2475 is_default=False, 

2476 ) 

2477 for idx, value in enumerate(parsed_data) 

2478 ] 

2479 

2480 

2481def _handle_build_rules( 

2482 _name: str, 

2483 parsed_data: list[BuildRule], 

2484 _attribute_path: AttributePath, 

2485 _parser_context: ParserContextData, 

2486) -> list[BuildRule]: 

2487 return parsed_data