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

683 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-09-07 09:27 +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 Mapping, 

13 Sequence, 

14 MutableMapping, 

15 Iterable, 

16 Container, 

17 List, 

18 Tuple, 

19 Union, 

20 Optional, 

21 TYPE_CHECKING, 

22 Literal, 

23 Required, 

24) 

25 

26from debian.debian_support import Version 

27 

28from debputy._manifest_constants import MK_BUILDS 

29from debputy.manifest_conditions import ManifestCondition 

30from debputy.manifest_parser.base_types import ( 

31 BuildEnvironmentDefinition, 

32 DebputyParsedContentStandardConditional, 

33 FileSystemExactMatchRule, 

34) 

35from debputy.manifest_parser.exceptions import ( 

36 ManifestParseException, 

37 ManifestInvalidUserDataException, 

38) 

39from debputy.manifest_parser.parser_data import ParserContextData 

40from debputy.manifest_parser.tagging_types import DebputyParsedContent 

41from debputy.manifest_parser.util import AttributePath 

42from debputy.plugin.api import reference_documentation 

43from debputy.plugin.api.impl import ( 

44 DebputyPluginInitializerProvider, 

45) 

46from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT 

47from debputy.plugin.api.spec import ( 

48 documented_attr, 

49 INTEGRATION_MODE_FULL, 

50 only_integrations, 

51 VirtualPath, 

52) 

53from debputy.plugin.api.std_docs import docs_from 

54from debputy.plugins.debputy.to_be_api_types import ( 

55 BuildRule, 

56 StepBasedBuildSystemRule, 

57 OptionalInstallDirectly, 

58 BuildSystemCharacteristics, 

59 OptionalBuildDirectory, 

60 OptionalInSourceBuild, 

61 MakefileSupport, 

62 BuildRuleParsedFormat, 

63 debputy_build_system, 

64 CleanHelper, 

65 NinjaBuildSupport, 

66 TestRule, 

67 OptionalTestRule, 

68) 

69from debputy.types import EnvironmentModification 

70from debputy.util import ( 

71 _warn, 

72 run_build_system_command, 

73 _error, 

74 PerlConfigVars, 

75 resolve_perl_config, 

76 generated_content_dir, 

77 manifest_format_doc, 

78 _is_debug_log_enabled, 

79) 

80 

81if TYPE_CHECKING: 

82 from debputy.build_support.build_context import BuildContext 

83 from debputy.highlevel_manifest import HighLevelManifest 

84 

85 

86PERL_CMD = "perl" 

87 

88 

89class Conditional(DebputyParsedContent): 

90 when: Required[ManifestCondition] 

91 

92 

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

94 register_build_keywords(api) 

95 register_build_rules(api) 

96 

97 

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

99 

100 api.pluggable_manifest_rule( 

101 OPARSER_MANIFEST_ROOT, 

102 "build-environments", 

103 List[NamedEnvironmentSourceFormat], 

104 _parse_build_environments, 

105 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

106 inline_reference_documentation=reference_documentation( 

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

108 description=textwrap.dedent( 

109 """\ 

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

111 a non-default environment. 

112 

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

114 build commands. An example: 

115 

116 build-environments: 

117 - name: custom-env 

118 set: 

119 ENV_VAR: foo 

120 ANOTHER_ENV_VAR: bar 

121 builds: 

122 - autoconf: 

123 environment: custom-env 

124 

125 The environment definition has multiple attributes for setting environment variables 

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

127 result of the following order of operations. 

128 

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

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

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

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

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

134 

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

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

137 

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

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

140 """ 

141 ), 

142 attributes=[ 

143 documented_attr( 

144 "name", 

145 textwrap.dedent( 

146 """\ 

147 The name of the environment 

148 

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

150 """ 

151 ), 

152 ), 

153 documented_attr( 

154 "set", 

155 textwrap.dedent( 

156 """\ 

157 A mapping of environment variables to be set. 

158 

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

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

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

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

163 """ 

164 ), 

165 ), 

166 documented_attr( 

167 "override", 

168 textwrap.dedent( 

169 """\ 

170 A mapping of environment variables to set. 

171 

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

173 `dpkg-buildflags`. 

174 """ 

175 ), 

176 ), 

177 documented_attr( 

178 "unset", 

179 textwrap.dedent( 

180 """\ 

181 A list of environment variables to unset. 

182 

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

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

185 """ 

186 ), 

187 ), 

188 ], 

189 reference_documentation_url=manifest_format_doc( 

190 "build-environment-build-environment" 

191 ), 

192 ), 

193 ) 

194 api.pluggable_manifest_rule( 

195 OPARSER_MANIFEST_ROOT, 

196 "default-build-environment", 

197 EnvironmentSourceFormat, 

198 _parse_default_environment, 

199 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

200 inline_reference_documentation=reference_documentation( 

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

202 description=textwrap.dedent( 

203 """\ 

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

205 environment. 

206 

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

208 build commands. An example: 

209  

210 default-build-environment: 

211 set: 

212 ENV_VAR: foo 

213 ANOTHER_ENV_VAR: bar 

214 

215 The environment definition has multiple attributes for setting environment variables 

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

217 result of the following order of operations. 

218  

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

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

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

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

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

224 

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

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

227 

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

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

230 """ 

231 ), 

232 attributes=[ 

233 documented_attr( 

234 "set", 

235 textwrap.dedent( 

236 """\ 

237 A mapping of environment variables to be set. 

238 

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

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

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

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

243 """ 

244 ), 

245 ), 

246 documented_attr( 

247 "override", 

248 textwrap.dedent( 

249 """\ 

250 A mapping of environment variables to set. 

251 

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

253 `dpkg-buildflags`. 

254 """ 

255 ), 

256 ), 

257 documented_attr( 

258 "unset", 

259 textwrap.dedent( 

260 """\ 

261 A list of environment variables to unset. 

262 

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

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

265 """ 

266 ), 

267 ), 

268 ], 

269 reference_documentation_url=manifest_format_doc( 

270 "build-environment-build-environment" 

271 ), 

272 ), 

273 ) 

274 api.pluggable_manifest_rule( 

275 OPARSER_MANIFEST_ROOT, 

276 MK_BUILDS, 

277 List[BuildRule], 

278 _handle_build_rules, 

279 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

280 inline_reference_documentation=reference_documentation( 

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

282 description=textwrap.dedent( 

283 """\ 

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

285 which also defines the clean rules. 

286 

287 A simple example is: 

288 

289 ```yaml 

290 builds: 

291 - autoconf: 

292 configure-args: 

293 - "--enable-foo" 

294 - "--without=bar" 

295 ``` 

296 

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

298 for `libpam-krb5` might look. 

299 

300 ```yaml 

301 builds: 

302 - autoconf: 

303 for: libpam-krb5 

304 install-directly-to-package: true 

305 configure-args: 

306 - "--enable-reduced-depends" 

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

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

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

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

311 - autoconf: 

312 for: libpam-heimdal 

313 install-directly-to-package: true 

314 configure-args: 

315 - "--enable-reduced-depends" 

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

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

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

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

320 ``` 

321 """ 

322 ), 

323 ), 

324 ) 

325 

326 api.provide_manifest_keyword( 

327 TestRule, 

328 "skip-tests", 

329 lambda n, ap, pc: TestRule( 

330 ap.path, 

331 ManifestCondition.literal_bool(False), 

332 ManifestCondition.literal_bool(True), 

333 ), 

334 inline_reference_documentation=reference_documentation( 

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

336 description=textwrap.dedent( 

337 """\ 

338 Skip all build time tests. 

339 

340 Example: 

341 

342 ```yaml 

343 ...: 

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

345 test-rule: skip-tests 

346 ``` 

347 

348 """ 

349 ), 

350 ), 

351 ) 

352 

353 api.pluggable_manifest_rule( 

354 TestRule, 

355 "skip-tests-when", 

356 Conditional, 

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

358 ap.path, 

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

360 ManifestCondition.literal_bool(False), 

361 ), 

362 source_format=ManifestCondition, 

363 inline_reference_documentation=reference_documentation( 

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

365 description=textwrap.dedent( 

366 """\ 

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

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

369 

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

371 then wrap the conditional with `not:`. 

372 

373 Example: 

374 

375 ```yaml 

376 ...: 

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

378 test-rule: 

379 skip-tests-when: 

380 not: 

381 arch-matches: "linux-any" 

382 ``` 

383 

384 """ 

385 ), 

386 ), 

387 ) 

388 

389 

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

391 api.register_build_system(ParsedAutoconfBuildRuleDefinition) 

392 api.register_build_system(ParsedMakeBuildRuleDefinition) 

393 

394 api.register_build_system(ParsedPerlBuildBuildRuleDefinition) 

395 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition) 

396 api.register_build_system(ParsedDebhelperBuildRuleDefinition) 

397 

398 api.register_build_system(ParsedCMakeBuildRuleDefinition) 

399 api.register_build_system(ParsedMesonBuildRuleDefinition) 

400 

401 api.register_build_system(ParsedQmakeBuildRuleDefinition) 

402 api.register_build_system(ParsedQmake6BuildRuleDefinition) 

403 

404 

405class EnvironmentSourceFormat(TypedDict): 

406 set: NotRequired[Dict[str, str]] 

407 override: NotRequired[Dict[str, str]] 

408 unset: NotRequired[List[str]] 

409 

410 

411class NamedEnvironmentSourceFormat(EnvironmentSourceFormat): 

412 name: str 

413 

414 

415_READ_ONLY_ENV_VARS = { 

416 "DEB_CHECK_COMMAND": None, 

417 "DEB_SIGN_KEYID": None, 

418 "DEB_SIGN_KEYFILE": None, 

419 "DEB_BUILD_OPTIONS": "DEB_BUILD_MAINT_OPTIONS", 

420 "DEB_BUILD_PROFILES": None, 

421 "DEB_RULES_REQUIRES_ROOT": None, 

422 "DEB_GAIN_ROOT_COMMAND": None, 

423 "DH_EXTRA_ADDONS": None, 

424 "DH_NO_ACT": None, 

425 "XDG_RUNTIME_DIR": None, 

426 "HOME": None, 

427} 

428 

429 

430def _check_variables( 

431 env_vars: Iterable[str], 

432 attribute_path: AttributePath, 

433) -> None: 

434 for env_var in env_vars: 

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

436 continue 

437 alt = _READ_ONLY_ENV_VARS.get(env_var) 

438 var_path = attribute_path[env_var].path_key_lc 

439 if alt is None: 

440 raise ManifestParseException( 

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

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

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

444 ) 

445 else: 

446 raise ManifestParseException( 

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

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

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

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

451 f" The problematic definition was {var_path}" 

452 ) 

453 

454 

455def _no_overlap( 

456 lhs: Iterable[Union[str, Tuple[int, str]]], 

457 rhs: Container[str], 

458 lhs_key: str, 

459 rhs_key: str, 

460 redundant_key: str, 

461 attribute_path: AttributePath, 

462) -> None: 

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

464 if isinstance(kt, tuple): 

465 lhs_path_key, var = kt 

466 else: 

467 lhs_path_key = var = kt 

468 if var not in rhs: 

469 continue 

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

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

472 r_path = lhs_path if redundant_key == rhs_key else rhs_path 

473 raise ManifestParseException( 

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

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

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

477 f" the two definitions." 

478 ) 

479 

480 

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

482class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition): 

483 

484 name: str 

485 is_default: bool 

486 attribute_path: AttributePath 

487 parser_context: ParserContextData 

488 

489 set_vars: Mapping[str, str] 

490 override_vars: Mapping[str, str] 

491 unset_vars: Sequence[str] 

492 

493 @classmethod 

494 def from_environment_definition( 

495 cls, 

496 env: EnvironmentSourceFormat, 

497 attribute_path: AttributePath, 

498 parser_context: ParserContextData, 

499 is_default: bool = False, 

500 ) -> Self: 

501 reference_name: Optional[str] 

502 if is_default: 

503 name = "default-env" 

504 reference_name = None 

505 else: 

506 named_env = cast("NamedEnvironmentSourceFormat", env) 

507 name = named_env["name"] 

508 reference_name = name 

509 

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

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

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

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

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

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

516 

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

518 raise ManifestParseException( 

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

520 " some content or delete the definition." 

521 ) 

522 

523 _no_overlap( 

524 enumerate(unset_vars), 

525 set_vars, 

526 "unset", 

527 "set", 

528 "set", 

529 attribute_path, 

530 ) 

531 _no_overlap( 

532 enumerate(unset_vars), 

533 override_vars, 

534 "unset", 

535 "override", 

536 "override", 

537 attribute_path, 

538 ) 

539 _no_overlap( 

540 override_vars, 

541 set_vars, 

542 "override", 

543 "set", 

544 "set", 

545 attribute_path, 

546 ) 

547 

548 r = cls( 

549 name, 

550 is_default, 

551 attribute_path, 

552 parser_context, 

553 set_vars, 

554 override_vars, 

555 unset_vars, 

556 ) 

557 parser_context._register_build_environment( 

558 reference_name, 

559 r, 

560 attribute_path, 

561 is_default, 

562 ) 

563 

564 return r 

565 

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

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

568 env.update(set_vars) 

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

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

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

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

573 for var in overlapping_env: 

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

575 _warn( 

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

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

578 f" `set`." 

579 ) 

580 env.update(dpkg_env) 

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

582 env.update(override_vars) 

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

584 for var in unset_vars: 

585 try: 

586 del env[var] 

587 except KeyError: 

588 pass 

589 

590 

591_MAKE_DEFAULT_TOOLS = [ 

592 ("CC", "gcc"), 

593 ("CXX", "g++"), 

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

595] 

596 

597 

598class MakefileBuildSystemRule(StepBasedBuildSystemRule): 

599 

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

601 

602 def __init__( 

603 self, 

604 attributes: "ParsedMakeBuildRuleDefinition", 

605 attribute_path: AttributePath, 

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

607 ) -> None: 

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

609 directory = attributes.get("directory") 

610 if directory is not None: 

611 self._directory = directory.match_rule.path 

612 else: 

613 self._directory = None 

614 self._make_support = MakefileSupport.from_build_system(self) 

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

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

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

618 

619 @classmethod 

620 def auto_detect_build_system( 

621 cls, 

622 source_root: VirtualPath, 

623 *args, 

624 **kwargs, 

625 ) -> bool: 

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

627 

628 @classmethod 

629 def characteristics(cls) -> BuildSystemCharacteristics: 

630 return BuildSystemCharacteristics( 

631 out_of_source_builds="not-supported", 

632 ) 

633 

634 def configure_impl( 

635 self, 

636 context: "BuildContext", 

637 manifest: "HighLevelManifest", 

638 **kwargs, 

639 ) -> None: 

640 # No configure step 

641 pass 

642 

643 def build_impl( 

644 self, 

645 context: "BuildContext", 

646 manifest: "HighLevelManifest", 

647 **kwargs, 

648 ) -> None: 

649 extra_vars = [] 

650 build_target = self._build_target 

651 if build_target is not None: 

652 extra_vars.append(build_target) 

653 if context.is_cross_compiling: 

654 for envvar, tool in _MAKE_DEFAULT_TOOLS: 

655 cross_tool = os.environ.get(envvar) 

656 if cross_tool is None: 

657 cross_tool = context.cross_tool(tool) 

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

659 self._make_support.run_make( 

660 context, 

661 *extra_vars, 

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

663 directory=self._directory, 

664 ) 

665 

666 def test_impl( 

667 self, 

668 context: "BuildContext", 

669 manifest: "HighLevelManifest", 

670 *, 

671 should_ignore_test_errors: bool = False, 

672 **kwargs, 

673 ) -> None: 

674 self._run_make_maybe_explicit_target( 

675 context, 

676 self._test_target, 

677 ["test", "check"], 

678 ) 

679 

680 def install_impl( 

681 self, 

682 context: "BuildContext", 

683 manifest: "HighLevelManifest", 

684 dest_dir: str, 

685 **kwargs, 

686 ) -> None: 

687 self._run_make_maybe_explicit_target( 

688 context, 

689 self._install_target, 

690 ["install"], 

691 f"DESTDIR={dest_dir}", 

692 "AM_UPDATE_INFO_DIR=no", 

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

694 ) 

695 

696 def _run_make_maybe_explicit_target( 

697 self, 

698 context: "BuildContext", 

699 provided_target: Optional[str], 

700 fallback_targets: Sequence[str], 

701 *make_args: str, 

702 ) -> None: 

703 make_support = self._make_support 

704 if provided_target is not None: 

705 make_support.run_make( 

706 context, 

707 provided_target, 

708 *make_args, 

709 directory=self._directory, 

710 ) 

711 else: 

712 make_support.run_first_existing_target_if_any( 

713 context, 

714 fallback_targets, 

715 *make_args, 

716 directory=self._directory, 

717 ) 

718 

719 def clean_impl( 

720 self, 

721 context: "BuildContext", 

722 manifest: "HighLevelManifest", 

723 clean_helper: "CleanHelper", 

724 **kwargs, 

725 ) -> None: 

726 self._make_support.run_first_existing_target_if_any( 

727 context, 

728 ["distclean", "realclean", "clean"], 

729 ) 

730 

731 

732class PerlBuildBuildSystemRule(StepBasedBuildSystemRule): 

733 

734 __slots__ = "configure_args" 

735 

736 def __init__( 

737 self, 

738 attributes: "ParsedPerlBuildBuildRuleDefinition", 

739 attribute_path: AttributePath, 

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

741 ) -> None: 

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

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

744 

745 @classmethod 

746 def auto_detect_build_system( 

747 cls, 

748 source_root: VirtualPath, 

749 *args, 

750 **kwargs, 

751 ) -> bool: 

752 return "Build.PL" in source_root 

753 

754 @classmethod 

755 def characteristics(cls) -> BuildSystemCharacteristics: 

756 return BuildSystemCharacteristics( 

757 out_of_source_builds="not-supported", 

758 ) 

759 

760 @staticmethod 

761 def _perl_cross_build_env( 

762 context: "BuildContext", 

763 ) -> Tuple[PerlConfigVars, Optional[EnvironmentModification]]: 

764 perl_config_data = resolve_perl_config( 

765 context.dpkg_architecture_variables, 

766 None, 

767 ) 

768 if context.is_cross_compiling: 

769 perl5lib_dir = perl_config_data.cross_inc_dir 

770 if perl5lib_dir is not None: 

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

772 if env_perl5lib is not None: 

773 perl5lib_dir = ( 

774 perl5lib_dir + perl_config_data.path_sep + env_perl5lib 

775 ) 

776 env_mod = EnvironmentModification( 

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

778 ) 

779 return perl_config_data, env_mod 

780 return perl_config_data, None 

781 

782 def configure_impl( 

783 self, 

784 context: "BuildContext", 

785 manifest: "HighLevelManifest", 

786 **kwargs, 

787 ) -> None: 

788 perl_config_data, cross_env_mod = self._perl_cross_build_env(context) 

789 configure_env = EnvironmentModification( 

790 replacements=( 

791 ("PERL_MM_USE_DEFAULT", "1"), 

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

793 ) 

794 ) 

795 if cross_env_mod is not None: 

796 configure_env = configure_env.combine(cross_env_mod) 

797 

798 configure_cmd = [ 

799 PERL_CMD, 

800 "Build.PL", 

801 "--installdirs", 

802 "vendor", 

803 ] 

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

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

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

807 

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

809 configure_cmd.append("--config") 

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

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

812 

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

814 configure_cmd.append("--config") 

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

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

817 if self.configure_args: 

818 substitution = self.substitution 

819 attr_path = self.attribute_path["configure_args"] 

820 configure_cmd.extend( 

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

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

823 ) 

824 run_build_system_command(*configure_cmd, env_mod=configure_env) 

825 

826 def build_impl( 

827 self, 

828 context: "BuildContext", 

829 manifest: "HighLevelManifest", 

830 **kwargs, 

831 ) -> None: 

832 _, cross_env_mod = self._perl_cross_build_env(context) 

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

834 

835 def test_impl( 

836 self, 

837 context: "BuildContext", 

838 manifest: "HighLevelManifest", 

839 *, 

840 should_ignore_test_errors: bool = False, 

841 **kwargs, 

842 ) -> None: 

843 _, cross_env_mod = self._perl_cross_build_env(context) 

844 run_build_system_command( 

845 PERL_CMD, 

846 "Build", 

847 "test", 

848 "--verbose", 

849 "1", 

850 env_mod=cross_env_mod, 

851 ) 

852 

853 def install_impl( 

854 self, 

855 context: "BuildContext", 

856 manifest: "HighLevelManifest", 

857 dest_dir: str, 

858 **kwargs, 

859 ) -> None: 

860 _, cross_env_mod = self._perl_cross_build_env(context) 

861 run_build_system_command( 

862 PERL_CMD, 

863 "Build", 

864 "install", 

865 "--destdir", 

866 dest_dir, 

867 "--create_packlist", 

868 "0", 

869 env_mod=cross_env_mod, 

870 ) 

871 

872 def clean_impl( 

873 self, 

874 context: "BuildContext", 

875 manifest: "HighLevelManifest", 

876 clean_helper: "CleanHelper", 

877 **kwargs, 

878 ) -> None: 

879 _, cross_env_mod = self._perl_cross_build_env(context) 

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

881 run_build_system_command( 

882 PERL_CMD, 

883 "Build", 

884 "realclean", 

885 "--allow_mb_mismatch", 

886 "1", 

887 env_mod=cross_env_mod, 

888 ) 

889 

890 

891class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule): 

892 

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

894 

895 def __init__( 

896 self, 

897 attributes: "ParsedPerlBuildBuildRuleDefinition", 

898 attribute_path: AttributePath, 

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

900 ) -> None: 

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

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

903 self._make_support = MakefileSupport.from_build_system(self) 

904 

905 @classmethod 

906 def auto_detect_build_system( 

907 cls, 

908 source_root: VirtualPath, 

909 *args, 

910 **kwargs, 

911 ) -> bool: 

912 return "Makefile.PL" in source_root 

913 

914 @classmethod 

915 def characteristics(cls) -> BuildSystemCharacteristics: 

916 return BuildSystemCharacteristics( 

917 out_of_source_builds="not-supported", 

918 ) 

919 

920 def configure_impl( 

921 self, 

922 context: "BuildContext", 

923 manifest: "HighLevelManifest", 

924 **kwargs, 

925 ) -> None: 

926 configure_env = EnvironmentModification( 

927 replacements=( 

928 ("PERL_MM_USE_DEFAULT", "1"), 

929 ("PERL_AUTOINSTALL", "--skipdeps"), 

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

931 ) 

932 ) 

933 perl_args = [] 

934 mm_args = ["INSTALLDIRS=vendor"] 

935 if "CFLAGS" in os.environ: 

936 mm_args.append( 

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

938 ) 

939 

940 perl_config_data = resolve_perl_config( 

941 context.dpkg_architecture_variables, 

942 None, 

943 ) 

944 

945 if "LDFLAGS" in os.environ: 

946 mm_args.append( 

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

948 ) 

949 

950 if context.is_cross_compiling: 

951 perl5lib_dir = perl_config_data.cross_inc_dir 

952 if perl5lib_dir is not None: 

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

954 

955 if self.configure_args: 

956 substitution = self.substitution 

957 attr_path = self.attribute_path["configure_args"] 

958 mm_args.extend( 

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

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

961 ) 

962 run_build_system_command( 

963 PERL_CMD, 

964 *perl_args, 

965 "Makefile.PL", 

966 *mm_args, 

967 env_mod=configure_env, 

968 ) 

969 

970 def build_impl( 

971 self, 

972 context: "BuildContext", 

973 manifest: "HighLevelManifest", 

974 **kwargs, 

975 ) -> None: 

976 self._make_support.run_make(context) 

977 

978 def test_impl( 

979 self, 

980 context: "BuildContext", 

981 manifest: "HighLevelManifest", 

982 *, 

983 should_ignore_test_errors: bool = False, 

984 **kwargs, 

985 ) -> None: 

986 self._make_support.run_first_existing_target_if_any( 

987 context, 

988 ["check", "test"], 

989 "TEST_VERBOSE=1", 

990 ) 

991 

992 def install_impl( 

993 self, 

994 context: "BuildContext", 

995 manifest: "HighLevelManifest", 

996 dest_dir: str, 

997 **kwargs, 

998 ) -> None: 

999 is_mm_makefile = False 

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

1001 for line in fd: 

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

1003 is_mm_makefile = True 

1004 break 

1005 

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

1007 

1008 # Special case for Makefile.PL that uses 

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

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

1011 if is_mm_makefile: 

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

1013 

1014 self._make_support.run_first_existing_target_if_any( 

1015 context, 

1016 ["install"], 

1017 *install_args, 

1018 ) 

1019 

1020 def clean_impl( 

1021 self, 

1022 context: "BuildContext", 

1023 manifest: "HighLevelManifest", 

1024 clean_helper: "CleanHelper", 

1025 **kwargs, 

1026 ) -> None: 

1027 self._make_support.run_first_existing_target_if_any( 

1028 context, 

1029 ["distclean", "realclean", "clean"], 

1030 ) 

1031 

1032 

1033class DebhelperBuildSystemRule(StepBasedBuildSystemRule): 

1034 

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

1036 

1037 def __init__( 

1038 self, 

1039 parsed_data: "ParsedDebhelperBuildRuleDefinition", 

1040 attribute_path: AttributePath, 

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

1042 ) -> None: 

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

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

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

1046 

1047 @classmethod 

1048 def auto_detect_build_system( 

1049 cls, 

1050 source_root: VirtualPath, 

1051 *args, 

1052 **kwargs, 

1053 ) -> bool: 

1054 try: 

1055 v = subprocess.check_output( 

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

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

1058 stderr=subprocess.DEVNULL, 

1059 cwd=source_root.fs_path, 

1060 ) 

1061 except subprocess.CalledProcessError: 

1062 return False 

1063 else: 

1064 d = json.loads(v) 

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

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

1067 

1068 @classmethod 

1069 def characteristics(cls) -> BuildSystemCharacteristics: 

1070 return BuildSystemCharacteristics( 

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

1072 ) 

1073 

1074 def before_first_impl_step( 

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

1076 ) -> None: 

1077 dh_build_system = self.dh_build_system 

1078 if dh_build_system is None: 

1079 return 

1080 try: 

1081 subprocess.check_call( 

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

1083 ) 

1084 except FileNotFoundError: 

1085 _error( 

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

1087 ) 

1088 except subprocess.SubprocessError: 

1089 raise ManifestInvalidUserDataException( 

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

1091 f" be available according to" 

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

1093 ) from None 

1094 

1095 def _default_options(self) -> List[str]: 

1096 default_options = [] 

1097 if self.dh_build_system is not None: 

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

1099 if self.build_directory is not None: 

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

1101 

1102 return default_options 

1103 

1104 def configure_impl( 

1105 self, 

1106 context: "BuildContext", 

1107 manifest: "HighLevelManifest", 

1108 **kwargs, 

1109 ) -> None: 

1110 if ( 

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

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

1113 run_build_system_command("dh_update_autotools_config") 

1114 run_build_system_command("dh_autoreconf") 

1115 

1116 default_options = self._default_options() 

1117 configure_args = default_options.copy() 

1118 if self.configure_args: 

1119 configure_args.append("--") 

1120 substitution = self.substitution 

1121 attr_path = self.attribute_path["configure_args"] 

1122 configure_args.extend( 

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

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

1125 ) 

1126 run_build_system_command("dh_auto_configure", *configure_args) 

1127 

1128 def build_impl( 

1129 self, 

1130 context: "BuildContext", 

1131 manifest: "HighLevelManifest", 

1132 **kwargs, 

1133 ) -> None: 

1134 default_options = self._default_options() 

1135 run_build_system_command("dh_auto_build", *default_options) 

1136 

1137 def test_impl( 

1138 self, 

1139 context: "BuildContext", 

1140 manifest: "HighLevelManifest", 

1141 *, 

1142 should_ignore_test_errors: bool = False, 

1143 **kwargs, 

1144 ) -> None: 

1145 default_options = self._default_options() 

1146 run_build_system_command("dh_auto_test", *default_options) 

1147 

1148 def install_impl( 

1149 self, 

1150 context: "BuildContext", 

1151 manifest: "HighLevelManifest", 

1152 dest_dir: str, 

1153 **kwargs, 

1154 ) -> None: 

1155 default_options = self._default_options() 

1156 run_build_system_command( 

1157 "dh_auto_install", 

1158 *default_options, 

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

1160 ) 

1161 

1162 def clean_impl( 

1163 self, 

1164 context: "BuildContext", 

1165 manifest: "HighLevelManifest", 

1166 clean_helper: "CleanHelper", 

1167 **kwargs, 

1168 ) -> None: 

1169 default_options = self._default_options() 

1170 run_build_system_command("dh_auto_clean", *default_options) 

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

1172 

1173 

1174class AutoconfBuildSystemRule(StepBasedBuildSystemRule): 

1175 

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

1177 

1178 def __init__( 

1179 self, 

1180 parsed_data: "ParsedAutoconfBuildRuleDefinition", 

1181 attribute_path: AttributePath, 

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

1183 ) -> None: 

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

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

1186 self.configure_args = configure_args 

1187 self._make_support = MakefileSupport.from_build_system(self) 

1188 

1189 @classmethod 

1190 def characteristics(cls) -> BuildSystemCharacteristics: 

1191 return BuildSystemCharacteristics( 

1192 out_of_source_builds="supported-and-default", 

1193 ) 

1194 

1195 @classmethod 

1196 def auto_detect_build_system( 

1197 cls, 

1198 source_root: VirtualPath, 

1199 *args, 

1200 **kwargs, 

1201 ) -> bool: 

1202 if "configure.ac" in source_root: 

1203 return True 

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

1205 if configure_in is not None and configure_in.is_file: 

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

1207 for no, line in enumerate(fd): 

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

1209 break 

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

1211 return True 

1212 configure = source_root.get("configure") 

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

1214 return False 

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

1216 for no, line in enumerate(fd): 

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

1218 break 

1219 if b"GNU Autoconf" in line: 

1220 return True 

1221 return False 

1222 

1223 def configure_impl( 

1224 self, 

1225 context: "BuildContext", 

1226 manifest: "HighLevelManifest", 

1227 **kwargs, 

1228 ) -> None: 

1229 if ( 

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

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

1232 run_build_system_command("dh_update_autotools_config") 

1233 run_build_system_command("dh_autoreconf") 

1234 

1235 dpkg_architecture_variables = context.dpkg_architecture_variables 

1236 multi_arch = dpkg_architecture_variables.current_host_multiarch 

1237 silent_rules = ( 

1238 "--enable-silent-rules" 

1239 if context.is_terse_build 

1240 else "--disable-silent-rules" 

1241 ) 

1242 

1243 configure_args = [ 

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

1245 "--prefix=/usr", 

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

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

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

1249 "--sysconfdir=/etc", 

1250 "--localstatedir=/var", 

1251 "--disable-option-checking", 

1252 silent_rules, 

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

1254 "--runstatedir=/run", 

1255 "--disable-maintainer-mode", 

1256 "--disable-dependency-tracking", 

1257 ] 

1258 if dpkg_architecture_variables.is_cross_compiling: 

1259 configure_args.append( 

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

1261 ) 

1262 if self.configure_args: 

1263 substitution = self.substitution 

1264 attr_path = self.attribute_path["configure_args"] 

1265 configure_args.extend( 

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

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

1268 ) 

1269 self.ensure_build_dir_exists() 

1270 configure_script = self.relative_from_builddir_to_source("configure") 

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

1272 run_build_system_command( 

1273 configure_script, 

1274 *configure_args, 

1275 cwd=self.build_directory, 

1276 ) 

1277 

1278 def build_impl( 

1279 self, 

1280 context: "BuildContext", 

1281 manifest: "HighLevelManifest", 

1282 **kwargs, 

1283 ) -> None: 

1284 self._make_support.run_make(context) 

1285 

1286 def test_impl( 

1287 self, 

1288 context: "BuildContext", 

1289 manifest: "HighLevelManifest", 

1290 *, 

1291 should_ignore_test_errors: bool = False, 

1292 **kwargs, 

1293 ) -> None: 

1294 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1296 

1297 if not context.is_terse_build: 

1298 testsuite_flags.append("--verbose") 

1299 self._make_support.run_first_existing_target_if_any( 

1300 context, 

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

1302 ["check", "test"], 

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

1304 "VERBOSE=1", 

1305 ) 

1306 

1307 def install_impl( 

1308 self, 

1309 context: "BuildContext", 

1310 manifest: "HighLevelManifest", 

1311 dest_dir: str, 

1312 **kwargs, 

1313 ) -> None: 

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

1315 self._make_support.run_first_existing_target_if_any( 

1316 context, 

1317 ["install"], 

1318 f"DESTDIR={dest_dir}", 

1319 "AM_UPDATE_INFO_DIR=no", 

1320 enable_parallelization=enable_parallelization, 

1321 ) 

1322 

1323 def clean_impl( 

1324 self, 

1325 context: "BuildContext", 

1326 manifest: "HighLevelManifest", 

1327 clean_helper: "CleanHelper", 

1328 **kwargs, 

1329 ) -> None: 

1330 if self.out_of_source_build: 

1331 return 

1332 self._make_support.run_first_existing_target_if_any( 

1333 context, 

1334 ["distclean", "realclean", "clean"], 

1335 ) 

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

1337 

1338 

1339class CMakeBuildSystemRule(StepBasedBuildSystemRule): 

1340 

1341 __slots__ = ( 

1342 "configure_args", 

1343 "target_build_system", 

1344 "_make_support", 

1345 "_ninja_support", 

1346 ) 

1347 

1348 def __init__( 

1349 self, 

1350 parsed_data: "ParsedCMakeBuildRuleDefinition", 

1351 attribute_path: AttributePath, 

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

1353 ) -> None: 

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

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

1356 self.configure_args = configure_args 

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

1358 "target_build_system", "make" 

1359 ) 

1360 self._make_support = MakefileSupport.from_build_system(self) 

1361 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1362 

1363 @classmethod 

1364 def characteristics(cls) -> BuildSystemCharacteristics: 

1365 return BuildSystemCharacteristics( 

1366 out_of_source_builds="required", 

1367 ) 

1368 

1369 @classmethod 

1370 def auto_detect_build_system( 

1371 cls, 

1372 source_root: VirtualPath, 

1373 *args, 

1374 **kwargs, 

1375 ) -> bool: 

1376 return "CMakeLists.txt" in source_root 

1377 

1378 @staticmethod 

1379 def _default_cmake_env( 

1380 build_context: "BuildContext", 

1381 ) -> EnvironmentModification: 

1382 replacements = {} 

1383 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1384 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1385 if "PKG_CONFIG" not in os.environ: 

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

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

1388 return EnvironmentModification( 

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

1390 ) 

1391 

1392 @classmethod 

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

1394 cmake_generators = { 

1395 "make": "Unix Makefiles", 

1396 "ninja": "Ninja", 

1397 } 

1398 return cmake_generators[target_build_system] 

1399 

1400 @staticmethod 

1401 def _compiler_and_cross_flags( 

1402 context: "BuildContext", 

1403 cmake_flags: List[str], 

1404 ) -> None: 

1405 

1406 if "CC" in os.environ: 

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

1408 elif context.is_cross_compiling: 

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

1410 

1411 if "CXX" in os.environ: 

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

1413 elif context.is_cross_compiling: 

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

1415 

1416 if context.is_cross_compiling: 

1417 deb_host2cmake_system = { 

1418 "linux": "Linux", 

1419 "kfreebsd": "kFreeBSD", 

1420 "hurd": "GNU", 

1421 } 

1422 

1423 gnu_cpu2system_processor = { 

1424 "arm": "armv7l", 

1425 "misp64el": "mips64", 

1426 "powerpc64le": "ppc64le", 

1427 } 

1428 dpkg_architecture_variables = context.dpkg_architecture_variables 

1429 

1430 try: 

1431 system_name = deb_host2cmake_system[ 

1432 dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1433 ] 

1434 except KeyError as e: 

1435 name = e.args[0] 

1436 _error( 

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

1438 ) 

1439 

1440 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"] 

1441 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu) 

1442 

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

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

1445 

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

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

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

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

1450 cmake_flags.append( 

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

1452 ) 

1453 

1454 def configure_impl( 

1455 self, 

1456 context: "BuildContext", 

1457 manifest: "HighLevelManifest", 

1458 **kwargs, 

1459 ) -> None: 

1460 cmake_flags = [ 

1461 "-DCMAKE_INSTALL_PREFIX=/usr", 

1462 "-DCMAKE_BUILD_TYPE=None", 

1463 "-DCMAKE_INSTALL_SYSCONFDIR=/etc", 

1464 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var", 

1465 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON", 

1466 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", 

1467 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON", 

1468 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON", 

1469 "-DCMAKE_INSTALL_RUNSTATEDIR=/run", 

1470 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON", 

1471 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON", 

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

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

1474 ] 

1475 if not context.is_terse_build: 

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

1477 if not context.should_run_tests: 

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

1479 

1480 self._compiler_and_cross_flags(context, cmake_flags) 

1481 

1482 if self.configure_args: 

1483 substitution = self.substitution 

1484 attr_path = self.attribute_path["configure_args"] 

1485 cmake_flags.extend( 

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

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

1488 ) 

1489 

1490 env_mod = self._default_cmake_env(context) 

1491 if "CPPFLAGS" in os.environ: 

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

1493 cppflags = os.environ["CPPFLAGS"] 

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

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

1496 env_mod = env_mod.combine( 

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

1498 EnvironmentModification( 

1499 replacements=( 

1500 ("CFLAGS", cflags), 

1501 ("CXXFLAGS", cxxflags), 

1502 ) 

1503 ) 

1504 ) 

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

1506 env_mod = env_mod.combine( 

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

1508 EnvironmentModification( 

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

1510 ) 

1511 ) 

1512 self.ensure_build_dir_exists() 

1513 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1514 

1515 with self.dump_logs_on_error( 

1516 "CMakeCache.txt", 

1517 "CMakeFiles/CMakeOutput.log", 

1518 "CMakeFiles/CMakeError.log", 

1519 ): 

1520 run_build_system_command( 

1521 "cmake", 

1522 *cmake_flags, 

1523 source_dir_from_build_dir, 

1524 cwd=self.build_directory, 

1525 env_mod=env_mod, 

1526 ) 

1527 

1528 def build_impl( 

1529 self, 

1530 context: "BuildContext", 

1531 manifest: "HighLevelManifest", 

1532 **kwargs, 

1533 ) -> None: 

1534 if self.target_build_system == "make": 

1535 make_flags = [] 

1536 if not context.is_terse_build: 

1537 make_flags.append("VERBOSE=1") 

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

1539 else: 

1540 self._ninja_support.run_ninja_build(context) 

1541 

1542 def test_impl( 

1543 self, 

1544 context: "BuildContext", 

1545 manifest: "HighLevelManifest", 

1546 *, 

1547 should_ignore_test_errors: bool = False, 

1548 **kwargs, 

1549 ) -> None: 

1550 env_mod = EnvironmentModification( 

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

1552 ) 

1553 if self.target_build_system == "make": 

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

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

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

1557 if not context.is_terse_build: 

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

1559 self._make_support.run_first_existing_target_if_any( 

1560 context, 

1561 ["check", "test"], 

1562 *make_flags, 

1563 env_mod=env_mod, 

1564 ) 

1565 else: 

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

1567 

1568 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1570 

1571 if not context.is_terse_build: 

1572 testsuite_flags.append("--verbose") 

1573 self._make_support.run_first_existing_target_if_any( 

1574 context, 

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

1576 ["check", "test"], 

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

1578 "VERBOSE=1", 

1579 ) 

1580 

1581 def install_impl( 

1582 self, 

1583 context: "BuildContext", 

1584 manifest: "HighLevelManifest", 

1585 dest_dir: str, 

1586 **kwargs, 

1587 ) -> None: 

1588 env_mod = EnvironmentModification( 

1589 replacements=( 

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

1591 ("DESTDIR", dest_dir), 

1592 ) 

1593 ).combine(self._default_cmake_env(context)) 

1594 run_build_system_command( 

1595 "cmake", 

1596 "--install", 

1597 self.build_directory, 

1598 env_mod=env_mod, 

1599 ) 

1600 

1601 def clean_impl( 

1602 self, 

1603 context: "BuildContext", 

1604 manifest: "HighLevelManifest", 

1605 clean_helper: "CleanHelper", 

1606 **kwargs, 

1607 ) -> None: 

1608 if self.out_of_source_build: 

1609 return 

1610 if self.target_build_system == "make": 

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

1612 self._make_support.run_first_existing_target_if_any( 

1613 context, 

1614 ["distclean", "realclean", "clean"], 

1615 ) 

1616 else: 

1617 self._ninja_support.run_ninja_clean(context) 

1618 

1619 

1620class MesonBuildSystemRule(StepBasedBuildSystemRule): 

1621 

1622 __slots__ = ( 

1623 "configure_args", 

1624 "_ninja_support", 

1625 ) 

1626 

1627 def __init__( 

1628 self, 

1629 parsed_data: "ParsedMesonBuildRuleDefinition", 

1630 attribute_path: AttributePath, 

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

1632 ) -> None: 

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

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

1635 self.configure_args = configure_args 

1636 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1637 

1638 @classmethod 

1639 def characteristics(cls) -> BuildSystemCharacteristics: 

1640 return BuildSystemCharacteristics( 

1641 out_of_source_builds="required", 

1642 ) 

1643 

1644 @classmethod 

1645 def auto_detect_build_system( 

1646 cls, 

1647 source_root: VirtualPath, 

1648 *args, 

1649 **kwargs, 

1650 ) -> bool: 

1651 return "meson.build" in source_root 

1652 

1653 @staticmethod 

1654 def _default_meson_env() -> EnvironmentModification: 

1655 replacements = { 

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

1657 } 

1658 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1659 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1660 return EnvironmentModification( 

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

1662 ) 

1663 

1664 @classmethod 

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

1666 cmake_generators = { 

1667 "make": "Unix Makefiles", 

1668 "ninja": "Ninja", 

1669 } 

1670 return cmake_generators[target_build_system] 

1671 

1672 @staticmethod 

1673 def _cross_flags( 

1674 context: "BuildContext", 

1675 meson_flags: List[str], 

1676 ) -> None: 

1677 if not context.is_cross_compiling: 

1678 return 

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

1680 cross_files_dir = os.path.abspath( 

1681 generated_content_dir( 

1682 subdir_key="meson-cross-files", 

1683 ) 

1684 ) 

1685 host_arch = context.dpkg_architecture_variables.current_host_arch 

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

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

1688 env = os.environ 

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

1690 env = dict(env) 

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

1692 else: 

1693 env = None 

1694 subprocess.check_call( 

1695 [ 

1696 "/usr/share/meson/debcrossgen", 

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

1698 f"-o{cross_file}", 

1699 ], 

1700 stdout=subprocess.DEVNULL, 

1701 env=env, 

1702 ) 

1703 

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

1705 meson_flags.append(cross_file) 

1706 

1707 def configure_impl( 

1708 self, 

1709 context: "BuildContext", 

1710 manifest: "HighLevelManifest", 

1711 **kwargs, 

1712 ) -> None: 

1713 meson_version = Version( 

1714 subprocess.check_output( 

1715 ["meson", "--version"], 

1716 encoding="utf-8", 

1717 ).strip() 

1718 ) 

1719 dpkg_architecture_variables = context.dpkg_architecture_variables 

1720 

1721 meson_flags = [ 

1722 "--wrap-mode=nodownload", 

1723 "--buildtype=plain", 

1724 "--prefix=/usr", 

1725 "--sysconfdir=/etc", 

1726 "--localstatedir=/var", 

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

1728 "--auto-features=enabled", 

1729 ] 

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

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

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

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

1734 # where the option exists. 

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

1736 

1737 self._cross_flags(context, meson_flags) 

1738 

1739 if self.configure_args: 

1740 substitution = self.substitution 

1741 attr_path = self.attribute_path["configure_args"] 

1742 meson_flags.extend( 

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

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

1745 ) 

1746 

1747 env_mod = self._default_meson_env() 

1748 

1749 self.ensure_build_dir_exists() 

1750 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1751 

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

1753 run_build_system_command( 

1754 "meson", 

1755 "setup", 

1756 source_dir_from_build_dir, 

1757 *meson_flags, 

1758 cwd=self.build_directory, 

1759 env_mod=env_mod, 

1760 ) 

1761 

1762 def build_impl( 

1763 self, 

1764 context: "BuildContext", 

1765 manifest: "HighLevelManifest", 

1766 **kwargs, 

1767 ) -> None: 

1768 self._ninja_support.run_ninja_build(context) 

1769 

1770 def test_impl( 

1771 self, 

1772 context: "BuildContext", 

1773 manifest: "HighLevelManifest", 

1774 *, 

1775 should_ignore_test_errors: bool = False, 

1776 **kwargs, 

1777 ) -> None: 

1778 env_mod = EnvironmentModification( 

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

1780 ).combine(self._default_meson_env()) 

1781 meson_args = [] 

1782 if not context.is_terse_build: 

1783 meson_args.append("--verbose") 

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

1785 run_build_system_command( 

1786 "meson", 

1787 "test", 

1788 *meson_args, 

1789 env_mod=env_mod, 

1790 cwd=self.build_directory, 

1791 ) 

1792 

1793 def install_impl( 

1794 self, 

1795 context: "BuildContext", 

1796 manifest: "HighLevelManifest", 

1797 dest_dir: str, 

1798 **kwargs, 

1799 ) -> None: 

1800 run_build_system_command( 

1801 "meson", 

1802 "install", 

1803 "--destdir", 

1804 dest_dir, 

1805 cwd=self.build_directory, 

1806 env_mod=self._default_meson_env(), 

1807 ) 

1808 

1809 def clean_impl( 

1810 self, 

1811 context: "BuildContext", 

1812 manifest: "HighLevelManifest", 

1813 clean_helper: "CleanHelper", 

1814 **kwargs, 

1815 ) -> None: 

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

1817 assert self.out_of_source_build 

1818 

1819 

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

1821 value = os.environ.get(envvar) 

1822 if value is None: 

1823 return 

1824 if include_cppflags: 

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

1826 if cppflags: 

1827 value = f"{value} {cppflags}" 

1828 

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

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

1831 

1832 

1833class ParsedGenericQmakeBuildRuleDefinition( 

1834 OptionalInstallDirectly, 

1835 OptionalInSourceBuild, 

1836 OptionalBuildDirectory, 

1837 OptionalTestRule, 

1838): 

1839 configure_args: NotRequired[List[str]] 

1840 

1841 

1842class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule): 

1843 

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

1845 

1846 def __init__( 

1847 self, 

1848 parsed_data: "ParsedGenericQmakeBuildRuleDefinition", 

1849 attribute_path: AttributePath, 

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

1851 ) -> None: 

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

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

1854 self.configure_args = configure_args 

1855 self._make_support = MakefileSupport.from_build_system(self) 

1856 

1857 @classmethod 

1858 def characteristics(cls) -> BuildSystemCharacteristics: 

1859 return BuildSystemCharacteristics( 

1860 out_of_source_builds="supported-and-default", 

1861 ) 

1862 

1863 @classmethod 

1864 def auto_detect_build_system( 

1865 cls, 

1866 source_root: VirtualPath, 

1867 *args, 

1868 **kwargs, 

1869 ) -> bool: 

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

1871 

1872 @classmethod 

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

1874 return { 

1875 "linux": "linux-g++", 

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

1877 "hurd": "hurd-g++", 

1878 } 

1879 

1880 def qmake_command(self) -> str: 

1881 raise NotImplementedError 

1882 

1883 def configure_impl( 

1884 self, 

1885 context: "BuildContext", 

1886 manifest: "HighLevelManifest", 

1887 **kwargs, 

1888 ) -> None: 

1889 

1890 configure_args = [ 

1891 "-makefile", 

1892 ] 

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

1894 

1895 if context.is_cross_compiling: 

1896 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1897 os2mkspec = self.os_mkspec_mapping() 

1898 try: 

1899 spec = os2mkspec[host_os] 

1900 except KeyError: 

1901 _error( 

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

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

1904 ) 

1905 configure_args.append("-spec") 

1906 configure_args.append(spec) 

1907 

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

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

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

1911 

1912 configure_args.append("QMAKE_STRIP=:") 

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

1914 

1915 if self.configure_args: 

1916 substitution = self.substitution 

1917 attr_path = self.attribute_path["configure_args"] 

1918 configure_args.extend( 

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

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

1921 ) 

1922 

1923 self.ensure_build_dir_exists() 

1924 if not self.out_of_source_build: 

1925 configure_args.append(self.relative_from_builddir_to_source()) 

1926 

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

1928 run_build_system_command( 

1929 qmake_cmd, 

1930 *configure_args, 

1931 cwd=self.build_directory, 

1932 ) 

1933 

1934 def build_impl( 

1935 self, 

1936 context: "BuildContext", 

1937 manifest: "HighLevelManifest", 

1938 **kwargs, 

1939 ) -> None: 

1940 self._make_support.run_make(context) 

1941 

1942 def test_impl( 

1943 self, 

1944 context: "BuildContext", 

1945 manifest: "HighLevelManifest", 

1946 *, 

1947 should_ignore_test_errors: bool = False, 

1948 **kwargs, 

1949 ) -> None: 

1950 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1952 

1953 if not context.is_terse_build: 

1954 testsuite_flags.append("--verbose") 

1955 self._make_support.run_first_existing_target_if_any( 

1956 context, 

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

1958 ["check", "test"], 

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

1960 "VERBOSE=1", 

1961 ) 

1962 

1963 def install_impl( 

1964 self, 

1965 context: "BuildContext", 

1966 manifest: "HighLevelManifest", 

1967 dest_dir: str, 

1968 **kwargs, 

1969 ) -> None: 

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

1971 self._make_support.run_first_existing_target_if_any( 

1972 context, 

1973 ["install"], 

1974 f"DESTDIR={dest_dir}", 

1975 "AM_UPDATE_INFO_DIR=no", 

1976 enable_parallelization=enable_parallelization, 

1977 ) 

1978 

1979 def clean_impl( 

1980 self, 

1981 context: "BuildContext", 

1982 manifest: "HighLevelManifest", 

1983 clean_helper: "CleanHelper", 

1984 **kwargs, 

1985 ) -> None: 

1986 if self.out_of_source_build: 

1987 return 

1988 self._make_support.run_first_existing_target_if_any( 

1989 context, 

1990 ["distclean", "realclean", "clean"], 

1991 ) 

1992 

1993 

1994class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule): 

1995 

1996 def qmake_command(self) -> str: 

1997 return "qmake" 

1998 

1999 

2000class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule): 

2001 

2002 def qmake_command(self) -> str: 

2003 return "qmake6" 

2004 

2005 

2006@debputy_build_system( 

2007 "make", 

2008 MakefileBuildSystemRule, 

2009 auto_detection_shadows_build_systems="debhelper", 

2010 online_reference_documentation=reference_documentation( 

2011 title="Make Build System", 

2012 description=textwrap.dedent( 

2013 """\ 

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

2015 

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

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

2018 

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

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

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

2022 some other directory. 

2023 """ 

2024 ), 

2025 attributes=[ 

2026 documented_attr( 

2027 "directory", 

2028 textwrap.dedent( 

2029 """\ 

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

2031 

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

2033 """ 

2034 ), 

2035 ), 

2036 documented_attr( 

2037 "build_target", 

2038 textwrap.dedent( 

2039 """\ 

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

2041 

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

2043 the default. 

2044 """ 

2045 ), 

2046 ), 

2047 documented_attr( 

2048 "test_target", 

2049 textwrap.dedent( 

2050 """\ 

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

2052 

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

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

2055 """ 

2056 ), 

2057 ), 

2058 documented_attr( 

2059 "install_target", 

2060 textwrap.dedent( 

2061 """\ 

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

2063 

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

2065 Otherwise, the step will be skipped. 

2066 """ 

2067 ), 

2068 ), 

2069 *docs_from( 

2070 DebputyParsedContentStandardConditional, 

2071 OptionalInstallDirectly, 

2072 OptionalTestRule, 

2073 BuildRuleParsedFormat, 

2074 ), 

2075 ], 

2076 ), 

2077) 

2078class ParsedMakeBuildRuleDefinition( 

2079 OptionalInstallDirectly, 

2080 OptionalTestRule, 

2081): 

2082 directory: NotRequired[FileSystemExactMatchRule] 

2083 build_target: NotRequired[str] 

2084 test_target: NotRequired[str] 

2085 install_target: NotRequired[str] 

2086 

2087 

2088@debputy_build_system( 

2089 "autoconf", 

2090 AutoconfBuildSystemRule, 

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

2092 online_reference_documentation=reference_documentation( 

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

2094 description=textwrap.dedent( 

2095 """\ 

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

2097 

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

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

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

2101 

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

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

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

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

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

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

2108 """ 

2109 ), 

2110 attributes=[ 

2111 documented_attr( 

2112 "configure_args", 

2113 textwrap.dedent( 

2114 """\ 

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

2116 """ 

2117 ), 

2118 ), 

2119 *docs_from( 

2120 DebputyParsedContentStandardConditional, 

2121 OptionalInstallDirectly, 

2122 OptionalInSourceBuild, 

2123 OptionalBuildDirectory, 

2124 OptionalTestRule, 

2125 BuildRuleParsedFormat, 

2126 ), 

2127 ], 

2128 ), 

2129) 

2130class ParsedAutoconfBuildRuleDefinition( 

2131 OptionalInstallDirectly, 

2132 OptionalInSourceBuild, 

2133 OptionalBuildDirectory, 

2134 OptionalTestRule, 

2135): 

2136 configure_args: NotRequired[List[str]] 

2137 

2138 

2139@debputy_build_system( 

2140 "cmake", 

2141 CMakeBuildSystemRule, 

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

2143 online_reference_documentation=reference_documentation( 

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

2145 description=textwrap.dedent( 

2146 """\ 

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

2148 

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

2150 """ 

2151 ), 

2152 attributes=[ 

2153 documented_attr( 

2154 "configure_args", 

2155 textwrap.dedent( 

2156 """\ 

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

2158 """ 

2159 ), 

2160 ), 

2161 documented_attr( 

2162 "target_build_system", 

2163 textwrap.dedent( 

2164 """\ 

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

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

2167 

2168 Supported options are: 

2169 * `make` - GNU Make 

2170 * `ninja` - Ninja 

2171 """ 

2172 ), 

2173 ), 

2174 *docs_from( 

2175 DebputyParsedContentStandardConditional, 

2176 OptionalInstallDirectly, 

2177 OptionalBuildDirectory, 

2178 OptionalTestRule, 

2179 BuildRuleParsedFormat, 

2180 ), 

2181 ], 

2182 ), 

2183) 

2184class ParsedCMakeBuildRuleDefinition( 

2185 OptionalInstallDirectly, 

2186 OptionalBuildDirectory, 

2187 OptionalTestRule, 

2188): 

2189 configure_args: NotRequired[List[str]] 

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

2191 

2192 

2193@debputy_build_system( 

2194 "meson", 

2195 MesonBuildSystemRule, 

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

2197 online_reference_documentation=reference_documentation( 

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

2199 description=textwrap.dedent( 

2200 """\ 

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

2202 

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

2204 """ 

2205 ), 

2206 attributes=[ 

2207 documented_attr( 

2208 "configure_args", 

2209 textwrap.dedent( 

2210 """\ 

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

2212 """ 

2213 ), 

2214 ), 

2215 *docs_from( 

2216 DebputyParsedContentStandardConditional, 

2217 OptionalInstallDirectly, 

2218 OptionalBuildDirectory, 

2219 OptionalTestRule, 

2220 BuildRuleParsedFormat, 

2221 ), 

2222 ], 

2223 ), 

2224) 

2225class ParsedMesonBuildRuleDefinition( 

2226 OptionalInstallDirectly, 

2227 OptionalBuildDirectory, 

2228 OptionalTestRule, 

2229): 

2230 configure_args: NotRequired[List[str]] 

2231 

2232 

2233@debputy_build_system( 

2234 "perl-build", 

2235 PerlBuildBuildSystemRule, 

2236 auto_detection_shadows_build_systems=[ 

2237 "debhelper", 

2238 "make", 

2239 "perl-makemaker", 

2240 ], 

2241 online_reference_documentation=reference_documentation( 

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

2243 description=textwrap.dedent( 

2244 """\ 

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

2246 

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

2248 upstream code. 

2249 """ 

2250 ), 

2251 attributes=[ 

2252 documented_attr( 

2253 "configure_args", 

2254 textwrap.dedent( 

2255 """\ 

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

2257 """ 

2258 ), 

2259 ), 

2260 *docs_from( 

2261 DebputyParsedContentStandardConditional, 

2262 OptionalInstallDirectly, 

2263 OptionalTestRule, 

2264 BuildRuleParsedFormat, 

2265 ), 

2266 ], 

2267 ), 

2268) 

2269class ParsedPerlBuildBuildRuleDefinition( 

2270 OptionalInstallDirectly, 

2271 OptionalTestRule, 

2272): 

2273 configure_args: NotRequired[List[str]] 

2274 

2275 

2276@debputy_build_system( 

2277 "debhelper", 

2278 DebhelperBuildSystemRule, 

2279 online_reference_documentation=reference_documentation( 

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

2281 description=textwrap.dedent( 

2282 """\ 

2283 Delegate to a debhelper provided build system 

2284 

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

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

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

2288 `dh-build-system` attribute. 

2289 """ 

2290 ), 

2291 attributes=[ 

2292 documented_attr( 

2293 "dh_build_system", 

2294 textwrap.dedent( 

2295 """\ 

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

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

2298 for that will be accepted. 

2299 

2300 Note that many debhelper build systems require extra build 

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

2302 of the relevant debhelper build system for details. 

2303 """ 

2304 ), 

2305 ), 

2306 documented_attr( 

2307 "configure_args", 

2308 textwrap.dedent( 

2309 """\ 

2310 Arguments to be passed to underlying configuration command 

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

2312 """ 

2313 ), 

2314 ), 

2315 *docs_from( 

2316 DebputyParsedContentStandardConditional, 

2317 OptionalInstallDirectly, 

2318 OptionalBuildDirectory, 

2319 OptionalTestRule, 

2320 BuildRuleParsedFormat, 

2321 ), 

2322 ], 

2323 ), 

2324) 

2325class ParsedDebhelperBuildRuleDefinition( 

2326 OptionalInstallDirectly, 

2327 OptionalBuildDirectory, 

2328 OptionalTestRule, 

2329): 

2330 configure_args: NotRequired[List[str]] 

2331 dh_build_system: NotRequired[str] 

2332 

2333 

2334@debputy_build_system( 

2335 "perl-makemaker", 

2336 PerlMakeMakerBuildSystemRule, 

2337 auto_detection_shadows_build_systems=[ 

2338 "debhelper", 

2339 "make", 

2340 ], 

2341 online_reference_documentation=reference_documentation( 

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

2343 description=textwrap.dedent( 

2344 """\ 

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

2346 

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

2348 upstream code. 

2349 """ 

2350 ), 

2351 attributes=[ 

2352 documented_attr( 

2353 "configure_args", 

2354 textwrap.dedent( 

2355 """\ 

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

2357 """ 

2358 ), 

2359 ), 

2360 *docs_from( 

2361 DebputyParsedContentStandardConditional, 

2362 OptionalInstallDirectly, 

2363 OptionalTestRule, 

2364 BuildRuleParsedFormat, 

2365 ), 

2366 ], 

2367 ), 

2368) 

2369class ParsedPerlMakeMakerBuildRuleDefinition( 

2370 OptionalInstallDirectly, 

2371 OptionalTestRule, 

2372): 

2373 configure_args: NotRequired[List[str]] 

2374 

2375 

2376@debputy_build_system( 

2377 "qmake", 

2378 QmakeBuildSystemRule, 

2379 auto_detection_shadows_build_systems=[ 

2380 "debhelper", 

2381 "make", 

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

2383 ], 

2384 online_reference_documentation=reference_documentation( 

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

2386 description=textwrap.dedent( 

2387 """\ 

2388 Build using the "qmake" by QT. 

2389 """ 

2390 ), 

2391 attributes=[ 

2392 documented_attr( 

2393 "configure_args", 

2394 textwrap.dedent( 

2395 """\ 

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

2397 """ 

2398 ), 

2399 ), 

2400 *docs_from( 

2401 DebputyParsedContentStandardConditional, 

2402 OptionalInstallDirectly, 

2403 OptionalInSourceBuild, 

2404 OptionalBuildDirectory, 

2405 OptionalTestRule, 

2406 BuildRuleParsedFormat, 

2407 ), 

2408 ], 

2409 ), 

2410) 

2411class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2412 pass 

2413 

2414 

2415@debputy_build_system( 

2416 "qmake6", 

2417 Qmake6BuildSystemRule, 

2418 auto_detection_shadows_build_systems=[ 

2419 "debhelper", 

2420 "make", 

2421 ], 

2422 online_reference_documentation=reference_documentation( 

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

2424 description=textwrap.dedent( 

2425 """\ 

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

2427 but is specifically for QT6. 

2428 """ 

2429 ), 

2430 attributes=[ 

2431 documented_attr( 

2432 "configure_args", 

2433 textwrap.dedent( 

2434 """\ 

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

2436 """ 

2437 ), 

2438 ), 

2439 *docs_from( 

2440 DebputyParsedContentStandardConditional, 

2441 OptionalInstallDirectly, 

2442 OptionalInSourceBuild, 

2443 OptionalBuildDirectory, 

2444 OptionalTestRule, 

2445 BuildRuleParsedFormat, 

2446 ), 

2447 ], 

2448 ), 

2449) 

2450class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2451 pass 

2452 

2453 

2454def _parse_default_environment( 

2455 _name: str, 

2456 parsed_data: EnvironmentSourceFormat, 

2457 attribute_path: AttributePath, 

2458 parser_context: ParserContextData, 

2459) -> ManifestProvidedBuildEnvironment: 

2460 return ManifestProvidedBuildEnvironment.from_environment_definition( 

2461 parsed_data, 

2462 attribute_path, 

2463 parser_context, 

2464 is_default=True, 

2465 ) 

2466 

2467 

2468def _parse_build_environments( 

2469 _name: str, 

2470 parsed_data: List[NamedEnvironmentSourceFormat], 

2471 attribute_path: AttributePath, 

2472 parser_context: ParserContextData, 

2473) -> List[ManifestProvidedBuildEnvironment]: 

2474 return [ 

2475 ManifestProvidedBuildEnvironment.from_environment_definition( 

2476 value, 

2477 attribute_path[idx], 

2478 parser_context, 

2479 is_default=False, 

2480 ) 

2481 for idx, value in enumerate(parsed_data) 

2482 ] 

2483 

2484 

2485def _handle_build_rules( 

2486 _name: str, 

2487 parsed_data: List[BuildRule], 

2488 _attribute_path: AttributePath, 

2489 _parser_context: ParserContextData, 

2490) -> List[BuildRule]: 

2491 return parsed_data