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
« 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)
26from debian.debian_support import Version
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)
81if TYPE_CHECKING:
82 from debputy.build_support.build_context import BuildContext
83 from debputy.highlevel_manifest import HighLevelManifest
86PERL_CMD = "perl"
89class Conditional(DebputyParsedContent):
90 when: Required[ManifestCondition]
93def register_build_system_rules(api: DebputyPluginInitializerProvider) -> None:
94 register_build_keywords(api)
95 register_build_rules(api)
98def register_build_keywords(api: DebputyPluginInitializerProvider) -> None:
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.
113 The environment definitions can be used to tweak the environment variables used by the
114 build commands. An example:
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
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.
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).
135 Accordingly, both `override` and `unset` will overrule any computed variables while
136 `set` will be overruled by any computed variables.
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
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.
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.
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.
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.
207 The environment definition can be used to tweak the environment variables used by the
208 build commands. An example:
210 default-build-environment:
211 set:
212 ENV_VAR: foo
213 ANOTHER_ENV_VAR: bar
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.
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).
225 Accordingly, both `override` and `unset` will overrule any computed variables while
226 `set` will be overruled by any computed variables.
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.
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.
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.
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.
287 A simple example is:
289 ```yaml
290 builds:
291 - autoconf:
292 configure-args:
293 - "--enable-foo"
294 - "--without=bar"
295 ```
297 Multiple builds are supported out of box. Here an example of how the build rules
298 for `libpam-krb5` might look.
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 )
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.
340 Example:
342 ```yaml
343 ...:
344 # The tests require internet access and cannot be run at build time.
345 test-rule: skip-tests
346 ```
348 """
349 ),
350 ),
351 )
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.
370 Note if you want to only run the tests when the conditional evaluates to `true`,
371 then wrap the conditional with `not:`.
373 Example:
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 ```
384 """
385 ),
386 ),
387 )
390def register_build_rules(api: DebputyPluginInitializerProvider) -> None:
391 api.register_build_system(ParsedAutoconfBuildRuleDefinition)
392 api.register_build_system(ParsedMakeBuildRuleDefinition)
394 api.register_build_system(ParsedPerlBuildBuildRuleDefinition)
395 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition)
396 api.register_build_system(ParsedDebhelperBuildRuleDefinition)
398 api.register_build_system(ParsedCMakeBuildRuleDefinition)
399 api.register_build_system(ParsedMesonBuildRuleDefinition)
401 api.register_build_system(ParsedQmakeBuildRuleDefinition)
402 api.register_build_system(ParsedQmake6BuildRuleDefinition)
405class EnvironmentSourceFormat(TypedDict):
406 set: NotRequired[Dict[str, str]]
407 override: NotRequired[Dict[str, str]]
408 unset: NotRequired[List[str]]
411class NamedEnvironmentSourceFormat(EnvironmentSourceFormat):
412 name: str
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}
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 )
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 )
481@dataclasses.dataclass(slots=True, frozen=True)
482class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition):
484 name: str
485 is_default: bool
486 attribute_path: AttributePath
487 parser_context: ParserContextData
489 set_vars: Mapping[str, str]
490 override_vars: Mapping[str, str]
491 unset_vars: Sequence[str]
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
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"])
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 )
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 )
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 )
564 return r
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
591_MAKE_DEFAULT_TOOLS = [
592 ("CC", "gcc"),
593 ("CXX", "g++"),
594 ("PKG_CONFIG", "pkg-config"),
595]
598class MakefileBuildSystemRule(StepBasedBuildSystemRule):
600 __slots__ = ("_make_support", "_build_target", "_install_target", "_directory")
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")
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"))
628 @classmethod
629 def characteristics(cls) -> BuildSystemCharacteristics:
630 return BuildSystemCharacteristics(
631 out_of_source_builds="not-supported",
632 )
634 def configure_impl(
635 self,
636 context: "BuildContext",
637 manifest: "HighLevelManifest",
638 **kwargs,
639 ) -> None:
640 # No configure step
641 pass
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 )
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 )
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 )
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 )
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 )
732class PerlBuildBuildSystemRule(StepBasedBuildSystemRule):
734 __slots__ = "configure_args"
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", [])
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
754 @classmethod
755 def characteristics(cls) -> BuildSystemCharacteristics:
756 return BuildSystemCharacteristics(
757 out_of_source_builds="not-supported",
758 )
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
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)
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", "")
808 if cflags != "" or cppflags != "":
809 configure_cmd.append("--config")
810 combined = f"{cflags} {cppflags}".strip()
811 configure_cmd.append(f"optimize={combined}")
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)
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)
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 )
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 )
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 )
891class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule):
893 __slots__ = ("configure_args", "_make_support")
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)
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
914 @classmethod
915 def characteristics(cls) -> BuildSystemCharacteristics:
916 return BuildSystemCharacteristics(
917 out_of_source_builds="not-supported",
918 )
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 )
940 perl_config_data = resolve_perl_config(
941 context.dpkg_architecture_variables,
942 None,
943 )
945 if "LDFLAGS" in os.environ:
946 mm_args.append(
947 f"LD={perl_config_data.ld} {os.environ['CFLAGS']} {os.environ['LDFLAGS']}"
948 )
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}")
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 )
970 def build_impl(
971 self,
972 context: "BuildContext",
973 manifest: "HighLevelManifest",
974 **kwargs,
975 ) -> None:
976 self._make_support.run_make(context)
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 )
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
1006 install_args = [f"DESTDIR={dest_dir}"]
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")
1014 self._make_support.run_first_existing_target_if_any(
1015 context,
1016 ["install"],
1017 *install_args,
1018 )
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 )
1033class DebhelperBuildSystemRule(StepBasedBuildSystemRule):
1035 __slots__ = ("configure_args", "dh_build_system")
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")
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"
1068 @classmethod
1069 def characteristics(cls) -> BuildSystemCharacteristics:
1070 return BuildSystemCharacteristics(
1071 out_of_source_builds="supported-but-not-default",
1072 )
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
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}")
1102 return default_options
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")
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)
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)
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)
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 )
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`
1174class AutoconfBuildSystemRule(StepBasedBuildSystemRule):
1176 __slots__ = ("configure_args", "_make_support")
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)
1189 @classmethod
1190 def characteristics(cls) -> BuildSystemCharacteristics:
1191 return BuildSystemCharacteristics(
1192 out_of_source_builds="supported-and-default",
1193 )
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
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")
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 )
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 )
1278 def build_impl(
1279 self,
1280 context: "BuildContext",
1281 manifest: "HighLevelManifest",
1282 **kwargs,
1283 ) -> None:
1284 self._make_support.run_make(context)
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"]
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 )
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 )
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`
1339class CMakeBuildSystemRule(StepBasedBuildSystemRule):
1341 __slots__ = (
1342 "configure_args",
1343 "target_build_system",
1344 "_make_support",
1345 "_ninja_support",
1346 )
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)
1363 @classmethod
1364 def characteristics(cls) -> BuildSystemCharacteristics:
1365 return BuildSystemCharacteristics(
1366 out_of_source_builds="required",
1367 )
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
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 )
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]
1400 @staticmethod
1401 def _compiler_and_cross_flags(
1402 context: "BuildContext",
1403 cmake_flags: List[str],
1404 ) -> None:
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')}")
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++')}")
1416 if context.is_cross_compiling:
1417 deb_host2cmake_system = {
1418 "linux": "Linux",
1419 "kfreebsd": "kFreeBSD",
1420 "hurd": "GNU",
1421 }
1423 gnu_cpu2system_processor = {
1424 "arm": "armv7l",
1425 "misp64el": "mips64",
1426 "powerpc64le": "ppc64le",
1427 }
1428 dpkg_architecture_variables = context.dpkg_architecture_variables
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 )
1440 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"]
1441 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu)
1443 cmake_flags.append(f"-DCMAKE_SYSTEM_NAME={system_name}")
1444 cmake_flags.append(f"-DCMAKE_SYSTEM_PROCESSOR={system_processor}")
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 )
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")
1480 self._compiler_and_cross_flags(context, cmake_flags)
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 )
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()
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 )
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)
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)
1568 limit = context.parallelization_limit(support_zero_as_unlimited=True)
1569 testsuite_flags = [f"-j{limit}"] if limit else ["-j"]
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 )
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 )
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)
1620class MesonBuildSystemRule(StepBasedBuildSystemRule):
1622 __slots__ = (
1623 "configure_args",
1624 "_ninja_support",
1625 )
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)
1638 @classmethod
1639 def characteristics(cls) -> BuildSystemCharacteristics:
1640 return BuildSystemCharacteristics(
1641 out_of_source_builds="required",
1642 )
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
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 )
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]
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 )
1704 meson_flags.append("--cross-file")
1705 meson_flags.append(cross_file)
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
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")
1737 self._cross_flags(context, meson_flags)
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 )
1747 env_mod = self._default_meson_env()
1749 self.ensure_build_dir_exists()
1750 source_dir_from_build_dir = self.relative_from_builddir_to_source()
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 )
1762 def build_impl(
1763 self,
1764 context: "BuildContext",
1765 manifest: "HighLevelManifest",
1766 **kwargs,
1767 ) -> None:
1768 self._ninja_support.run_ninja_build(context)
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 )
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 )
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
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}"
1829 options.append(f"QMAKE_{envvar}_RELEASE={value}")
1830 options.append(f"QMAKE_{envvar}_DEBUG={value}")
1833class ParsedGenericQmakeBuildRuleDefinition(
1834 OptionalInstallDirectly,
1835 OptionalInSourceBuild,
1836 OptionalBuildDirectory,
1837 OptionalTestRule,
1838):
1839 configure_args: NotRequired[List[str]]
1842class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule):
1844 __slots__ = ("configure_args", "_make_support")
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)
1857 @classmethod
1858 def characteristics(cls) -> BuildSystemCharacteristics:
1859 return BuildSystemCharacteristics(
1860 out_of_source_builds="supported-and-default",
1861 )
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)
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 }
1880 def qmake_command(self) -> str:
1881 raise NotImplementedError
1883 def configure_impl(
1884 self,
1885 context: "BuildContext",
1886 manifest: "HighLevelManifest",
1887 **kwargs,
1888 ) -> None:
1890 configure_args = [
1891 "-makefile",
1892 ]
1893 qmake_cmd = context.cross_tool(self.qmake_command())
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)
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)
1912 configure_args.append("QMAKE_STRIP=:")
1913 configure_args.append("PREFIX=/usr")
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 )
1923 self.ensure_build_dir_exists()
1924 if not self.out_of_source_build:
1925 configure_args.append(self.relative_from_builddir_to_source())
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 )
1934 def build_impl(
1935 self,
1936 context: "BuildContext",
1937 manifest: "HighLevelManifest",
1938 **kwargs,
1939 ) -> None:
1940 self._make_support.run_make(context)
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"]
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 )
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 )
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 )
1994class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule):
1996 def qmake_command(self) -> str:
1997 return "qmake"
2000class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule):
2002 def qmake_command(self) -> str:
2003 return "qmake6"
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.
2016 This build system will attempt to use `make` to leverage instructions
2017 in a makefile (such as, `Makefile` or `GNUMakefile`).
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
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.
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.
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.
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]
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.
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.
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]]
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.
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).
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"]
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.
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]]
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.
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]]
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
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.
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]
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.
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]]
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
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
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 )
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 ]
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