Coverage for src/debputy/plugin/debputy/build_system_rules.py: 40%

676 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-03-24 16:38 +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) 

24 

25from debian.debian_support import Version 

26 

27from debputy._manifest_constants import MK_BUILDS 

28from debputy.manifest_parser.base_types import ( 

29 BuildEnvironmentDefinition, 

30 DebputyParsedContentStandardConditional, 

31 FileSystemExactMatchRule, 

32) 

33from debputy.manifest_parser.exceptions import ( 

34 ManifestParseException, 

35 ManifestInvalidUserDataException, 

36) 

37from debputy.manifest_parser.parser_data import ParserContextData 

38from debputy.manifest_parser.util import AttributePath 

39from debputy.plugin.api import reference_documentation 

40from debputy.plugin.api.impl import ( 

41 DebputyPluginInitializerProvider, 

42) 

43from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT 

44from debputy.plugin.api.spec import ( 

45 documented_attr, 

46 INTEGRATION_MODE_FULL, 

47 only_integrations, 

48 VirtualPath, 

49) 

50from debputy.plugin.api.std_docs import docs_from 

51from debputy.plugin.debputy.to_be_api_types import ( 

52 BuildRule, 

53 StepBasedBuildSystemRule, 

54 OptionalInstallDirectly, 

55 BuildSystemCharacteristics, 

56 OptionalBuildDirectory, 

57 OptionalInSourceBuild, 

58 MakefileSupport, 

59 BuildRuleParsedFormat, 

60 debputy_build_system, 

61 CleanHelper, 

62 NinjaBuildSupport, 

63) 

64from debputy.types import EnvironmentModification 

65from debputy.util import ( 

66 _warn, 

67 run_build_system_command, 

68 _error, 

69 PerlConfigVars, 

70 resolve_perl_config, 

71 generated_content_dir, 

72 manifest_format_doc, 

73) 

74 

75if TYPE_CHECKING: 

76 from debputy.build_support.build_context import BuildContext 

77 from debputy.highlevel_manifest import HighLevelManifest 

78 

79 

80PERL_CMD = "perl" 

81 

82 

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

84 register_build_keywords(api) 

85 register_build_rules(api) 

86 

87 

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

89 

90 api.pluggable_manifest_rule( 

91 OPARSER_MANIFEST_ROOT, 

92 "build-environments", 

93 List[NamedEnvironmentSourceFormat], 

94 _parse_build_environments, 

95 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

96 inline_reference_documentation=reference_documentation( 

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

98 description=textwrap.dedent( 

99 """\ 

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

101 a non-default environment. 

102 

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

104 build commands. An example: 

105 

106 build-environments: 

107 - name: custom-env 

108 set: 

109 ENV_VAR: foo 

110 ANOTHER_ENV_VAR: bar 

111 builds: 

112 - autoconf: 

113 environment: custom-env 

114 

115 The environment definition has multiple attributes for setting environment variables 

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

117 result of the following order of operations. 

118 

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

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

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

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

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

124 

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

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

127 

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

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

130 """ 

131 ), 

132 attributes=[ 

133 documented_attr( 

134 "name", 

135 textwrap.dedent( 

136 """\ 

137 The name of the environment 

138 

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

140 """ 

141 ), 

142 ), 

143 documented_attr( 

144 "set", 

145 textwrap.dedent( 

146 """\ 

147 A mapping of environment variables to be set. 

148 

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

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

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

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

153 """ 

154 ), 

155 ), 

156 documented_attr( 

157 "override", 

158 textwrap.dedent( 

159 """\ 

160 A mapping of environment variables to set. 

161 

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

163 `dpkg-buildflags`. 

164 """ 

165 ), 

166 ), 

167 documented_attr( 

168 "unset", 

169 textwrap.dedent( 

170 """\ 

171 A list of environment variables to unset. 

172 

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

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

175 """ 

176 ), 

177 ), 

178 ], 

179 reference_documentation_url=manifest_format_doc( 

180 f"build-environment-build-environment" 

181 ), 

182 ), 

183 ) 

184 api.pluggable_manifest_rule( 

185 OPARSER_MANIFEST_ROOT, 

186 "default-build-environment", 

187 EnvironmentSourceFormat, 

188 _parse_default_environment, 

189 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

190 inline_reference_documentation=reference_documentation( 

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

192 description=textwrap.dedent( 

193 """\ 

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

195 environment. 

196 

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

198 build commands. An example: 

199  

200 default-build-environment: 

201 set: 

202 ENV_VAR: foo 

203 ANOTHER_ENV_VAR: bar 

204 

205 The environment definition has multiple attributes for setting environment variables 

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

207 result of the following order of operations. 

208  

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

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

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

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

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

214 

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

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

217 

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

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

220 """ 

221 ), 

222 attributes=[ 

223 documented_attr( 

224 "set", 

225 textwrap.dedent( 

226 """\ 

227 A mapping of environment variables to be set. 

228 

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

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

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

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

233 """ 

234 ), 

235 ), 

236 documented_attr( 

237 "override", 

238 textwrap.dedent( 

239 """\ 

240 A mapping of environment variables to set. 

241 

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

243 `dpkg-buildflags`. 

244 """ 

245 ), 

246 ), 

247 documented_attr( 

248 "unset", 

249 textwrap.dedent( 

250 """\ 

251 A list of environment variables to unset. 

252 

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

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

255 """ 

256 ), 

257 ), 

258 ], 

259 reference_documentation_url=manifest_format_doc( 

260 "build-environment-build-environment" 

261 ), 

262 ), 

263 ) 

264 api.pluggable_manifest_rule( 

265 OPARSER_MANIFEST_ROOT, 

266 MK_BUILDS, 

267 List[BuildRule], 

268 _handle_build_rules, 

269 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), 

270 inline_reference_documentation=reference_documentation( 

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

272 description=textwrap.dedent( 

273 """\ 

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

275 which also defines the clean rules. 

276 

277 A simple example is: 

278 

279 ```yaml 

280 builds: 

281 - autoconf: 

282 configure-args: 

283 - "--enable-foo" 

284 - "--without=bar" 

285 ``` 

286 

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

288 for `libpam-krb5` might look. 

289 

290 ```yaml 

291 builds: 

292 - autoconf: 

293 for: libpam-krb5 

294 install-directly-to-package: true 

295 configure-args: 

296 - "--enable-reduced-depends" 

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

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

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

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

301 - autoconf: 

302 for: libpam-heimdal 

303 install-directly-to-package: true 

304 configure-args: 

305 - "--enable-reduced-depends" 

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

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

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

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

310 ``` 

311 """ 

312 ), 

313 ), 

314 ) 

315 

316 

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

318 api.register_build_system(ParsedAutoconfBuildRuleDefinition) 

319 api.register_build_system(ParsedMakeBuildRuleDefinition) 

320 

321 api.register_build_system(ParsedPerlBuildBuildRuleDefinition) 

322 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition) 

323 api.register_build_system(ParsedDebhelperBuildRuleDefinition) 

324 

325 api.register_build_system(ParsedCMakeBuildRuleDefinition) 

326 api.register_build_system(ParsedMesonBuildRuleDefinition) 

327 

328 api.register_build_system(ParsedQmakeBuildRuleDefinition) 

329 api.register_build_system(ParsedQmake6BuildRuleDefinition) 

330 

331 

332class EnvironmentSourceFormat(TypedDict): 

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

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

335 unset: NotRequired[List[str]] 

336 

337 

338class NamedEnvironmentSourceFormat(EnvironmentSourceFormat): 

339 name: str 

340 

341 

342_READ_ONLY_ENV_VARS = { 

343 "DEB_CHECK_COMMAND": None, 

344 "DEB_SIGN_KEYID": None, 

345 "DEB_SIGN_KEYFILE": None, 

346 "DEB_BUILD_OPTIONS": "DEB_BUILD_MAINT_OPTIONS", 

347 "DEB_BUILD_PROFILES": None, 

348 "DEB_RULES_REQUIRES_ROOT": None, 

349 "DEB_GAIN_ROOT_COMMAND": None, 

350 "DH_EXTRA_ADDONS": None, 

351 "DH_NO_ACT": None, 

352} 

353 

354 

355def _check_variables( 

356 env_vars: Iterable[str], 

357 attribute_path: AttributePath, 

358) -> None: 

359 for env_var in env_vars: 

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

361 continue 

362 alt = _READ_ONLY_ENV_VARS.get(env_var) 

363 var_path = attribute_path[env_var].path_key_lc 

364 if alt is None: 

365 raise ManifestParseException( 

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

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

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

369 ) 

370 else: 

371 raise ManifestParseException( 

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

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

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

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

376 f" The problematic definition was {var_path}" 

377 ) 

378 

379 

380def _no_overlap( 

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

382 rhs: Container[str], 

383 lhs_key: str, 

384 rhs_key: str, 

385 redundant_key: str, 

386 attribute_path: AttributePath, 

387) -> None: 

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

389 if isinstance(kt, tuple): 

390 lhs_path_key, var = kt 

391 else: 

392 lhs_path_key = var = kt 

393 if var not in rhs: 

394 continue 

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

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

397 r_path = lhs_path if redundant_key == rhs_key else rhs_path 

398 raise ManifestParseException( 

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

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

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

402 f" the two definitions." 

403 ) 

404 

405 

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

407class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition): 

408 

409 name: str 

410 is_default: bool 

411 attribute_path: AttributePath 

412 parser_context: ParserContextData 

413 

414 set_vars: Mapping[str, str] 

415 override_vars: Mapping[str, str] 

416 unset_vars: Sequence[str] 

417 

418 @classmethod 

419 def from_environment_definition( 

420 cls, 

421 env: EnvironmentSourceFormat, 

422 attribute_path: AttributePath, 

423 parser_context: ParserContextData, 

424 is_default: bool = False, 

425 ) -> Self: 

426 reference_name: Optional[str] 

427 if is_default: 

428 name = "default-env" 

429 reference_name = None 

430 else: 

431 named_env = cast("NamedEnvironmentSourceFormat", env) 

432 name = named_env["name"] 

433 reference_name = name 

434 

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

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

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

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

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

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

441 

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

443 raise ManifestParseException( 

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

445 " some content or delete the definition." 

446 ) 

447 

448 _no_overlap( 

449 enumerate(unset_vars), 

450 set_vars, 

451 "unset", 

452 "set", 

453 "set", 

454 attribute_path, 

455 ) 

456 _no_overlap( 

457 enumerate(unset_vars), 

458 override_vars, 

459 "unset", 

460 "override", 

461 "override", 

462 attribute_path, 

463 ) 

464 _no_overlap( 

465 override_vars, 

466 set_vars, 

467 "override", 

468 "set", 

469 "set", 

470 attribute_path, 

471 ) 

472 

473 r = cls( 

474 name, 

475 is_default, 

476 attribute_path, 

477 parser_context, 

478 set_vars, 

479 override_vars, 

480 unset_vars, 

481 ) 

482 parser_context._register_build_environment( 

483 reference_name, 

484 r, 

485 attribute_path, 

486 is_default, 

487 ) 

488 

489 return r 

490 

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

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

493 env.update(set_vars) 

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

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

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

497 for var in overlapping_env: 

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

499 _warn( 

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

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

502 f" `set`." 

503 ) 

504 env.update(dpkg_env) 

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

506 env.update(override_vars) 

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

508 for var in unset_vars: 

509 try: 

510 del env[var] 

511 except KeyError: 

512 pass 

513 

514 

515_MAKE_DEFAULT_TOOLS = [ 

516 ("CC", "gcc"), 

517 ("CXX", "g++"), 

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

519] 

520 

521 

522class MakefileBuildSystemRule(StepBasedBuildSystemRule): 

523 

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

525 

526 def __init__( 

527 self, 

528 attributes: "ParsedMakeBuildRuleDefinition", 

529 attribute_path: AttributePath, 

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

531 ) -> None: 

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

533 directory = attributes.get("directory") 

534 if directory is not None: 

535 self._directory = directory.match_rule.path 

536 else: 

537 self._directory = None 

538 self._make_support = MakefileSupport.from_build_system(self) 

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

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

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

542 

543 @classmethod 

544 def auto_detect_build_system( 

545 cls, 

546 source_root: VirtualPath, 

547 *args, 

548 **kwargs, 

549 ) -> bool: 

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

551 

552 @classmethod 

553 def characteristics(cls) -> BuildSystemCharacteristics: 

554 return BuildSystemCharacteristics( 

555 out_of_source_builds="not-supported", 

556 ) 

557 

558 def configure_impl( 

559 self, 

560 context: "BuildContext", 

561 manifest: "HighLevelManifest", 

562 **kwargs, 

563 ) -> None: 

564 # No configure step 

565 pass 

566 

567 def build_impl( 

568 self, 

569 context: "BuildContext", 

570 manifest: "HighLevelManifest", 

571 **kwargs, 

572 ) -> None: 

573 extra_vars = [] 

574 build_target = self._build_target 

575 if build_target is not None: 

576 extra_vars.append(build_target) 

577 if context.is_cross_compiling: 

578 for envvar, tool in _MAKE_DEFAULT_TOOLS: 

579 cross_tool = os.environ.get(envvar) 

580 if cross_tool is None: 

581 cross_tool = context.cross_tool(tool) 

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

583 self._make_support.run_make( 

584 context, 

585 *extra_vars, 

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

587 directory=self._directory, 

588 ) 

589 

590 def test_impl( 

591 self, 

592 context: "BuildContext", 

593 manifest: "HighLevelManifest", 

594 **kwargs, 

595 ) -> None: 

596 self._run_make_maybe_explicit_target( 

597 context, 

598 self._test_target, 

599 ["test", "check"], 

600 ) 

601 

602 def install_impl( 

603 self, 

604 context: "BuildContext", 

605 manifest: "HighLevelManifest", 

606 dest_dir: str, 

607 **kwargs, 

608 ) -> None: 

609 self._run_make_maybe_explicit_target( 

610 context, 

611 self._install_target, 

612 ["install"], 

613 f"DESTDIR={dest_dir}", 

614 "AM_UPDATE_INFO_DIR=no", 

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

616 ) 

617 

618 def _run_make_maybe_explicit_target( 

619 self, 

620 context: "BuildContext", 

621 provided_target: Optional[str], 

622 fallback_targets: Sequence[str], 

623 *make_args: str, 

624 ) -> None: 

625 make_support = self._make_support 

626 if provided_target is not None: 

627 make_support.run_make( 

628 context, 

629 provided_target, 

630 *make_args, 

631 directory=self._directory, 

632 ) 

633 else: 

634 make_support.run_first_existing_target_if_any( 

635 context, 

636 fallback_targets, 

637 *make_args, 

638 directory=self._directory, 

639 ) 

640 

641 def clean_impl( 

642 self, 

643 context: "BuildContext", 

644 manifest: "HighLevelManifest", 

645 clean_helper: "CleanHelper", 

646 **kwargs, 

647 ) -> None: 

648 self._make_support.run_first_existing_target_if_any( 

649 context, 

650 ["distclean", "realclean", "clean"], 

651 ) 

652 

653 

654class PerlBuildBuildSystemRule(StepBasedBuildSystemRule): 

655 

656 __slots__ = "configure_args" 

657 

658 def __init__( 

659 self, 

660 attributes: "ParsedPerlBuildBuildRuleDefinition", 

661 attribute_path: AttributePath, 

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

663 ) -> None: 

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

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

666 

667 @classmethod 

668 def auto_detect_build_system( 

669 cls, 

670 source_root: VirtualPath, 

671 *args, 

672 **kwargs, 

673 ) -> bool: 

674 return "Build.PL" in source_root 

675 

676 @classmethod 

677 def characteristics(cls) -> BuildSystemCharacteristics: 

678 return BuildSystemCharacteristics( 

679 out_of_source_builds="not-supported", 

680 ) 

681 

682 @staticmethod 

683 def _perl_cross_build_env( 

684 context: "BuildContext", 

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

686 perl_config_data = resolve_perl_config( 

687 context.dpkg_architecture_variables, 

688 None, 

689 ) 

690 if context.is_cross_compiling: 

691 perl5lib_dir = perl_config_data.cross_inc_dir 

692 if perl5lib_dir is not None: 

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

694 if env_perl5lib is not None: 

695 perl5lib_dir = ( 

696 perl5lib_dir + perl_config_data.path_sep + env_perl5lib 

697 ) 

698 env_mod = EnvironmentModification( 

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

700 ) 

701 return perl_config_data, env_mod 

702 return perl_config_data, None 

703 

704 def configure_impl( 

705 self, 

706 context: "BuildContext", 

707 manifest: "HighLevelManifest", 

708 **kwargs, 

709 ) -> None: 

710 perl_config_data, cross_env_mod = self._perl_cross_build_env(context) 

711 configure_env = EnvironmentModification( 

712 replacements=( 

713 ("PERL_MM_USE_DEFAULT", "1"), 

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

715 ) 

716 ) 

717 if cross_env_mod is not None: 

718 configure_env = configure_env.combine(cross_env_mod) 

719 

720 configure_cmd = [ 

721 PERL_CMD, 

722 "Build.PL", 

723 "--installdirs", 

724 "vendor", 

725 ] 

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

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

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

729 

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

731 configure_cmd.append("--config") 

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

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

734 

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

736 configure_cmd.append("--config") 

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

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

739 if self.configure_args: 

740 substitution = self.substitution 

741 attr_path = self.attribute_path["configure_args"] 

742 configure_cmd.extend( 

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

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

745 ) 

746 run_build_system_command(*configure_cmd, env_mod=configure_env) 

747 

748 def build_impl( 

749 self, 

750 context: "BuildContext", 

751 manifest: "HighLevelManifest", 

752 **kwargs, 

753 ) -> None: 

754 _, cross_env_mod = self._perl_cross_build_env(context) 

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

756 

757 def test_impl( 

758 self, 

759 context: "BuildContext", 

760 manifest: "HighLevelManifest", 

761 **kwargs, 

762 ) -> None: 

763 _, cross_env_mod = self._perl_cross_build_env(context) 

764 run_build_system_command( 

765 PERL_CMD, 

766 "Build", 

767 "test", 

768 "--verbose", 

769 "1", 

770 env_mod=cross_env_mod, 

771 ) 

772 

773 def install_impl( 

774 self, 

775 context: "BuildContext", 

776 manifest: "HighLevelManifest", 

777 dest_dir: str, 

778 **kwargs, 

779 ) -> None: 

780 _, cross_env_mod = self._perl_cross_build_env(context) 

781 run_build_system_command( 

782 PERL_CMD, 

783 "Build", 

784 "install", 

785 "--destdir", 

786 dest_dir, 

787 "--create_packlist", 

788 "0", 

789 env_mod=cross_env_mod, 

790 ) 

791 

792 def clean_impl( 

793 self, 

794 context: "BuildContext", 

795 manifest: "HighLevelManifest", 

796 clean_helper: "CleanHelper", 

797 **kwargs, 

798 ) -> None: 

799 _, cross_env_mod = self._perl_cross_build_env(context) 

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

801 run_build_system_command( 

802 PERL_CMD, 

803 "Build", 

804 "realclean", 

805 "--allow_mb_mismatch", 

806 "1", 

807 env_mod=cross_env_mod, 

808 ) 

809 

810 

811class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule): 

812 

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

814 

815 def __init__( 

816 self, 

817 attributes: "ParsedPerlBuildBuildRuleDefinition", 

818 attribute_path: AttributePath, 

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

820 ) -> None: 

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

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

823 self._make_support = MakefileSupport.from_build_system(self) 

824 

825 @classmethod 

826 def auto_detect_build_system( 

827 cls, 

828 source_root: VirtualPath, 

829 *args, 

830 **kwargs, 

831 ) -> bool: 

832 return "Makefile.PL" in source_root 

833 

834 @classmethod 

835 def characteristics(cls) -> BuildSystemCharacteristics: 

836 return BuildSystemCharacteristics( 

837 out_of_source_builds="not-supported", 

838 ) 

839 

840 def configure_impl( 

841 self, 

842 context: "BuildContext", 

843 manifest: "HighLevelManifest", 

844 **kwargs, 

845 ) -> None: 

846 configure_env = EnvironmentModification( 

847 replacements=( 

848 ("PERL_MM_USE_DEFAULT", "1"), 

849 ("PERL_AUTOINSTALL", "--skipdeps"), 

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

851 ) 

852 ) 

853 perl_args = [] 

854 mm_args = ["INSTALLDIRS=vendor"] 

855 if "CFLAGS" in os.environ: 

856 mm_args.append( 

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

858 ) 

859 

860 perl_config_data = resolve_perl_config( 

861 context.dpkg_architecture_variables, 

862 None, 

863 ) 

864 

865 if "LDFLAGS" in os.environ: 

866 mm_args.append( 

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

868 ) 

869 

870 if context.is_cross_compiling: 

871 perl5lib_dir = perl_config_data.cross_inc_dir 

872 if perl5lib_dir is not None: 

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

874 

875 if self.configure_args: 

876 substitution = self.substitution 

877 attr_path = self.attribute_path["configure_args"] 

878 mm_args.extend( 

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

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

881 ) 

882 run_build_system_command( 

883 PERL_CMD, 

884 *perl_args, 

885 "Makefile.PL", 

886 *mm_args, 

887 env_mod=configure_env, 

888 ) 

889 

890 def build_impl( 

891 self, 

892 context: "BuildContext", 

893 manifest: "HighLevelManifest", 

894 **kwargs, 

895 ) -> None: 

896 self._make_support.run_make(context) 

897 

898 def test_impl( 

899 self, 

900 context: "BuildContext", 

901 manifest: "HighLevelManifest", 

902 **kwargs, 

903 ) -> None: 

904 self._make_support.run_first_existing_target_if_any( 

905 context, 

906 ["check", "test"], 

907 "TEST_VERBOSE=1", 

908 ) 

909 

910 def install_impl( 

911 self, 

912 context: "BuildContext", 

913 manifest: "HighLevelManifest", 

914 dest_dir: str, 

915 **kwargs, 

916 ) -> None: 

917 is_mm_makefile = False 

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

919 for line in fd: 

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

921 is_mm_makefile = True 

922 break 

923 

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

925 

926 # Special case for Makefile.PL that uses 

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

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

929 if is_mm_makefile: 

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

931 

932 self._make_support.run_first_existing_target_if_any( 

933 context, 

934 ["install"], 

935 *install_args, 

936 ) 

937 

938 def clean_impl( 

939 self, 

940 context: "BuildContext", 

941 manifest: "HighLevelManifest", 

942 clean_helper: "CleanHelper", 

943 **kwargs, 

944 ) -> None: 

945 self._make_support.run_first_existing_target_if_any( 

946 context, 

947 ["distclean", "realclean", "clean"], 

948 ) 

949 

950 

951class DebhelperBuildSystemRule(StepBasedBuildSystemRule): 

952 

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

954 

955 def __init__( 

956 self, 

957 parsed_data: "ParsedDebhelperBuildRuleDefinition", 

958 attribute_path: AttributePath, 

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

960 ) -> None: 

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

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

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

964 

965 @classmethod 

966 def auto_detect_build_system( 

967 cls, 

968 source_root: VirtualPath, 

969 *args, 

970 **kwargs, 

971 ) -> bool: 

972 try: 

973 v = subprocess.check_output( 

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

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

976 stderr=subprocess.DEVNULL, 

977 cwd=source_root.fs_path, 

978 ) 

979 except subprocess.CalledProcessError: 

980 return False 

981 else: 

982 d = json.loads(v) 

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

984 return build_system is not None 

985 

986 @classmethod 

987 def characteristics(cls) -> BuildSystemCharacteristics: 

988 return BuildSystemCharacteristics( 

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

990 ) 

991 

992 def before_first_impl_step( 

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

994 ) -> None: 

995 dh_build_system = self.dh_build_system 

996 if dh_build_system is None: 

997 return 

998 try: 

999 subprocess.check_call( 

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

1001 ) 

1002 except FileNotFoundError: 

1003 _error( 

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

1005 ) 

1006 except subprocess.SubprocessError: 

1007 raise ManifestInvalidUserDataException( 

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

1009 f" be available according to" 

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

1011 ) from None 

1012 

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

1014 default_options = [] 

1015 if self.dh_build_system is not None: 

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

1017 if self.build_directory is not None: 

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

1019 

1020 return default_options 

1021 

1022 def configure_impl( 

1023 self, 

1024 context: "BuildContext", 

1025 manifest: "HighLevelManifest", 

1026 **kwargs, 

1027 ) -> None: 

1028 if ( 

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

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

1031 run_build_system_command("dh_update_autotools_config") 

1032 run_build_system_command("dh_autoreconf") 

1033 

1034 default_options = self._default_options() 

1035 configure_args = default_options.copy() 

1036 if self.configure_args: 

1037 configure_args.append("--") 

1038 substitution = self.substitution 

1039 attr_path = self.attribute_path["configure_args"] 

1040 configure_args.extend( 

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

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

1043 ) 

1044 run_build_system_command("dh_auto_configure", *configure_args) 

1045 

1046 def build_impl( 

1047 self, 

1048 context: "BuildContext", 

1049 manifest: "HighLevelManifest", 

1050 **kwargs, 

1051 ) -> None: 

1052 default_options = self._default_options() 

1053 run_build_system_command("dh_auto_build", *default_options) 

1054 

1055 def test_impl( 

1056 self, 

1057 context: "BuildContext", 

1058 manifest: "HighLevelManifest", 

1059 **kwargs, 

1060 ) -> None: 

1061 default_options = self._default_options() 

1062 run_build_system_command("dh_auto_test", *default_options) 

1063 

1064 def install_impl( 

1065 self, 

1066 context: "BuildContext", 

1067 manifest: "HighLevelManifest", 

1068 dest_dir: str, 

1069 **kwargs, 

1070 ) -> None: 

1071 default_options = self._default_options() 

1072 run_build_system_command( 

1073 "dh_auto_install", 

1074 *default_options, 

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

1076 ) 

1077 

1078 def clean_impl( 

1079 self, 

1080 context: "BuildContext", 

1081 manifest: "HighLevelManifest", 

1082 clean_helper: "CleanHelper", 

1083 **kwargs, 

1084 ) -> None: 

1085 default_options = self._default_options() 

1086 run_build_system_command("dh_auto_clean", *default_options) 

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

1088 

1089 

1090class AutoconfBuildSystemRule(StepBasedBuildSystemRule): 

1091 

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

1093 

1094 def __init__( 

1095 self, 

1096 parsed_data: "ParsedAutoconfBuildRuleDefinition", 

1097 attribute_path: AttributePath, 

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

1099 ) -> None: 

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

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

1102 self.configure_args = configure_args 

1103 self._make_support = MakefileSupport.from_build_system(self) 

1104 

1105 @classmethod 

1106 def characteristics(cls) -> BuildSystemCharacteristics: 

1107 return BuildSystemCharacteristics( 

1108 out_of_source_builds="supported-and-default", 

1109 ) 

1110 

1111 @classmethod 

1112 def auto_detect_build_system( 

1113 cls, 

1114 source_root: VirtualPath, 

1115 *args, 

1116 **kwargs, 

1117 ) -> bool: 

1118 if "configure.ac" in source_root: 

1119 return True 

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

1121 if configure_in is not None and configure_in.is_file: 

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

1123 for no, line in enumerate(fd): 

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

1125 break 

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

1127 return True 

1128 configure = source_root.get("configure") 

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

1130 return False 

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

1132 for no, line in enumerate(fd): 

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

1134 break 

1135 if b"GNU Autoconf" in line: 

1136 return True 

1137 return False 

1138 

1139 def configure_impl( 

1140 self, 

1141 context: "BuildContext", 

1142 manifest: "HighLevelManifest", 

1143 **kwargs, 

1144 ) -> None: 

1145 if ( 

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

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

1148 run_build_system_command("dh_update_autotools_config") 

1149 run_build_system_command("dh_autoreconf") 

1150 

1151 dpkg_architecture_variables = context.dpkg_architecture_variables 

1152 multi_arch = dpkg_architecture_variables.current_host_multiarch 

1153 silent_rules = ( 

1154 "--enable-silent-rules" 

1155 if context.is_terse_build 

1156 else "--disable-silent-rules" 

1157 ) 

1158 

1159 configure_args = [ 

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

1161 "--prefix=/usr", 

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

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

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

1165 "--sysconfdir=/etc", 

1166 "--localstatedir=/var", 

1167 "--disable-option-checking", 

1168 silent_rules, 

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

1170 "--runstatedir=/run", 

1171 "--disable-maintainer-mode", 

1172 "--disable-dependency-tracking", 

1173 ] 

1174 if dpkg_architecture_variables.is_cross_compiling: 

1175 configure_args.append( 

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

1177 ) 

1178 if self.configure_args: 

1179 substitution = self.substitution 

1180 attr_path = self.attribute_path["configure_args"] 

1181 configure_args.extend( 

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

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

1184 ) 

1185 self.ensure_build_dir_exists() 

1186 configure_script = self.relative_from_builddir_to_source("configure") 

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

1188 run_build_system_command( 

1189 configure_script, 

1190 *configure_args, 

1191 cwd=self.build_directory, 

1192 ) 

1193 

1194 def build_impl( 

1195 self, 

1196 context: "BuildContext", 

1197 manifest: "HighLevelManifest", 

1198 **kwargs, 

1199 ) -> None: 

1200 self._make_support.run_make(context) 

1201 

1202 def test_impl( 

1203 self, 

1204 context: "BuildContext", 

1205 manifest: "HighLevelManifest", 

1206 **kwargs, 

1207 ) -> None: 

1208 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1210 

1211 if not context.is_terse_build: 

1212 testsuite_flags.append("--verbose") 

1213 self._make_support.run_first_existing_target_if_any( 

1214 context, 

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

1216 ["check", "test"], 

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

1218 "VERBOSE=1", 

1219 ) 

1220 

1221 def install_impl( 

1222 self, 

1223 context: "BuildContext", 

1224 manifest: "HighLevelManifest", 

1225 dest_dir: str, 

1226 **kwargs, 

1227 ) -> None: 

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

1229 self._make_support.run_first_existing_target_if_any( 

1230 context, 

1231 ["install"], 

1232 f"DESTDIR={dest_dir}", 

1233 "AM_UPDATE_INFO_DIR=no", 

1234 enable_parallelization=enable_parallelization, 

1235 ) 

1236 

1237 def clean_impl( 

1238 self, 

1239 context: "BuildContext", 

1240 manifest: "HighLevelManifest", 

1241 clean_helper: "CleanHelper", 

1242 **kwargs, 

1243 ) -> None: 

1244 if self.out_of_source_build: 

1245 return 

1246 self._make_support.run_first_existing_target_if_any( 

1247 context, 

1248 ["distclean", "realclean", "clean"], 

1249 ) 

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

1251 

1252 

1253class CMakeBuildSystemRule(StepBasedBuildSystemRule): 

1254 

1255 __slots__ = ( 

1256 "configure_args", 

1257 "target_build_system", 

1258 "_make_support", 

1259 "_ninja_support", 

1260 ) 

1261 

1262 def __init__( 

1263 self, 

1264 parsed_data: "ParsedCMakeBuildRuleDefinition", 

1265 attribute_path: AttributePath, 

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

1267 ) -> None: 

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

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

1270 self.configure_args = configure_args 

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

1272 "target_build_system", "make" 

1273 ) 

1274 self._make_support = MakefileSupport.from_build_system(self) 

1275 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1276 

1277 @classmethod 

1278 def characteristics(cls) -> BuildSystemCharacteristics: 

1279 return BuildSystemCharacteristics( 

1280 out_of_source_builds="required", 

1281 ) 

1282 

1283 @classmethod 

1284 def auto_detect_build_system( 

1285 cls, 

1286 source_root: VirtualPath, 

1287 *args, 

1288 **kwargs, 

1289 ) -> bool: 

1290 return "CMakeLists.txt" in source_root 

1291 

1292 @staticmethod 

1293 def _default_cmake_env( 

1294 build_context: "BuildContext", 

1295 ) -> EnvironmentModification: 

1296 replacements = {} 

1297 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1298 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1299 if "PKG_CONFIG" not in os.environ: 

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

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

1302 return EnvironmentModification( 

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

1304 ) 

1305 

1306 @classmethod 

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

1308 cmake_generators = { 

1309 "make": "Unix Makefiles", 

1310 "ninja": "Ninja", 

1311 } 

1312 return cmake_generators[target_build_system] 

1313 

1314 @staticmethod 

1315 def _compiler_and_cross_flags( 

1316 context: "BuildContext", 

1317 cmake_flags: List[str], 

1318 ) -> None: 

1319 

1320 if "CC" in os.environ: 

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

1322 elif context.is_cross_compiling: 

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

1324 

1325 if "CXX" in os.environ: 

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

1327 elif context.is_cross_compiling: 

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

1329 

1330 if context.is_cross_compiling: 

1331 deb_host2cmake_system = { 

1332 "linux": "Linux", 

1333 "kfreebsd": "kFreeBSD", 

1334 "hurd": "GNU", 

1335 } 

1336 

1337 gnu_cpu2system_processor = { 

1338 "arm": "armv7l", 

1339 "misp64el": "mips64", 

1340 "powerpc64le": "ppc64le", 

1341 } 

1342 dpkg_architecture_variables = context.dpkg_architecture_variables 

1343 

1344 try: 

1345 system_name = deb_host2cmake_system[ 

1346 dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1347 ] 

1348 except KeyError as e: 

1349 name = e.args[0] 

1350 _error( 

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

1352 ) 

1353 

1354 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"] 

1355 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu) 

1356 

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

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

1359 

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

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

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

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

1364 cmake_flags.append( 

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

1366 ) 

1367 

1368 def configure_impl( 

1369 self, 

1370 context: "BuildContext", 

1371 manifest: "HighLevelManifest", 

1372 **kwargs, 

1373 ) -> None: 

1374 cmake_flags = [ 

1375 "-DCMAKE_INSTALL_PREFIX=/usr", 

1376 "-DCMAKE_BUILD_TYPE=None", 

1377 "-DCMAKE_INSTALL_SYSCONFDIR=/etc", 

1378 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var", 

1379 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON", 

1380 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", 

1381 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON", 

1382 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON", 

1383 "-DCMAKE_INSTALL_RUNSTATEDIR=/run", 

1384 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON", 

1385 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON", 

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

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

1388 ] 

1389 if not context.is_terse_build: 

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

1391 if not context.should_run_tests: 

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

1393 

1394 self._compiler_and_cross_flags(context, cmake_flags) 

1395 

1396 if self.configure_args: 

1397 substitution = self.substitution 

1398 attr_path = self.attribute_path["configure_args"] 

1399 cmake_flags.extend( 

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

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

1402 ) 

1403 

1404 env_mod = self._default_cmake_env(context) 

1405 if "CPPFLAGS" in os.environ: 

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

1407 cppflags = os.environ["CPPFLAGS"] 

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

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

1410 env_mod = env_mod.combine( 

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

1412 EnvironmentModification( 

1413 replacements=( 

1414 ("CFLAGS", cflags), 

1415 ("CXXFLAGS", cxxflags), 

1416 ) 

1417 ) 

1418 ) 

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

1420 env_mod = env_mod.combine( 

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

1422 EnvironmentModification( 

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

1424 ) 

1425 ) 

1426 self.ensure_build_dir_exists() 

1427 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1428 

1429 with self.dump_logs_on_error( 

1430 "CMakeCache.txt", 

1431 "CMakeFiles/CMakeOutput.log", 

1432 "CMakeFiles/CMakeError.log", 

1433 ): 

1434 run_build_system_command( 

1435 "cmake", 

1436 *cmake_flags, 

1437 source_dir_from_build_dir, 

1438 cwd=self.build_directory, 

1439 env_mod=env_mod, 

1440 ) 

1441 

1442 def build_impl( 

1443 self, 

1444 context: "BuildContext", 

1445 manifest: "HighLevelManifest", 

1446 **kwargs, 

1447 ) -> None: 

1448 if self.target_build_system == "make": 

1449 make_flags = [] 

1450 if not context.is_terse_build: 

1451 make_flags.append("VERBOSE=1") 

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

1453 else: 

1454 self._ninja_support.run_ninja_build(context) 

1455 

1456 def test_impl( 

1457 self, 

1458 context: "BuildContext", 

1459 manifest: "HighLevelManifest", 

1460 **kwargs, 

1461 ) -> None: 

1462 env_mod = EnvironmentModification( 

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

1464 ) 

1465 if self.target_build_system == "make": 

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

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

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

1469 if not context.is_terse_build: 

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

1471 self._make_support.run_first_existing_target_if_any( 

1472 context, 

1473 ["check", "test"], 

1474 *make_flags, 

1475 env_mod=env_mod, 

1476 ) 

1477 else: 

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

1479 

1480 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1482 

1483 if not context.is_terse_build: 

1484 testsuite_flags.append("--verbose") 

1485 self._make_support.run_first_existing_target_if_any( 

1486 context, 

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

1488 ["check", "test"], 

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

1490 "VERBOSE=1", 

1491 ) 

1492 

1493 def install_impl( 

1494 self, 

1495 context: "BuildContext", 

1496 manifest: "HighLevelManifest", 

1497 dest_dir: str, 

1498 **kwargs, 

1499 ) -> None: 

1500 env_mod = EnvironmentModification( 

1501 replacements=( 

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

1503 ("DESTDIR", dest_dir), 

1504 ) 

1505 ).combine(self._default_cmake_env(context)) 

1506 run_build_system_command( 

1507 "cmake", 

1508 "--install", 

1509 self.build_directory, 

1510 env_mod=env_mod, 

1511 ) 

1512 

1513 def clean_impl( 

1514 self, 

1515 context: "BuildContext", 

1516 manifest: "HighLevelManifest", 

1517 clean_helper: "CleanHelper", 

1518 **kwargs, 

1519 ) -> None: 

1520 if self.out_of_source_build: 

1521 return 

1522 if self.target_build_system == "make": 

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

1524 self._make_support.run_first_existing_target_if_any( 

1525 context, 

1526 ["distclean", "realclean", "clean"], 

1527 ) 

1528 else: 

1529 self._ninja_support.run_ninja_clean(context) 

1530 

1531 

1532class MesonBuildSystemRule(StepBasedBuildSystemRule): 

1533 

1534 __slots__ = ( 

1535 "configure_args", 

1536 "_ninja_support", 

1537 ) 

1538 

1539 def __init__( 

1540 self, 

1541 parsed_data: "ParsedMesonBuildRuleDefinition", 

1542 attribute_path: AttributePath, 

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

1544 ) -> None: 

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

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

1547 self.configure_args = configure_args 

1548 self._ninja_support = NinjaBuildSupport.from_build_system(self) 

1549 

1550 @classmethod 

1551 def characteristics(cls) -> BuildSystemCharacteristics: 

1552 return BuildSystemCharacteristics( 

1553 out_of_source_builds="required", 

1554 ) 

1555 

1556 @classmethod 

1557 def auto_detect_build_system( 

1558 cls, 

1559 source_root: VirtualPath, 

1560 *args, 

1561 **kwargs, 

1562 ) -> bool: 

1563 return "meson.build" in source_root 

1564 

1565 @staticmethod 

1566 def _default_meson_env() -> EnvironmentModification: 

1567 replacements = { 

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

1569 } 

1570 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ: 

1571 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb" 

1572 return EnvironmentModification( 

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

1574 ) 

1575 

1576 @classmethod 

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

1578 cmake_generators = { 

1579 "make": "Unix Makefiles", 

1580 "ninja": "Ninja", 

1581 } 

1582 return cmake_generators[target_build_system] 

1583 

1584 @staticmethod 

1585 def _cross_flags( 

1586 context: "BuildContext", 

1587 meson_flags: List[str], 

1588 ) -> None: 

1589 if not context.is_cross_compiling: 

1590 return 

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

1592 cross_files_dir = os.path.abspath( 

1593 generated_content_dir( 

1594 subdir_key="meson-cross-files", 

1595 ) 

1596 ) 

1597 host_arch = context.dpkg_architecture_variables.current_host_arch 

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

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

1600 env = os.environ 

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

1602 env = dict(env) 

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

1604 else: 

1605 env = None 

1606 subprocess.check_call( 

1607 [ 

1608 "/usr/share/meson/debcrossgen", 

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

1610 f"-o{cross_file}", 

1611 ], 

1612 stdout=subprocess.DEVNULL, 

1613 env=env, 

1614 ) 

1615 

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

1617 meson_flags.append(cross_file) 

1618 

1619 def configure_impl( 

1620 self, 

1621 context: "BuildContext", 

1622 manifest: "HighLevelManifest", 

1623 **kwargs, 

1624 ) -> None: 

1625 meson_version = Version( 

1626 subprocess.check_output( 

1627 ["meson", "--version"], 

1628 encoding="utf-8", 

1629 ).strip() 

1630 ) 

1631 dpkg_architecture_variables = context.dpkg_architecture_variables 

1632 

1633 meson_flags = [ 

1634 "--wrap-mode=nodownload", 

1635 "--buildtype=plain", 

1636 "--prefix=/usr", 

1637 "--sysconfdir=/etc", 

1638 "--localstatedir=/var", 

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

1640 "--auto-features=enabled", 

1641 ] 

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

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

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

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

1646 # where the option exists. 

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

1648 

1649 self._cross_flags(context, meson_flags) 

1650 

1651 if self.configure_args: 

1652 substitution = self.substitution 

1653 attr_path = self.attribute_path["configure_args"] 

1654 meson_flags.extend( 

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

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

1657 ) 

1658 

1659 env_mod = self._default_meson_env() 

1660 

1661 self.ensure_build_dir_exists() 

1662 source_dir_from_build_dir = self.relative_from_builddir_to_source() 

1663 

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

1665 run_build_system_command( 

1666 "meson", 

1667 "setup", 

1668 source_dir_from_build_dir, 

1669 *meson_flags, 

1670 cwd=self.build_directory, 

1671 env_mod=env_mod, 

1672 ) 

1673 

1674 def build_impl( 

1675 self, 

1676 context: "BuildContext", 

1677 manifest: "HighLevelManifest", 

1678 **kwargs, 

1679 ) -> None: 

1680 self._ninja_support.run_ninja_build(context) 

1681 

1682 def test_impl( 

1683 self, 

1684 context: "BuildContext", 

1685 manifest: "HighLevelManifest", 

1686 **kwargs, 

1687 ) -> None: 

1688 env_mod = EnvironmentModification( 

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

1690 ).combine(self._default_meson_env()) 

1691 meson_args = [] 

1692 if not context.is_terse_build: 

1693 meson_args.append("--verbose") 

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

1695 run_build_system_command( 

1696 "meson", 

1697 "test", 

1698 *meson_args, 

1699 env_mod=env_mod, 

1700 cwd=self.build_directory, 

1701 ) 

1702 

1703 def install_impl( 

1704 self, 

1705 context: "BuildContext", 

1706 manifest: "HighLevelManifest", 

1707 dest_dir: str, 

1708 **kwargs, 

1709 ) -> None: 

1710 run_build_system_command( 

1711 "meson", 

1712 "install", 

1713 "--destdir", 

1714 dest_dir, 

1715 cwd=self.build_directory, 

1716 env_mod=self._default_meson_env(), 

1717 ) 

1718 

1719 def clean_impl( 

1720 self, 

1721 context: "BuildContext", 

1722 manifest: "HighLevelManifest", 

1723 clean_helper: "CleanHelper", 

1724 **kwargs, 

1725 ) -> None: 

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

1727 assert self.out_of_source_build 

1728 

1729 

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

1731 value = os.environ.get(envvar) 

1732 if value is None: 

1733 return 

1734 if include_cppflags: 

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

1736 if cppflags: 

1737 value = f"{value} {cppflags}" 

1738 

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

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

1741 

1742 

1743class ParsedGenericQmakeBuildRuleDefinition( 

1744 OptionalInstallDirectly, 

1745 OptionalInSourceBuild, 

1746 OptionalBuildDirectory, 

1747): 

1748 configure_args: NotRequired[List[str]] 

1749 

1750 

1751class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule): 

1752 

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

1754 

1755 def __init__( 

1756 self, 

1757 parsed_data: "ParsedGenericQmakeBuildRuleDefinition", 

1758 attribute_path: AttributePath, 

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

1760 ) -> None: 

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

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

1763 self.configure_args = configure_args 

1764 self._make_support = MakefileSupport.from_build_system(self) 

1765 

1766 @classmethod 

1767 def characteristics(cls) -> BuildSystemCharacteristics: 

1768 return BuildSystemCharacteristics( 

1769 out_of_source_builds="supported-and-default", 

1770 ) 

1771 

1772 @classmethod 

1773 def auto_detect_build_system( 

1774 cls, 

1775 source_root: VirtualPath, 

1776 *args, 

1777 **kwargs, 

1778 ) -> bool: 

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

1780 

1781 @classmethod 

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

1783 return { 

1784 "linux": "linux-g++", 

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

1786 "hurd": "hurd-g++", 

1787 } 

1788 

1789 def qmake_command(self) -> str: 

1790 raise NotImplementedError 

1791 

1792 def configure_impl( 

1793 self, 

1794 context: "BuildContext", 

1795 manifest: "HighLevelManifest", 

1796 **kwargs, 

1797 ) -> None: 

1798 

1799 configure_args = [ 

1800 "-makefile", 

1801 ] 

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

1803 

1804 if context.is_cross_compiling: 

1805 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"] 

1806 os2mkspec = self.os_mkspec_mapping() 

1807 try: 

1808 spec = os2mkspec[host_os] 

1809 except KeyError: 

1810 _error( 

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

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

1813 ) 

1814 configure_args.append("-spec") 

1815 configure_args.append(spec) 

1816 

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

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

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

1820 

1821 configure_args.append("QMAKE_STRIP=:") 

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

1823 

1824 if self.configure_args: 

1825 substitution = self.substitution 

1826 attr_path = self.attribute_path["configure_args"] 

1827 configure_args.extend( 

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

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

1830 ) 

1831 

1832 self.ensure_build_dir_exists() 

1833 if not self.out_of_source_build: 

1834 configure_args.append(self.relative_from_builddir_to_source()) 

1835 

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

1837 run_build_system_command( 

1838 qmake_cmd, 

1839 *configure_args, 

1840 cwd=self.build_directory, 

1841 ) 

1842 

1843 def build_impl( 

1844 self, 

1845 context: "BuildContext", 

1846 manifest: "HighLevelManifest", 

1847 **kwargs, 

1848 ) -> None: 

1849 self._make_support.run_make(context) 

1850 

1851 def test_impl( 

1852 self, 

1853 context: "BuildContext", 

1854 manifest: "HighLevelManifest", 

1855 **kwargs, 

1856 ) -> None: 

1857 limit = context.parallelization_limit(support_zero_as_unlimited=True) 

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

1859 

1860 if not context.is_terse_build: 

1861 testsuite_flags.append("--verbose") 

1862 self._make_support.run_first_existing_target_if_any( 

1863 context, 

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

1865 ["check", "test"], 

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

1867 "VERBOSE=1", 

1868 ) 

1869 

1870 def install_impl( 

1871 self, 

1872 context: "BuildContext", 

1873 manifest: "HighLevelManifest", 

1874 dest_dir: str, 

1875 **kwargs, 

1876 ) -> None: 

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

1878 self._make_support.run_first_existing_target_if_any( 

1879 context, 

1880 ["install"], 

1881 f"DESTDIR={dest_dir}", 

1882 "AM_UPDATE_INFO_DIR=no", 

1883 enable_parallelization=enable_parallelization, 

1884 ) 

1885 

1886 def clean_impl( 

1887 self, 

1888 context: "BuildContext", 

1889 manifest: "HighLevelManifest", 

1890 clean_helper: "CleanHelper", 

1891 **kwargs, 

1892 ) -> None: 

1893 if self.out_of_source_build: 

1894 return 

1895 self._make_support.run_first_existing_target_if_any( 

1896 context, 

1897 ["distclean", "realclean", "clean"], 

1898 ) 

1899 

1900 

1901class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule): 

1902 

1903 def qmake_command(self) -> str: 

1904 return "qmake" 

1905 

1906 

1907class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule): 

1908 

1909 def qmake_command(self) -> str: 

1910 return "qmake6" 

1911 

1912 

1913@debputy_build_system( 

1914 "make", 

1915 MakefileBuildSystemRule, 

1916 auto_detection_shadows_build_systems="debhelper", 

1917 online_reference_documentation=reference_documentation( 

1918 title="Make Build System", 

1919 description=textwrap.dedent( 

1920 """\ 

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

1922 

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

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

1925 

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

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

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

1929 some other directory. 

1930 """ 

1931 ), 

1932 attributes=[ 

1933 documented_attr( 

1934 "directory", 

1935 textwrap.dedent( 

1936 """\ 

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

1938 

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

1940 """ 

1941 ), 

1942 ), 

1943 documented_attr( 

1944 "build_target", 

1945 textwrap.dedent( 

1946 """\ 

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

1948 

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

1950 the default. 

1951 """ 

1952 ), 

1953 ), 

1954 documented_attr( 

1955 "test_target", 

1956 textwrap.dedent( 

1957 """\ 

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

1959 

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

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

1962 """ 

1963 ), 

1964 ), 

1965 documented_attr( 

1966 "install_target", 

1967 textwrap.dedent( 

1968 """\ 

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

1970 

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

1972 Otherwise, the step will be skipped. 

1973 """ 

1974 ), 

1975 ), 

1976 *docs_from( 

1977 DebputyParsedContentStandardConditional, 

1978 OptionalInstallDirectly, 

1979 BuildRuleParsedFormat, 

1980 ), 

1981 ], 

1982 ), 

1983) 

1984class ParsedMakeBuildRuleDefinition( 

1985 OptionalInstallDirectly, 

1986): 

1987 directory: NotRequired[FileSystemExactMatchRule] 

1988 build_target: NotRequired[str] 

1989 test_target: NotRequired[str] 

1990 install_target: NotRequired[str] 

1991 

1992 

1993@debputy_build_system( 

1994 "autoconf", 

1995 AutoconfBuildSystemRule, 

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

1997 online_reference_documentation=reference_documentation( 

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

1999 description=textwrap.dedent( 

2000 """\ 

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

2002 

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

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

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

2006 

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

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

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

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

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

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

2013 """ 

2014 ), 

2015 attributes=[ 

2016 documented_attr( 

2017 "configure_args", 

2018 textwrap.dedent( 

2019 """\ 

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

2021 """ 

2022 ), 

2023 ), 

2024 *docs_from( 

2025 DebputyParsedContentStandardConditional, 

2026 OptionalInstallDirectly, 

2027 OptionalInSourceBuild, 

2028 OptionalBuildDirectory, 

2029 BuildRuleParsedFormat, 

2030 ), 

2031 ], 

2032 ), 

2033) 

2034class ParsedAutoconfBuildRuleDefinition( 

2035 OptionalInstallDirectly, 

2036 OptionalInSourceBuild, 

2037 OptionalBuildDirectory, 

2038): 

2039 configure_args: NotRequired[List[str]] 

2040 

2041 

2042@debputy_build_system( 

2043 "cmake", 

2044 CMakeBuildSystemRule, 

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

2046 online_reference_documentation=reference_documentation( 

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

2048 description=textwrap.dedent( 

2049 """\ 

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

2051 

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

2053 """ 

2054 ), 

2055 attributes=[ 

2056 documented_attr( 

2057 "configure_args", 

2058 textwrap.dedent( 

2059 """\ 

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

2061 """ 

2062 ), 

2063 ), 

2064 documented_attr( 

2065 "target_build_system", 

2066 textwrap.dedent( 

2067 """\ 

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

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

2070 

2071 Supported options are: 

2072 * `make` - GNU Make 

2073 * `ninja` - Ninja 

2074 """ 

2075 ), 

2076 ), 

2077 *docs_from( 

2078 DebputyParsedContentStandardConditional, 

2079 OptionalInstallDirectly, 

2080 OptionalBuildDirectory, 

2081 BuildRuleParsedFormat, 

2082 ), 

2083 ], 

2084 ), 

2085) 

2086class ParsedCMakeBuildRuleDefinition( 

2087 OptionalInstallDirectly, 

2088 OptionalBuildDirectory, 

2089): 

2090 configure_args: NotRequired[List[str]] 

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

2092 

2093 

2094@debputy_build_system( 

2095 "meson", 

2096 MesonBuildSystemRule, 

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

2098 online_reference_documentation=reference_documentation( 

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

2100 description=textwrap.dedent( 

2101 """\ 

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

2103 

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

2105 """ 

2106 ), 

2107 attributes=[ 

2108 documented_attr( 

2109 "configure_args", 

2110 textwrap.dedent( 

2111 """\ 

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

2113 """ 

2114 ), 

2115 ), 

2116 *docs_from( 

2117 DebputyParsedContentStandardConditional, 

2118 OptionalInstallDirectly, 

2119 OptionalBuildDirectory, 

2120 BuildRuleParsedFormat, 

2121 ), 

2122 ], 

2123 ), 

2124) 

2125class ParsedMesonBuildRuleDefinition( 

2126 OptionalInstallDirectly, 

2127 OptionalBuildDirectory, 

2128): 

2129 configure_args: NotRequired[List[str]] 

2130 

2131 

2132@debputy_build_system( 

2133 "perl-build", 

2134 PerlBuildBuildSystemRule, 

2135 auto_detection_shadows_build_systems=[ 

2136 "debhelper", 

2137 "make", 

2138 "perl-makemaker", 

2139 ], 

2140 online_reference_documentation=reference_documentation( 

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

2142 description=textwrap.dedent( 

2143 """\ 

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

2145 

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

2147 upstream code. 

2148 """ 

2149 ), 

2150 attributes=[ 

2151 documented_attr( 

2152 "configure_args", 

2153 textwrap.dedent( 

2154 """\ 

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

2156 """ 

2157 ), 

2158 ), 

2159 *docs_from( 

2160 DebputyParsedContentStandardConditional, 

2161 OptionalInstallDirectly, 

2162 BuildRuleParsedFormat, 

2163 ), 

2164 ], 

2165 ), 

2166) 

2167class ParsedPerlBuildBuildRuleDefinition( 

2168 OptionalInstallDirectly, 

2169): 

2170 configure_args: NotRequired[List[str]] 

2171 

2172 

2173@debputy_build_system( 

2174 "debhelper", 

2175 DebhelperBuildSystemRule, 

2176 online_reference_documentation=reference_documentation( 

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

2178 description=textwrap.dedent( 

2179 """\ 

2180 Delegate to a debhelper provided build system 

2181 

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

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

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

2185 `dh-build-system` attribute. 

2186 """ 

2187 ), 

2188 attributes=[ 

2189 documented_attr( 

2190 "dh_build_system", 

2191 textwrap.dedent( 

2192 """\ 

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

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

2195 for that will be accepted. 

2196 

2197 Note that many debhelper build systems require extra build 

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

2199 of the relevant debhelper build system for details. 

2200 """ 

2201 ), 

2202 ), 

2203 documented_attr( 

2204 "configure_args", 

2205 textwrap.dedent( 

2206 """\ 

2207 Arguments to be passed to underlying configuration command 

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

2209 """ 

2210 ), 

2211 ), 

2212 *docs_from( 

2213 DebputyParsedContentStandardConditional, 

2214 OptionalInstallDirectly, 

2215 OptionalBuildDirectory, 

2216 BuildRuleParsedFormat, 

2217 ), 

2218 ], 

2219 ), 

2220) 

2221class ParsedDebhelperBuildRuleDefinition( 

2222 OptionalInstallDirectly, 

2223 OptionalBuildDirectory, 

2224): 

2225 configure_args: NotRequired[List[str]] 

2226 dh_build_system: NotRequired[str] 

2227 

2228 

2229@debputy_build_system( 

2230 "perl-makemaker", 

2231 PerlMakeMakerBuildSystemRule, 

2232 auto_detection_shadows_build_systems=[ 

2233 "debhelper", 

2234 "make", 

2235 ], 

2236 online_reference_documentation=reference_documentation( 

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

2238 description=textwrap.dedent( 

2239 """\ 

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

2241 

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

2243 upstream code. 

2244 """ 

2245 ), 

2246 attributes=[ 

2247 documented_attr( 

2248 "configure_args", 

2249 textwrap.dedent( 

2250 """\ 

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

2252 """ 

2253 ), 

2254 ), 

2255 *docs_from( 

2256 DebputyParsedContentStandardConditional, 

2257 OptionalInstallDirectly, 

2258 BuildRuleParsedFormat, 

2259 ), 

2260 ], 

2261 ), 

2262) 

2263class ParsedPerlMakeMakerBuildRuleDefinition( 

2264 OptionalInstallDirectly, 

2265): 

2266 configure_args: NotRequired[List[str]] 

2267 

2268 

2269@debputy_build_system( 

2270 "qmake", 

2271 QmakeBuildSystemRule, 

2272 auto_detection_shadows_build_systems=[ 

2273 "debhelper", 

2274 "make", 

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

2276 ], 

2277 online_reference_documentation=reference_documentation( 

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

2279 description=textwrap.dedent( 

2280 """\ 

2281 Build using the "qmake" by QT. 

2282 """ 

2283 ), 

2284 attributes=[ 

2285 documented_attr( 

2286 "configure_args", 

2287 textwrap.dedent( 

2288 """\ 

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

2290 """ 

2291 ), 

2292 ), 

2293 *docs_from( 

2294 DebputyParsedContentStandardConditional, 

2295 OptionalInstallDirectly, 

2296 OptionalInSourceBuild, 

2297 OptionalBuildDirectory, 

2298 BuildRuleParsedFormat, 

2299 ), 

2300 ], 

2301 ), 

2302) 

2303class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2304 pass 

2305 

2306 

2307@debputy_build_system( 

2308 "qmake6", 

2309 Qmake6BuildSystemRule, 

2310 auto_detection_shadows_build_systems=[ 

2311 "debhelper", 

2312 "make", 

2313 ], 

2314 online_reference_documentation=reference_documentation( 

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

2316 description=textwrap.dedent( 

2317 """\ 

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

2319 but is specifically for QT6. 

2320 """ 

2321 ), 

2322 attributes=[ 

2323 documented_attr( 

2324 "configure_args", 

2325 textwrap.dedent( 

2326 """\ 

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

2328 """ 

2329 ), 

2330 ), 

2331 *docs_from( 

2332 DebputyParsedContentStandardConditional, 

2333 OptionalInstallDirectly, 

2334 OptionalInSourceBuild, 

2335 OptionalBuildDirectory, 

2336 BuildRuleParsedFormat, 

2337 ), 

2338 ], 

2339 ), 

2340) 

2341class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition): 

2342 pass 

2343 

2344 

2345def _parse_default_environment( 

2346 _name: str, 

2347 parsed_data: EnvironmentSourceFormat, 

2348 attribute_path: AttributePath, 

2349 parser_context: ParserContextData, 

2350) -> ManifestProvidedBuildEnvironment: 

2351 return ManifestProvidedBuildEnvironment.from_environment_definition( 

2352 parsed_data, 

2353 attribute_path, 

2354 parser_context, 

2355 is_default=True, 

2356 ) 

2357 

2358 

2359def _parse_build_environments( 

2360 _name: str, 

2361 parsed_data: List[NamedEnvironmentSourceFormat], 

2362 attribute_path: AttributePath, 

2363 parser_context: ParserContextData, 

2364) -> List[ManifestProvidedBuildEnvironment]: 

2365 return [ 

2366 ManifestProvidedBuildEnvironment.from_environment_definition( 

2367 value, 

2368 attribute_path[idx], 

2369 parser_context, 

2370 is_default=False, 

2371 ) 

2372 for idx, value in enumerate(parsed_data) 

2373 ] 

2374 

2375 

2376def _handle_build_rules( 

2377 _name: str, 

2378 parsed_data: List[BuildRule], 

2379 _attribute_path: AttributePath, 

2380 _parser_context: ParserContextData, 

2381) -> List[BuildRule]: 

2382 return parsed_data