Coverage for src/debputy/plugins/debputy/build_system_rules.py: 37%
680 statements
« prev ^ index » next coverage.py v7.8.2, created at 2026-01-16 17:20 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2026-01-16 17:20 +0000
1import dataclasses
2import json
3import os
4import subprocess
5import textwrap
6from typing import (
7 NotRequired,
8 TypedDict,
9 Self,
10 cast,
11 Union,
12 TYPE_CHECKING,
13 Literal,
14 Required,
15)
16from collections.abc import Mapping, Sequence, MutableMapping, Iterable, Container
18from debian.debian_support import Version
20from debputy._manifest_constants import MK_BUILDS
21from debputy.manifest_conditions import ManifestCondition
22from debputy.manifest_parser.base_types import (
23 BuildEnvironmentDefinition,
24 DebputyParsedContentStandardConditional,
25 FileSystemExactMatchRule,
26)
27from debputy.manifest_parser.exceptions import (
28 ManifestParseException,
29 ManifestInvalidUserDataException,
30)
31from debputy.manifest_parser.parser_data import ParserContextData
32from debputy.manifest_parser.tagging_types import DebputyParsedContent
33from debputy.manifest_parser.util import AttributePath
34from debputy.plugin.api import reference_documentation
35from debputy.plugin.api.impl import (
36 DebputyPluginInitializerProvider,
37)
38from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT
39from debputy.plugin.api.spec import (
40 documented_attr,
41 INTEGRATION_MODE_FULL,
42 only_integrations,
43 VirtualPath,
44)
45from debputy.plugin.api.std_docs import docs_from
46from debputy.plugins.debputy.to_be_api_types import (
47 BuildRule,
48 StepBasedBuildSystemRule,
49 OptionalInstallDirectly,
50 BuildSystemCharacteristics,
51 OptionalBuildDirectory,
52 OptionalInSourceBuild,
53 MakefileSupport,
54 BuildRuleParsedFormat,
55 debputy_build_system,
56 CleanHelper,
57 NinjaBuildSupport,
58 TestRule,
59 OptionalTestRule,
60)
61from debputy.types import EnvironmentModification
62from debputy.util import (
63 _warn,
64 run_build_system_command,
65 _error,
66 PerlConfigVars,
67 resolve_perl_config,
68 generated_content_dir,
69 manifest_format_doc,
70 _is_debug_log_enabled,
71)
73if TYPE_CHECKING:
74 from debputy.build_support.build_context import BuildContext
75 from debputy.highlevel_manifest import HighLevelManifest
78PERL_CMD = "perl"
81class Conditional(DebputyParsedContent):
82 when: Required[ManifestCondition]
85def register_build_system_rules(api: DebputyPluginInitializerProvider) -> None:
86 register_build_keywords(api)
87 register_build_rules(api)
90def register_build_keywords(api: DebputyPluginInitializerProvider) -> None:
92 api.pluggable_manifest_rule(
93 OPARSER_MANIFEST_ROOT,
94 "build-environments",
95 list[NamedEnvironmentSourceFormat],
96 _parse_build_environments,
97 register_value=False,
98 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL),
99 inline_reference_documentation=reference_documentation(
100 title="Build Environments (`build-environments`)",
101 description=textwrap.dedent(
102 """\
103 Define named environments to set the environment for any build commands that needs
104 a non-default environment.
106 The environment definitions can be used to tweak the environment variables used by the
107 build commands. An example:
109 build-environments:
110 - name: custom-env
111 set:
112 ENV_VAR: foo
113 ANOTHER_ENV_VAR: bar
114 builds:
115 - autoconf:
116 environment: custom-env
118 The environment definition has multiple attributes for setting environment variables
119 which determines when the definition is applied. The resulting environment is the
120 result of the following order of operations.
122 1. The environment `debputy` received from its parent process.
123 2. Apply all the variable definitions from `set` (if the attribute is present)
124 3. Apply all computed variables (such as variables from `dpkg-buildflags`).
125 4. Apply all the variable definitions from `override` (if the attribute is present)
126 5. Remove all variables listed in `unset` (if the attribute is present).
128 Accordingly, both `override` and `unset` will overrule any computed variables while
129 `set` will be overruled by any computed variables.
131 Note that these variables are not available via manifest substitution (they are only
132 visible to build commands). They are only available to build commands.
133 """
134 ),
135 attributes=[
136 documented_attr(
137 "name",
138 textwrap.dedent(
139 """\
140 The name of the environment
142 The name is used to reference the environment from build rules.
143 """
144 ),
145 ),
146 documented_attr(
147 "set",
148 textwrap.dedent(
149 """\
150 A mapping of environment variables to be set.
152 Note these environment variables are set before computed variables (such
153 as `dpkg-buildflags`) are provided. They can affect the content of the
154 computed variables, but they cannot overrule them. If you need to overrule
155 a computed variable, please use `override` instead.
156 """
157 ),
158 ),
159 documented_attr(
160 "override",
161 textwrap.dedent(
162 """\
163 A mapping of environment variables to set.
165 Similar to `set`, but it can overrule computed variables like those from
166 `dpkg-buildflags`.
167 """
168 ),
169 ),
170 documented_attr(
171 "unset",
172 textwrap.dedent(
173 """\
174 A list of environment variables to unset.
176 Any environment variable named here will be unset. No warnings or errors
177 will be raised if a given variable was not set.
178 """
179 ),
180 ),
181 ],
182 reference_documentation_url=manifest_format_doc(
183 "build-environment-build-environment"
184 ),
185 ),
186 )
187 api.pluggable_manifest_rule(
188 OPARSER_MANIFEST_ROOT,
189 "default-build-environment",
190 EnvironmentSourceFormat,
191 _parse_default_environment,
192 register_value=False,
193 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL),
194 inline_reference_documentation=reference_documentation(
195 title="Default Build Environment (`default-build-environment`)",
196 description=textwrap.dedent(
197 """\
198 Define the environment variables used in all build commands that uses the default
199 environment.
201 The environment definition can be used to tweak the environment variables used by the
202 build commands. An example:
204 default-build-environment:
205 set:
206 ENV_VAR: foo
207 ANOTHER_ENV_VAR: bar
209 The environment definition has multiple attributes for setting environment variables
210 which determines when the definition is applied. The resulting environment is the
211 result of the following order of operations.
213 1. The environment `debputy` received from its parent process.
214 2. Apply all the variable definitions from `set` (if the attribute is present)
215 3. Apply all computed variables (such as variables from `dpkg-buildflags`).
216 4. Apply all the variable definitions from `override` (if the attribute is present)
217 5. Remove all variables listed in `unset` (if the attribute is present).
219 Accordingly, both `override` and `unset` will overrule any computed variables while
220 `set` will be overruled by any computed variables.
222 Note that these variables are not available via manifest substitution (they are only
223 visible to build commands). They are only available to build commands.
224 """
225 ),
226 attributes=[
227 documented_attr(
228 "set",
229 textwrap.dedent(
230 """\
231 A mapping of environment variables to be set.
233 Note these environment variables are set before computed variables (such
234 as `dpkg-buildflags`) are provided. They can affect the content of the
235 computed variables, but they cannot overrule them. If you need to overrule
236 a computed variable, please use `override` instead.
237 """
238 ),
239 ),
240 documented_attr(
241 "override",
242 textwrap.dedent(
243 """\
244 A mapping of environment variables to set.
246 Similar to `set`, but it can overrule computed variables like those from
247 `dpkg-buildflags`.
248 """
249 ),
250 ),
251 documented_attr(
252 "unset",
253 textwrap.dedent(
254 """\
255 A list of environment variables to unset.
257 Any environment variable named here will be unset. No warnings or errors
258 will be raised if a given variable was not set.
259 """
260 ),
261 ),
262 ],
263 reference_documentation_url=manifest_format_doc(
264 "build-environment-build-environment"
265 ),
266 ),
267 )
268 api.pluggable_manifest_rule(
269 OPARSER_MANIFEST_ROOT,
270 MK_BUILDS,
271 list[BuildRule],
272 _handle_build_rules,
273 register_value=False,
274 expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL),
275 inline_reference_documentation=reference_documentation(
276 title=f"Build rules (`{MK_BUILDS}`)",
277 description=textwrap.dedent(
278 """\
279 Define how to build the upstream part of the package. Usually this is done via "build systems",
280 which also defines the clean rules.
282 A simple example is:
284 ```yaml
285 builds:
286 - autoconf:
287 configure-args:
288 - "--enable-foo"
289 - "--without=bar"
290 ```
292 Multiple builds are supported out of box. Here an example of how the build rules
293 for `libpam-krb5` might look.
295 ```yaml
296 builds:
297 - autoconf:
298 for: libpam-krb5
299 install-directly-to-package: true
300 configure-args:
301 - "--enable-reduced-depends"
302 - "--with-krb5-include=/usr/include/mit-krb5"
303 - "--with-krb5-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/mit-krb5"
304 - "--with-kadm-client-include=/usr/include/mit-krb5"
305 - "--with-kadm-client-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/mit-krb5"
306 - autoconf:
307 for: libpam-heimdal
308 install-directly-to-package: true
309 configure-args:
310 - "--enable-reduced-depends"
311 - "--with-krb5-include=/usr/include/heimdal"
312 - "--with-krb5-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/heimdal"
313 - "--with-kadm-client-include=/usr/include/heimdal"
314 - "--with-kadm-client-lib=/usr/lib/{{DEB_HOST_MULTIARCH}}/heimdal"
315 ```
316 """
317 ),
318 ),
319 )
321 api.provide_manifest_keyword(
322 TestRule,
323 "skip-tests",
324 lambda n, ap, pc: TestRule(
325 ap.path,
326 ManifestCondition.literal_bool(False),
327 ManifestCondition.literal_bool(True),
328 ),
329 inline_reference_documentation=reference_documentation(
330 title="Skip build time tests (`skip-tests`)",
331 description=textwrap.dedent(
332 """\
333 Skip all build time tests.
335 Example:
337 ```yaml
338 ...:
339 # The tests require internet access and cannot be run at build time.
340 test-rule: skip-tests
341 ```
343 """
344 ),
345 ),
346 )
348 api.pluggable_manifest_rule(
349 TestRule,
350 "skip-tests-when",
351 Conditional,
352 lambda n, pf, ap, pc: TestRule(
353 ap.path,
354 pf["when"].negated(),
355 ManifestCondition.literal_bool(False),
356 ),
357 source_format=ManifestCondition,
358 inline_reference_documentation=reference_documentation(
359 title="Conditionally skip build time tests (`skip-tests-when`)",
360 description=textwrap.dedent(
361 """\
362 The `skip-tests-when` test rule accepts a conditional. When that conditional evaluates to
363 true, the build time tests will be skipped. Otherwise, they will be run as usual.
365 Note if you want to only run the tests when the conditional evaluates to `true`,
366 then wrap the conditional with `not:`.
368 Example:
370 ```yaml
371 ...:
372 # Only run for linux-any (`DEB_HOST_ARCH`).
373 test-rule:
374 skip-tests-when:
375 not:
376 arch-matches: "linux-any"
377 ```
379 """
380 ),
381 ),
382 )
385def register_build_rules(api: DebputyPluginInitializerProvider) -> None:
386 api.register_build_system(ParsedAutoconfBuildRuleDefinition)
387 api.register_build_system(ParsedMakeBuildRuleDefinition)
389 api.register_build_system(ParsedPerlBuildBuildRuleDefinition)
390 api.register_build_system(ParsedPerlMakeMakerBuildRuleDefinition)
391 api.register_build_system(ParsedDebhelperBuildRuleDefinition)
393 api.register_build_system(ParsedCMakeBuildRuleDefinition)
394 api.register_build_system(ParsedMesonBuildRuleDefinition)
396 api.register_build_system(ParsedQmakeBuildRuleDefinition)
397 api.register_build_system(ParsedQmake6BuildRuleDefinition)
400class EnvironmentSourceFormat(TypedDict):
401 set: NotRequired[dict[str, str]]
402 override: NotRequired[dict[str, str]]
403 unset: NotRequired[list[str]]
406class NamedEnvironmentSourceFormat(EnvironmentSourceFormat):
407 name: str
410_READ_ONLY_ENV_VARS = {
411 "DEB_CHECK_COMMAND": None,
412 "DEB_SIGN_KEYID": None,
413 "DEB_SIGN_KEYFILE": None,
414 "DEB_BUILD_OPTIONS": "DEB_BUILD_MAINT_OPTIONS",
415 "DEB_BUILD_PROFILES": None,
416 "DEB_RULES_REQUIRES_ROOT": None,
417 "DEB_GAIN_ROOT_COMMAND": None,
418 "DH_EXTRA_ADDONS": None,
419 "DH_NO_ACT": None,
420 "XDG_RUNTIME_DIR": None,
421 "HOME": None,
422}
425def _check_variables(
426 env_vars: Iterable[str],
427 attribute_path: AttributePath,
428) -> None:
429 for env_var in env_vars:
430 if env_var not in _READ_ONLY_ENV_VARS: 430 ↛ 432line 430 didn't jump to line 432 because the condition on line 430 was always true
431 continue
432 alt = _READ_ONLY_ENV_VARS.get(env_var)
433 var_path = attribute_path[env_var].path_key_lc
434 if alt is None:
435 raise ManifestParseException(
436 f"The variable {env_var} cannot be modified by the manifest. This restriction is generally"
437 f" because the build should not touch those variables or changing them have no effect"
438 f" (since the consumer will not see the change). The problematic definition was {var_path}"
439 )
440 else:
441 raise ManifestParseException(
442 f"The variable {env_var} cannot be modified by the manifest. This restriction is generally"
443 f" because the build should not touch those variables or changing them have no effect"
444 f" (since the consumer will not see the change). Depending on what you are trying to"
445 f' accomplish, the variable "{alt}" might be a suitable alternative.'
446 f" The problematic definition was {var_path}"
447 )
450def _no_overlap(
451 lhs: Iterable[str | tuple[int, str]],
452 rhs: Container[str],
453 lhs_key: str,
454 rhs_key: str,
455 redundant_key: str,
456 attribute_path: AttributePath,
457) -> None:
458 for kt in lhs: 458 ↛ 459line 458 didn't jump to line 459 because the loop on line 458 never started
459 if isinstance(kt, tuple):
460 lhs_path_key, var = kt
461 else:
462 lhs_path_key = var = kt
463 if var not in rhs:
464 continue
465 lhs_path = attribute_path[lhs_key][lhs_path_key].path_key_lc
466 rhs_path = attribute_path[rhs_key][var].path_key_lc
467 r_path = lhs_path if redundant_key == rhs_key else rhs_path
468 raise ManifestParseException(
469 f"The environment variable {var} was declared in {lhs_path} and {rhs_path}."
470 f" Due to how the variables are applied, the definition in {r_path} is redundant"
471 f" and can effectively be removed. Please review the manifest and remove one of"
472 f" the two definitions."
473 )
476@dataclasses.dataclass(slots=True, frozen=True)
477class ManifestProvidedBuildEnvironment(BuildEnvironmentDefinition):
479 name: str
480 is_default: bool
481 attribute_path: AttributePath
482 parser_context: ParserContextData
484 set_vars: Mapping[str, str]
485 override_vars: Mapping[str, str]
486 unset_vars: Sequence[str]
488 @classmethod
489 def from_environment_definition(
490 cls,
491 env: EnvironmentSourceFormat,
492 attribute_path: AttributePath,
493 parser_context: ParserContextData,
494 is_default: bool = False,
495 ) -> Self:
496 reference_name: str | None
497 if is_default:
498 name = "default-env"
499 reference_name = None
500 else:
501 named_env = cast("NamedEnvironmentSourceFormat", env)
502 name = named_env["name"]
503 reference_name = name
505 set_vars = env.get("set", {})
506 override_vars = env.get("override", {})
507 unset_vars = env.get("unset", [])
508 _check_variables(set_vars, attribute_path["set"])
509 _check_variables(override_vars, attribute_path["override"])
510 _check_variables(unset_vars, attribute_path["unset"])
512 if not set_vars and not override_vars and not unset_vars: 512 ↛ 513line 512 didn't jump to line 513 because the condition on line 512 was never true
513 raise ManifestParseException(
514 f"The environment definition {attribute_path.path_key_lc} was empty. Please provide"
515 " some content or delete the definition."
516 )
518 _no_overlap(
519 enumerate(unset_vars),
520 set_vars,
521 "unset",
522 "set",
523 "set",
524 attribute_path,
525 )
526 _no_overlap(
527 enumerate(unset_vars),
528 override_vars,
529 "unset",
530 "override",
531 "override",
532 attribute_path,
533 )
534 _no_overlap(
535 override_vars,
536 set_vars,
537 "override",
538 "set",
539 "set",
540 attribute_path,
541 )
543 r = cls(
544 name,
545 is_default,
546 attribute_path,
547 parser_context,
548 set_vars,
549 override_vars,
550 unset_vars,
551 )
552 parser_context._register_build_environment(
553 reference_name,
554 r,
555 attribute_path,
556 is_default,
557 )
559 return r
561 def update_env(self, env: MutableMapping[str, str]) -> None:
562 if set_vars := self.set_vars: 562 ↛ 564line 562 didn't jump to line 564 because the condition on line 562 was always true
563 env.update(set_vars)
564 dpkg_env = self.dpkg_buildflags_env(env, self.attribute_path.path_key_lc)
565 if _is_debug_log_enabled(): 565 ↛ 566line 565 didn't jump to line 566 because the condition on line 565 was never true
566 self.log_computed_env(f"dpkg-buildflags [{self.name}]", dpkg_env)
567 if overlapping_env := dpkg_env.keys() & set_vars.keys(): 567 ↛ 568line 567 didn't jump to line 568 because the condition on line 567 was never true
568 for var in overlapping_env:
569 key_lc = self.attribute_path["set"][var].path_key_lc
570 _warn(
571 f'The variable "{var}" defined at {key_lc} is shadowed by a computed variable.'
572 f" If the manifest definition is more important, please define it via `override` rather than"
573 f" `set`."
574 )
575 env.update(dpkg_env)
576 if override_vars := self.override_vars: 576 ↛ 577line 576 didn't jump to line 577 because the condition on line 576 was never true
577 env.update(override_vars)
578 if unset_vars := self.unset_vars: 578 ↛ 579line 578 didn't jump to line 579 because the condition on line 578 was never true
579 for var in unset_vars:
580 try:
581 del env[var]
582 except KeyError:
583 pass
586_MAKE_DEFAULT_TOOLS = [
587 ("CC", "gcc"),
588 ("CXX", "g++"),
589 ("PKG_CONFIG", "pkg-config"),
590]
593class MakefileBuildSystemRule(StepBasedBuildSystemRule):
595 __slots__ = ("_make_support", "_build_target", "_install_target", "_directory")
597 def __init__(
598 self,
599 attributes: "ParsedMakeBuildRuleDefinition",
600 attribute_path: AttributePath,
601 parser_context: Union[ParserContextData, "HighLevelManifest"],
602 ) -> None:
603 super().__init__(attributes, attribute_path, parser_context)
604 directory = attributes.get("directory")
605 if directory is not None:
606 self._directory = directory.match_rule.path
607 else:
608 self._directory = None
609 self._make_support = MakefileSupport.from_build_system(self)
610 self._build_target = attributes.get("build_target")
611 self._test_target = attributes.get("test_target")
612 self._install_target = attributes.get("install_target")
614 @classmethod
615 def auto_detect_build_system(
616 cls,
617 source_root: VirtualPath,
618 *args,
619 **kwargs,
620 ) -> bool:
621 return any(p in source_root for p in ("Makefile", "makefile", "GNUmakefile"))
623 @classmethod
624 def characteristics(cls) -> BuildSystemCharacteristics:
625 return BuildSystemCharacteristics(
626 out_of_source_builds="not-supported",
627 )
629 def configure_impl(
630 self,
631 context: "BuildContext",
632 manifest: "HighLevelManifest",
633 **kwargs,
634 ) -> None:
635 # No configure step
636 pass
638 def build_impl(
639 self,
640 context: "BuildContext",
641 manifest: "HighLevelManifest",
642 **kwargs,
643 ) -> None:
644 extra_vars = []
645 build_target = self._build_target
646 if build_target is not None:
647 extra_vars.append(build_target)
648 if context.is_cross_compiling:
649 for envvar, tool in _MAKE_DEFAULT_TOOLS:
650 cross_tool = os.environ.get(envvar)
651 if cross_tool is None:
652 cross_tool = context.cross_tool(tool)
653 extra_vars.append(f"{envvar}={cross_tool}")
654 self._make_support.run_make(
655 context,
656 *extra_vars,
657 "INSTALL=install --strip-program=true",
658 directory=self._directory,
659 )
661 def test_impl(
662 self,
663 context: "BuildContext",
664 manifest: "HighLevelManifest",
665 *,
666 should_ignore_test_errors: bool = False,
667 **kwargs,
668 ) -> None:
669 self._run_make_maybe_explicit_target(
670 context,
671 self._test_target,
672 ["test", "check"],
673 )
675 def install_impl(
676 self,
677 context: "BuildContext",
678 manifest: "HighLevelManifest",
679 dest_dir: str,
680 **kwargs,
681 ) -> None:
682 self._run_make_maybe_explicit_target(
683 context,
684 self._install_target,
685 ["install"],
686 f"DESTDIR={dest_dir}",
687 "AM_UPDATE_INFO_DIR=no",
688 "INSTALL=install --strip-program=true",
689 )
691 def _run_make_maybe_explicit_target(
692 self,
693 context: "BuildContext",
694 provided_target: str | None,
695 fallback_targets: Sequence[str],
696 *make_args: str,
697 ) -> None:
698 make_support = self._make_support
699 if provided_target is not None:
700 make_support.run_make(
701 context,
702 provided_target,
703 *make_args,
704 directory=self._directory,
705 )
706 else:
707 make_support.run_first_existing_target_if_any(
708 context,
709 fallback_targets,
710 *make_args,
711 directory=self._directory,
712 )
714 def clean_impl(
715 self,
716 context: "BuildContext",
717 manifest: "HighLevelManifest",
718 clean_helper: "CleanHelper",
719 **kwargs,
720 ) -> None:
721 self._make_support.run_first_existing_target_if_any(
722 context,
723 ["distclean", "realclean", "clean"],
724 )
727class PerlBuildBuildSystemRule(StepBasedBuildSystemRule):
729 __slots__ = "configure_args"
731 def __init__(
732 self,
733 attributes: "ParsedPerlBuildBuildRuleDefinition",
734 attribute_path: AttributePath,
735 parser_context: Union[ParserContextData, "HighLevelManifest"],
736 ) -> None:
737 super().__init__(attributes, attribute_path, parser_context)
738 self.configure_args = attributes.get("configure_args", [])
740 @classmethod
741 def auto_detect_build_system(
742 cls,
743 source_root: VirtualPath,
744 *args,
745 **kwargs,
746 ) -> bool:
747 return "Build.PL" in source_root
749 @classmethod
750 def characteristics(cls) -> BuildSystemCharacteristics:
751 return BuildSystemCharacteristics(
752 out_of_source_builds="not-supported",
753 )
755 @staticmethod
756 def _perl_cross_build_env(
757 context: "BuildContext",
758 ) -> tuple[PerlConfigVars, EnvironmentModification | None]:
759 perl_config_data = resolve_perl_config(
760 context.dpkg_architecture_variables,
761 None,
762 )
763 if context.is_cross_compiling:
764 perl5lib_dir = perl_config_data.cross_inc_dir
765 if perl5lib_dir is not None:
766 env_perl5lib = os.environ.get("PERL5LIB")
767 if env_perl5lib is not None:
768 perl5lib_dir = (
769 perl5lib_dir + perl_config_data.path_sep + env_perl5lib
770 )
771 env_mod = EnvironmentModification(
772 replacements=(("PERL5LIB", perl5lib_dir),),
773 )
774 return perl_config_data, env_mod
775 return perl_config_data, None
777 def configure_impl(
778 self,
779 context: "BuildContext",
780 manifest: "HighLevelManifest",
781 **kwargs,
782 ) -> None:
783 perl_config_data, cross_env_mod = self._perl_cross_build_env(context)
784 configure_env = EnvironmentModification(
785 replacements=(
786 ("PERL_MM_USE_DEFAULT", "1"),
787 ("PKG_CONFIG", context.cross_tool("pkg-config")),
788 )
789 )
790 if cross_env_mod is not None:
791 configure_env = configure_env.combine(cross_env_mod)
793 configure_cmd = [
794 PERL_CMD,
795 "Build.PL",
796 "--installdirs",
797 "vendor",
798 ]
799 cflags = os.environ.get("CFLAGS", "")
800 cppflags = os.environ.get("CPPFLAGS", "")
801 ldflags = os.environ.get("LDFLAGS", "")
803 if cflags != "" or cppflags != "":
804 configure_cmd.append("--config")
805 combined = f"{cflags} {cppflags}".strip()
806 configure_cmd.append(f"optimize={combined}")
808 if ldflags != "" or cflags != "" or context.is_cross_compiling:
809 configure_cmd.append("--config")
810 combined = f"{perl_config_data.ld} {cflags} {ldflags}".strip()
811 configure_cmd.append(f"ld={combined}")
812 if self.configure_args:
813 substitution = self.substitution
814 attr_path = self.attribute_path["configure_args"]
815 configure_cmd.extend(
816 substitution.substitute(v, attr_path[i].path)
817 for i, v in enumerate(self.configure_args)
818 )
819 run_build_system_command(*configure_cmd, env_mod=configure_env)
821 def build_impl(
822 self,
823 context: "BuildContext",
824 manifest: "HighLevelManifest",
825 **kwargs,
826 ) -> None:
827 _, cross_env_mod = self._perl_cross_build_env(context)
828 run_build_system_command(PERL_CMD, "Build", env_mod=cross_env_mod)
830 def test_impl(
831 self,
832 context: "BuildContext",
833 manifest: "HighLevelManifest",
834 *,
835 should_ignore_test_errors: bool = False,
836 **kwargs,
837 ) -> None:
838 _, cross_env_mod = self._perl_cross_build_env(context)
839 run_build_system_command(
840 PERL_CMD,
841 "Build",
842 "test",
843 "--verbose",
844 "1",
845 env_mod=cross_env_mod,
846 )
848 def install_impl(
849 self,
850 context: "BuildContext",
851 manifest: "HighLevelManifest",
852 dest_dir: str,
853 **kwargs,
854 ) -> None:
855 _, cross_env_mod = self._perl_cross_build_env(context)
856 run_build_system_command(
857 PERL_CMD,
858 "Build",
859 "install",
860 "--destdir",
861 dest_dir,
862 "--create_packlist",
863 "0",
864 env_mod=cross_env_mod,
865 )
867 def clean_impl(
868 self,
869 context: "BuildContext",
870 manifest: "HighLevelManifest",
871 clean_helper: "CleanHelper",
872 **kwargs,
873 ) -> None:
874 _, cross_env_mod = self._perl_cross_build_env(context)
875 if os.path.lexists("Build"):
876 run_build_system_command(
877 PERL_CMD,
878 "Build",
879 "realclean",
880 "--allow_mb_mismatch",
881 "1",
882 env_mod=cross_env_mod,
883 )
886class PerlMakeMakerBuildSystemRule(StepBasedBuildSystemRule):
888 __slots__ = ("configure_args", "_make_support")
890 def __init__(
891 self,
892 attributes: "ParsedPerlBuildBuildRuleDefinition",
893 attribute_path: AttributePath,
894 parser_context: Union[ParserContextData, "HighLevelManifest"],
895 ) -> None:
896 super().__init__(attributes, attribute_path, parser_context)
897 self.configure_args = attributes.get("configure_args", [])
898 self._make_support = MakefileSupport.from_build_system(self)
900 @classmethod
901 def auto_detect_build_system(
902 cls,
903 source_root: VirtualPath,
904 *args,
905 **kwargs,
906 ) -> bool:
907 return "Makefile.PL" in source_root
909 @classmethod
910 def characteristics(cls) -> BuildSystemCharacteristics:
911 return BuildSystemCharacteristics(
912 out_of_source_builds="not-supported",
913 )
915 def configure_impl(
916 self,
917 context: "BuildContext",
918 manifest: "HighLevelManifest",
919 **kwargs,
920 ) -> None:
921 configure_env = EnvironmentModification(
922 replacements=(
923 ("PERL_MM_USE_DEFAULT", "1"),
924 ("PERL_AUTOINSTALL", "--skipdeps"),
925 ("PKG_CONFIG", context.cross_tool("pkg-config")),
926 )
927 )
928 perl_args = []
929 mm_args = ["INSTALLDIRS=vendor"]
930 if "CFLAGS" in os.environ:
931 mm_args.append(
932 f"OPTIMIZE={os.environ['CFLAGS']} {os.environ['CPPFLAGS']}".rstrip()
933 )
935 perl_config_data = resolve_perl_config(
936 context.dpkg_architecture_variables,
937 None,
938 )
940 if "LDFLAGS" in os.environ:
941 mm_args.append(
942 f"LD={perl_config_data.ld} {os.environ['CFLAGS']} {os.environ['LDFLAGS']}"
943 )
945 if context.is_cross_compiling:
946 perl5lib_dir = perl_config_data.cross_inc_dir
947 if perl5lib_dir is not None:
948 perl_args.append(f"-I{perl5lib_dir}")
950 if self.configure_args:
951 substitution = self.substitution
952 attr_path = self.attribute_path["configure_args"]
953 mm_args.extend(
954 substitution.substitute(v, attr_path[i].path)
955 for i, v in enumerate(self.configure_args)
956 )
957 run_build_system_command(
958 PERL_CMD,
959 *perl_args,
960 "Makefile.PL",
961 *mm_args,
962 env_mod=configure_env,
963 )
965 def build_impl(
966 self,
967 context: "BuildContext",
968 manifest: "HighLevelManifest",
969 **kwargs,
970 ) -> None:
971 self._make_support.run_make(context)
973 def test_impl(
974 self,
975 context: "BuildContext",
976 manifest: "HighLevelManifest",
977 *,
978 should_ignore_test_errors: bool = False,
979 **kwargs,
980 ) -> None:
981 self._make_support.run_first_existing_target_if_any(
982 context,
983 ["check", "test"],
984 "TEST_VERBOSE=1",
985 )
987 def install_impl(
988 self,
989 context: "BuildContext",
990 manifest: "HighLevelManifest",
991 dest_dir: str,
992 **kwargs,
993 ) -> None:
994 is_mm_makefile = False
995 with open("Makefile", "rb") as fd:
996 for line in fd:
997 if b"generated automatically by MakeMaker" in line:
998 is_mm_makefile = True
999 break
1001 install_args = [f"DESTDIR={dest_dir}"]
1003 # Special case for Makefile.PL that uses
1004 # Module::Build::Compat. PREFIX should not be passed
1005 # for those; it already installs into /usr by default.
1006 if is_mm_makefile:
1007 install_args.append("PREFIX=/usr")
1009 self._make_support.run_first_existing_target_if_any(
1010 context,
1011 ["install"],
1012 *install_args,
1013 )
1015 def clean_impl(
1016 self,
1017 context: "BuildContext",
1018 manifest: "HighLevelManifest",
1019 clean_helper: "CleanHelper",
1020 **kwargs,
1021 ) -> None:
1022 self._make_support.run_first_existing_target_if_any(
1023 context,
1024 ["distclean", "realclean", "clean"],
1025 )
1028class DebhelperBuildSystemRule(StepBasedBuildSystemRule):
1030 __slots__ = ("configure_args", "dh_build_system")
1032 def __init__(
1033 self,
1034 parsed_data: "ParsedDebhelperBuildRuleDefinition",
1035 attribute_path: AttributePath,
1036 parser_context: Union[ParserContextData, "HighLevelManifest"],
1037 ) -> None:
1038 super().__init__(parsed_data, attribute_path, parser_context)
1039 self.configure_args = parsed_data.get("configure_args", [])
1040 self.dh_build_system = parsed_data.get("dh_build_system")
1042 @classmethod
1043 def auto_detect_build_system(
1044 cls,
1045 source_root: VirtualPath,
1046 *args,
1047 **kwargs,
1048 ) -> bool:
1049 try:
1050 v = subprocess.check_output(
1051 ["dh_assistant", "which-build-system"],
1052 # Packages without `debhelper-compat` will trigger an error, which will just be noise
1053 stderr=subprocess.DEVNULL,
1054 cwd=source_root.fs_path,
1055 )
1056 except subprocess.CalledProcessError:
1057 return False
1058 d = json.loads(v)
1059 build_system = d.get("build-system")
1060 return build_system is not None and build_system != "none"
1062 @classmethod
1063 def characteristics(cls) -> BuildSystemCharacteristics:
1064 return BuildSystemCharacteristics(
1065 out_of_source_builds="supported-but-not-default",
1066 )
1068 def before_first_impl_step(
1069 self, *, stage: Literal["build", "clean"], **kwargs
1070 ) -> None:
1071 dh_build_system = self.dh_build_system
1072 if dh_build_system is None:
1073 return
1074 try:
1075 subprocess.check_call(
1076 ["dh_assistant", "which-build-system", f"-S{dh_build_system}"]
1077 )
1078 except FileNotFoundError:
1079 _error(
1080 "The debhelper build system assumes `dh_assistant` is available (`debhelper (>= 13.5~)`)"
1081 )
1082 except subprocess.SubprocessError:
1083 raise ManifestInvalidUserDataException(
1084 f'The debhelper build system "{dh_build_system}" does not seem to'
1085 f" be available according to"
1086 f" `dh_assistant which-build-system -S{dh_build_system}`"
1087 ) from None
1089 def _default_options(self) -> list[str]:
1090 default_options = []
1091 if self.dh_build_system is not None:
1092 default_options.append(f"-S{self.dh_build_system}")
1093 if self.build_directory is not None:
1094 default_options.append(f"-B{self.build_directory}")
1096 return default_options
1098 def configure_impl(
1099 self,
1100 context: "BuildContext",
1101 manifest: "HighLevelManifest",
1102 **kwargs,
1103 ) -> None:
1104 if (
1105 os.path.lexists("configure.ac") or os.path.lexists("configure.in")
1106 ) and not os.path.lexists("debian/autoreconf.before"):
1107 run_build_system_command("dh_update_autotools_config")
1108 run_build_system_command("dh_autoreconf")
1110 default_options = self._default_options()
1111 configure_args = default_options.copy()
1112 if self.configure_args:
1113 configure_args.append("--")
1114 substitution = self.substitution
1115 attr_path = self.attribute_path["configure_args"]
1116 configure_args.extend(
1117 substitution.substitute(v, attr_path[i].path)
1118 for i, v in enumerate(self.configure_args)
1119 )
1120 run_build_system_command("dh_auto_configure", *configure_args)
1122 def build_impl(
1123 self,
1124 context: "BuildContext",
1125 manifest: "HighLevelManifest",
1126 **kwargs,
1127 ) -> None:
1128 default_options = self._default_options()
1129 run_build_system_command("dh_auto_build", *default_options)
1131 def test_impl(
1132 self,
1133 context: "BuildContext",
1134 manifest: "HighLevelManifest",
1135 *,
1136 should_ignore_test_errors: bool = False,
1137 **kwargs,
1138 ) -> None:
1139 default_options = self._default_options()
1140 run_build_system_command("dh_auto_test", *default_options)
1142 def install_impl(
1143 self,
1144 context: "BuildContext",
1145 manifest: "HighLevelManifest",
1146 dest_dir: str,
1147 **kwargs,
1148 ) -> None:
1149 default_options = self._default_options()
1150 run_build_system_command(
1151 "dh_auto_install",
1152 *default_options,
1153 f"--destdir={dest_dir}",
1154 )
1156 def clean_impl(
1157 self,
1158 context: "BuildContext",
1159 manifest: "HighLevelManifest",
1160 clean_helper: "CleanHelper",
1161 **kwargs,
1162 ) -> None:
1163 default_options = self._default_options()
1164 run_build_system_command("dh_auto_clean", *default_options)
1165 # The "global" clean logic takes care of `dh_autoreconf_clean` and `dh_clean`
1168class AutoconfBuildSystemRule(StepBasedBuildSystemRule):
1170 __slots__ = ("configure_args", "_make_support")
1172 def __init__(
1173 self,
1174 parsed_data: "ParsedAutoconfBuildRuleDefinition",
1175 attribute_path: AttributePath,
1176 parser_context: Union[ParserContextData, "HighLevelManifest"],
1177 ) -> None:
1178 super().__init__(parsed_data, attribute_path, parser_context)
1179 self.configure_args = list(parsed_data.get("configure_args", []))
1180 self._make_support = MakefileSupport.from_build_system(self)
1182 @classmethod
1183 def characteristics(cls) -> BuildSystemCharacteristics:
1184 return BuildSystemCharacteristics(
1185 out_of_source_builds="supported-and-default",
1186 )
1188 @classmethod
1189 def auto_detect_build_system(
1190 cls,
1191 source_root: VirtualPath,
1192 *args,
1193 **kwargs,
1194 ) -> bool:
1195 if "configure.ac" in source_root:
1196 return True
1197 configure_in = source_root.get("configure.in")
1198 if configure_in is not None and configure_in.is_file:
1199 with configure_in.open(byte_io=True, buffering=4096) as fd:
1200 for no, line in enumerate(fd):
1201 if no > 100: 1201 ↛ 1202line 1201 didn't jump to line 1202 because the condition on line 1201 was never true
1202 break
1203 if b"AC_INIT" in line or b"AC_PREREQ" in line:
1204 return True
1205 configure = source_root.get("configure")
1206 if configure is None or not configure.is_executable or not configure.is_file:
1207 return False
1208 with configure.open(byte_io=True, buffering=4096) as fd:
1209 for no, line in enumerate(fd):
1210 if no > 10: 1210 ↛ 1211line 1210 didn't jump to line 1211 because the condition on line 1210 was never true
1211 break
1212 if b"GNU Autoconf" in line:
1213 return True
1214 return False
1216 def configure_impl(
1217 self,
1218 context: "BuildContext",
1219 manifest: "HighLevelManifest",
1220 **kwargs,
1221 ) -> None:
1222 if (
1223 os.path.lexists("configure.ac") or os.path.lexists("configure.in")
1224 ) and not os.path.lexists("debian/autoreconf.before"):
1225 run_build_system_command("dh_update_autotools_config")
1226 run_build_system_command("dh_autoreconf")
1228 dpkg_architecture_variables = context.dpkg_architecture_variables
1229 multi_arch = dpkg_architecture_variables.current_host_multiarch
1230 silent_rules = (
1231 "--enable-silent-rules"
1232 if context.is_terse_build
1233 else "--disable-silent-rules"
1234 )
1236 configure_args = [
1237 f"--build={dpkg_architecture_variables['DEB_BUILD_GNU_TYPE']}",
1238 "--prefix=/usr",
1239 "--includedir=${prefix}/include",
1240 "--mandir=${prefix}/share/man",
1241 "--infodir=${prefix}/share/info",
1242 "--sysconfdir=/etc",
1243 "--localstatedir=/var",
1244 "--disable-option-checking",
1245 silent_rules,
1246 f"--libdir=${ prefix} /{multi_arch}",
1247 "--runstatedir=/run",
1248 "--disable-maintainer-mode",
1249 "--disable-dependency-tracking",
1250 ]
1251 if dpkg_architecture_variables.is_cross_compiling:
1252 configure_args.append(
1253 f"--host={dpkg_architecture_variables['DEB_HOST_GNU_TYPE']}"
1254 )
1255 if self.configure_args:
1256 substitution = self.substitution
1257 attr_path = self.attribute_path["configure_args"]
1258 configure_args.extend(
1259 substitution.substitute(v, attr_path[i].path)
1260 for i, v in enumerate(self.configure_args)
1261 )
1262 self.ensure_build_dir_exists()
1263 configure_script = self.relative_from_builddir_to_source("configure")
1264 with self.dump_logs_on_error("config.log"):
1265 run_build_system_command(
1266 configure_script,
1267 *configure_args,
1268 cwd=self.build_directory,
1269 )
1271 def build_impl(
1272 self,
1273 context: "BuildContext",
1274 manifest: "HighLevelManifest",
1275 **kwargs,
1276 ) -> None:
1277 self._make_support.run_make(context)
1279 def test_impl(
1280 self,
1281 context: "BuildContext",
1282 manifest: "HighLevelManifest",
1283 *,
1284 should_ignore_test_errors: bool = False,
1285 **kwargs,
1286 ) -> None:
1287 limit = context.parallelization_limit(support_zero_as_unlimited=True)
1288 testsuite_flags = [f"-j{limit}"] if limit else ["-j"]
1290 if not context.is_terse_build:
1291 testsuite_flags.append("--verbose")
1292 self._make_support.run_first_existing_target_if_any(
1293 context,
1294 # Order is deliberately inverse compared to debhelper (#924052)
1295 ["check", "test"],
1296 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}",
1297 "VERBOSE=1",
1298 )
1300 def install_impl(
1301 self,
1302 context: "BuildContext",
1303 manifest: "HighLevelManifest",
1304 dest_dir: str,
1305 **kwargs,
1306 ) -> None:
1307 enable_parallelization = not os.path.lexists(self.build_dir_path("libtool"))
1308 self._make_support.run_first_existing_target_if_any(
1309 context,
1310 ["install"],
1311 f"DESTDIR={dest_dir}",
1312 "AM_UPDATE_INFO_DIR=no",
1313 enable_parallelization=enable_parallelization,
1314 )
1316 def clean_impl(
1317 self,
1318 context: "BuildContext",
1319 manifest: "HighLevelManifest",
1320 clean_helper: "CleanHelper",
1321 **kwargs,
1322 ) -> None:
1323 if self.out_of_source_build:
1324 return
1325 self._make_support.run_first_existing_target_if_any(
1326 context,
1327 ["distclean", "realclean", "clean"],
1328 )
1329 # The "global" clean logic takes care of `dh_autoreconf_clean` and `dh_clean`
1332class CMakeBuildSystemRule(StepBasedBuildSystemRule):
1334 __slots__ = (
1335 "configure_args",
1336 "target_build_system",
1337 "_make_support",
1338 "_ninja_support",
1339 )
1341 def __init__(
1342 self,
1343 parsed_data: "ParsedCMakeBuildRuleDefinition",
1344 attribute_path: AttributePath,
1345 parser_context: Union[ParserContextData, "HighLevelManifest"],
1346 ) -> None:
1347 super().__init__(parsed_data, attribute_path, parser_context)
1348 self.configure_args = list(parsed_data.get("configure_args", []))
1349 self.target_build_system: Literal["make", "ninja"] = parsed_data.get(
1350 "target_build_system", "make"
1351 )
1352 self._make_support = MakefileSupport.from_build_system(self)
1353 self._ninja_support = NinjaBuildSupport.from_build_system(self)
1355 @classmethod
1356 def characteristics(cls) -> BuildSystemCharacteristics:
1357 return BuildSystemCharacteristics(
1358 out_of_source_builds="required",
1359 )
1361 @classmethod
1362 def auto_detect_build_system(
1363 cls,
1364 source_root: VirtualPath,
1365 *args,
1366 **kwargs,
1367 ) -> bool:
1368 return "CMakeLists.txt" in source_root
1370 @staticmethod
1371 def _default_cmake_env(
1372 build_context: "BuildContext",
1373 ) -> EnvironmentModification:
1374 replacements = {}
1375 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ:
1376 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb"
1377 if "PKG_CONFIG" not in os.environ:
1378 pkg_config = build_context.cross_tool("pkg-config")
1379 replacements["PKG_CONFIG"] = f"/usr/bin/{pkg_config}"
1380 return EnvironmentModification(
1381 replacements=tuple((k, v) for k, v in replacements.items())
1382 )
1384 @classmethod
1385 def cmake_generator(cls, target_build_system: Literal["make", "ninja"]) -> str:
1386 cmake_generators = {
1387 "make": "Unix Makefiles",
1388 "ninja": "Ninja",
1389 }
1390 return cmake_generators[target_build_system]
1392 @staticmethod
1393 def _compiler_and_cross_flags(
1394 context: "BuildContext",
1395 cmake_flags: list[str],
1396 ) -> None:
1398 if "CC" in os.environ:
1399 cmake_flags.append(f"-DCMAKE_C_COMPILER={os.environ['CC']}")
1400 elif context.is_cross_compiling:
1401 cmake_flags.append(f"-DCMAKE_C_COMPILER={context.cross_tool('gcc')}")
1403 if "CXX" in os.environ:
1404 cmake_flags.append(f"-DCMAKE_CXX_COMPILER={os.environ['CXX']}")
1405 elif context.is_cross_compiling:
1406 cmake_flags.append(f"-DCMAKE_CXX_COMPILER={context.cross_tool('g++')}")
1408 if context.is_cross_compiling:
1409 deb_host2cmake_system = {
1410 "linux": "Linux",
1411 "kfreebsd": "kFreeBSD",
1412 "hurd": "GNU",
1413 }
1415 gnu_cpu2system_processor = {
1416 "arm": "armv7l",
1417 "misp64el": "mips64",
1418 "powerpc64le": "ppc64le",
1419 }
1420 dpkg_architecture_variables = context.dpkg_architecture_variables
1422 try:
1423 system_name = deb_host2cmake_system[
1424 dpkg_architecture_variables["DEB_HOST_ARCH_OS"]
1425 ]
1426 except KeyError as e:
1427 name = e.args[0]
1428 _error(
1429 f"Cannot cross-compile via cmake: Missing CMAKE_SYSTEM_NAME for the DEB_HOST_ARCH_OS {name}"
1430 )
1432 gnu_cpu = dpkg_architecture_variables["DEB_HOST_GNU_CPU"]
1433 system_processor = gnu_cpu2system_processor.get(gnu_cpu, gnu_cpu)
1435 cmake_flags.append(f"-DCMAKE_SYSTEM_NAME={system_name}")
1436 cmake_flags.append(f"-DCMAKE_SYSTEM_PROCESSOR={system_processor}")
1438 pkg_config = context.cross_tool("pkg-config")
1439 # Historical uses. Current versions of cmake uses the env variable instead.
1440 cmake_flags.append(f"-DPKG_CONFIG_EXECUTABLE=/usr/bin/{pkg_config}")
1441 cmake_flags.append(f"-DPKGCONFIG_EXECUTABLE=/usr/bin/{pkg_config}")
1442 cmake_flags.append(
1443 f"-DQMAKE_EXECUTABLE=/usr/bin/{context.cross_tool('qmake')}"
1444 )
1446 def configure_impl(
1447 self,
1448 context: "BuildContext",
1449 manifest: "HighLevelManifest",
1450 **kwargs,
1451 ) -> None:
1452 cmake_flags = [
1453 "-DCMAKE_INSTALL_PREFIX=/usr",
1454 "-DCMAKE_BUILD_TYPE=None",
1455 "-DCMAKE_INSTALL_SYSCONFDIR=/etc",
1456 "-DCMAKE_INSTALL_LOCALSTATEDIR=/var",
1457 "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON",
1458 "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF",
1459 "-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON",
1460 "-DFETCHCONTENT_FULLY_DISCONNECTED=ON",
1461 "-DCMAKE_INSTALL_RUNSTATEDIR=/run",
1462 "-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON",
1463 "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON",
1464 f"-DCMAKE_INSTALL_LIBDIR=lib/{context.dpkg_architecture_variables['DEB_HOST_MULTIARCH']}",
1465 f"-G{self.cmake_generator(self.target_build_system)}",
1466 ]
1467 if not context.is_terse_build:
1468 cmake_flags.append("-DCMAKE_VERBOSE_MAKEFILE=ON")
1469 if not context.should_run_tests:
1470 cmake_flags.append("-DBUILD_TESTING:BOOL=OFF")
1472 self._compiler_and_cross_flags(context, cmake_flags)
1474 if self.configure_args:
1475 substitution = self.substitution
1476 attr_path = self.attribute_path["configure_args"]
1477 cmake_flags.extend(
1478 substitution.substitute(v, attr_path[i].path)
1479 for i, v in enumerate(self.configure_args)
1480 )
1482 env_mod = self._default_cmake_env(context)
1483 if "CPPFLAGS" in os.environ:
1484 # CMake doesn't respect CPPFLAGS, see #653916.
1485 cppflags = os.environ["CPPFLAGS"]
1486 cflags = f"{os.environ.get('CFLAGS', '')} {cppflags}".lstrip()
1487 cxxflags = f"{os.environ.get('CXXFLAGS', '')} {cppflags}".lstrip()
1488 env_mod = env_mod.combine(
1489 # The debhelper build system never showed this delta, so people might find it annoying.
1490 EnvironmentModification(
1491 replacements=(
1492 ("CFLAGS", cflags),
1493 ("CXXFLAGS", cxxflags),
1494 )
1495 )
1496 )
1497 if "ASMFLAGS" not in os.environ and "ASFLAGS" in os.environ:
1498 env_mod = env_mod.combine(
1499 # The debhelper build system never showed this delta, so people might find it annoying.
1500 EnvironmentModification(
1501 replacements=(("ASMFLAGS", os.environ["ASFLAGS"]),),
1502 )
1503 )
1504 self.ensure_build_dir_exists()
1505 source_dir_from_build_dir = self.relative_from_builddir_to_source()
1507 with self.dump_logs_on_error(
1508 "CMakeCache.txt",
1509 "CMakeFiles/CMakeOutput.log",
1510 "CMakeFiles/CMakeError.log",
1511 ):
1512 run_build_system_command(
1513 "cmake",
1514 *cmake_flags,
1515 source_dir_from_build_dir,
1516 cwd=self.build_directory,
1517 env_mod=env_mod,
1518 )
1520 def build_impl(
1521 self,
1522 context: "BuildContext",
1523 manifest: "HighLevelManifest",
1524 **kwargs,
1525 ) -> None:
1526 if self.target_build_system == "make":
1527 make_flags = []
1528 if not context.is_terse_build:
1529 make_flags.append("VERBOSE=1")
1530 self._make_support.run_make(context, *make_flags)
1531 else:
1532 self._ninja_support.run_ninja_build(context)
1534 def test_impl(
1535 self,
1536 context: "BuildContext",
1537 manifest: "HighLevelManifest",
1538 *,
1539 should_ignore_test_errors: bool = False,
1540 **kwargs,
1541 ) -> None:
1542 env_mod = EnvironmentModification(
1543 replacements=(("CTEST_OUTPUT_ON_FAILURE", "1"),),
1544 )
1545 if self.target_build_system == "make":
1546 # Unlike make, CTest does not have "unlimited parallel" setting (-j implies
1547 # -j1). Therefore, we do not set "allow zero as unlimited" here.
1548 make_flags = [f"ARGS+=-j{context.parallelization_limit()}"]
1549 if not context.is_terse_build:
1550 make_flags.append("ARGS+=--verbose")
1551 self._make_support.run_first_existing_target_if_any(
1552 context,
1553 ["check", "test"],
1554 *make_flags,
1555 env_mod=env_mod,
1556 )
1557 else:
1558 self._ninja_support.run_ninja_test(context, env_mod=env_mod)
1560 limit = context.parallelization_limit(support_zero_as_unlimited=True)
1561 testsuite_flags = [f"-j{limit}"] if limit else ["-j"]
1563 if not context.is_terse_build:
1564 testsuite_flags.append("--verbose")
1565 self._make_support.run_first_existing_target_if_any(
1566 context,
1567 # Order is deliberately inverse compared to debhelper (#924052)
1568 ["check", "test"],
1569 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}",
1570 "VERBOSE=1",
1571 )
1573 def install_impl(
1574 self,
1575 context: "BuildContext",
1576 manifest: "HighLevelManifest",
1577 dest_dir: str,
1578 **kwargs,
1579 ) -> None:
1580 env_mod = EnvironmentModification(
1581 replacements=(
1582 ("LC_ALL", "C.UTF-8"),
1583 ("DESTDIR", dest_dir),
1584 )
1585 ).combine(self._default_cmake_env(context))
1586 run_build_system_command(
1587 "cmake",
1588 "--install",
1589 self.build_directory,
1590 env_mod=env_mod,
1591 )
1593 def clean_impl(
1594 self,
1595 context: "BuildContext",
1596 manifest: "HighLevelManifest",
1597 clean_helper: "CleanHelper",
1598 **kwargs,
1599 ) -> None:
1600 if self.out_of_source_build:
1601 return
1602 if self.target_build_system == "make":
1603 # Keep it here in case we change the `required` "out of source" to "supported-default"
1604 self._make_support.run_first_existing_target_if_any(
1605 context,
1606 ["distclean", "realclean", "clean"],
1607 )
1608 else:
1609 self._ninja_support.run_ninja_clean(context)
1612class MesonBuildSystemRule(StepBasedBuildSystemRule):
1614 __slots__ = (
1615 "configure_args",
1616 "_ninja_support",
1617 )
1619 def __init__(
1620 self,
1621 parsed_data: "ParsedMesonBuildRuleDefinition",
1622 attribute_path: AttributePath,
1623 parser_context: Union[ParserContextData, "HighLevelManifest"],
1624 ) -> None:
1625 super().__init__(parsed_data, attribute_path, parser_context)
1626 self.configure_args = list(parsed_data.get("configure_args", []))
1627 self._ninja_support = NinjaBuildSupport.from_build_system(self)
1629 @classmethod
1630 def characteristics(cls) -> BuildSystemCharacteristics:
1631 return BuildSystemCharacteristics(
1632 out_of_source_builds="required",
1633 )
1635 @classmethod
1636 def auto_detect_build_system(
1637 cls,
1638 source_root: VirtualPath,
1639 *args,
1640 **kwargs,
1641 ) -> bool:
1642 return "meson.build" in source_root
1644 @staticmethod
1645 def _default_meson_env() -> EnvironmentModification:
1646 replacements = {
1647 "LC_ALL": "C.UTF-8",
1648 }
1649 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ:
1650 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb"
1651 return EnvironmentModification(
1652 replacements=tuple((k, v) for k, v in replacements.items())
1653 )
1655 @classmethod
1656 def cmake_generator(cls, target_build_system: Literal["make", "ninja"]) -> str:
1657 cmake_generators = {
1658 "make": "Unix Makefiles",
1659 "ninja": "Ninja",
1660 }
1661 return cmake_generators[target_build_system]
1663 @staticmethod
1664 def _cross_flags(
1665 context: "BuildContext",
1666 meson_flags: list[str],
1667 ) -> None:
1668 if not context.is_cross_compiling:
1669 return
1670 # Needs a cross-file http://mesonbuild.com/Cross-compilation.html
1671 cross_files_dir = os.path.abspath(
1672 generated_content_dir(
1673 subdir_key="meson-cross-files",
1674 )
1675 )
1676 host_arch = context.dpkg_architecture_variables.current_host_arch
1677 cross_file = os.path.join(cross_files_dir, f"meson-cross-file-{host_arch}.conf")
1678 if not os.path.isfile(cross_file):
1679 env = os.environ
1680 if env.get("LC_ALL") != "C.UTF-8":
1681 env = dict(env)
1682 env["LC_ALL"] = "C.UTF-8"
1683 else:
1684 env = None
1685 subprocess.check_call(
1686 [
1687 "/usr/share/meson/debcrossgen",
1688 f"--arch={host_arch}",
1689 f"-o{cross_file}",
1690 ],
1691 stdout=subprocess.DEVNULL,
1692 env=env,
1693 )
1695 meson_flags.append("--cross-file")
1696 meson_flags.append(cross_file)
1698 def configure_impl(
1699 self,
1700 context: "BuildContext",
1701 manifest: "HighLevelManifest",
1702 **kwargs,
1703 ) -> None:
1704 meson_version = Version(
1705 subprocess.check_output(
1706 ["meson", "--version"],
1707 encoding="utf-8",
1708 ).strip()
1709 )
1710 dpkg_architecture_variables = context.dpkg_architecture_variables
1712 meson_flags = [
1713 "--wrap-mode=nodownload",
1714 "--buildtype=plain",
1715 "--prefix=/usr",
1716 "--sysconfdir=/etc",
1717 "--localstatedir=/var",
1718 f"--libdir=lib/{dpkg_architecture_variables.current_host_multiarch}",
1719 "--auto-features=enabled",
1720 ]
1721 if meson_version >= Version("1.2.0"):
1722 # There was a behavior change in Meson 1.2.0: previously
1723 # byte-compilation wasn't supported, but since 1.2.0 it is on by
1724 # default. We can only use this option to turn it off in versions
1725 # where the option exists.
1726 meson_flags.append("-Dpython.bytecompile=-1")
1728 self._cross_flags(context, meson_flags)
1730 if self.configure_args:
1731 substitution = self.substitution
1732 attr_path = self.attribute_path["configure_args"]
1733 meson_flags.extend(
1734 substitution.substitute(v, attr_path[i].path)
1735 for i, v in enumerate(self.configure_args)
1736 )
1738 env_mod = self._default_meson_env()
1740 self.ensure_build_dir_exists()
1741 source_dir_from_build_dir = self.relative_from_builddir_to_source()
1743 with self.dump_logs_on_error("meson-logs/meson-log.txt"):
1744 run_build_system_command(
1745 "meson",
1746 "setup",
1747 source_dir_from_build_dir,
1748 *meson_flags,
1749 cwd=self.build_directory,
1750 env_mod=env_mod,
1751 )
1753 def build_impl(
1754 self,
1755 context: "BuildContext",
1756 manifest: "HighLevelManifest",
1757 **kwargs,
1758 ) -> None:
1759 self._ninja_support.run_ninja_build(context)
1761 def test_impl(
1762 self,
1763 context: "BuildContext",
1764 manifest: "HighLevelManifest",
1765 *,
1766 should_ignore_test_errors: bool = False,
1767 **kwargs,
1768 ) -> None:
1769 env_mod = EnvironmentModification(
1770 replacements=(("MESON_TESTTHREADS", f"{context.parallelization_limit()}"),),
1771 ).combine(self._default_meson_env())
1772 meson_args = []
1773 if not context.is_terse_build:
1774 meson_args.append("--verbose")
1775 with self.dump_logs_on_error("meson-logs/testlog.txt"):
1776 run_build_system_command(
1777 "meson",
1778 "test",
1779 *meson_args,
1780 env_mod=env_mod,
1781 cwd=self.build_directory,
1782 )
1784 def install_impl(
1785 self,
1786 context: "BuildContext",
1787 manifest: "HighLevelManifest",
1788 dest_dir: str,
1789 **kwargs,
1790 ) -> None:
1791 run_build_system_command(
1792 "meson",
1793 "install",
1794 "--destdir",
1795 dest_dir,
1796 cwd=self.build_directory,
1797 env_mod=self._default_meson_env(),
1798 )
1800 def clean_impl(
1801 self,
1802 context: "BuildContext",
1803 manifest: "HighLevelManifest",
1804 clean_helper: "CleanHelper",
1805 **kwargs,
1806 ) -> None:
1807 # `debputy` will handle all the cleanup for us by virtue of "out of source build"
1808 assert self.out_of_source_build
1811def _add_qmake_flag(options: list[str], envvar: str, *, include_cppflags: bool) -> None:
1812 value = os.environ.get(envvar)
1813 if value is None:
1814 return
1815 if include_cppflags:
1816 cppflags = os.environ.get("CPPFLAGS")
1817 if cppflags:
1818 value = f"{value} {cppflags}"
1820 options.append(f"QMAKE_{envvar}_RELEASE={value}")
1821 options.append(f"QMAKE_{envvar}_DEBUG={value}")
1824class ParsedGenericQmakeBuildRuleDefinition(
1825 OptionalInstallDirectly,
1826 OptionalInSourceBuild,
1827 OptionalBuildDirectory,
1828 OptionalTestRule,
1829):
1830 configure_args: NotRequired[list[str]]
1833class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule):
1835 __slots__ = ("configure_args", "_make_support")
1837 def __init__(
1838 self,
1839 parsed_data: "ParsedGenericQmakeBuildRuleDefinition",
1840 attribute_path: AttributePath,
1841 parser_context: Union[ParserContextData, "HighLevelManifest"],
1842 ) -> None:
1843 super().__init__(parsed_data, attribute_path, parser_context)
1844 self.configure_args = list(parsed_data.get("configure_args", []))
1845 self._make_support = MakefileSupport.from_build_system(self)
1847 @classmethod
1848 def characteristics(cls) -> BuildSystemCharacteristics:
1849 return BuildSystemCharacteristics(
1850 out_of_source_builds="supported-and-default",
1851 )
1853 @classmethod
1854 def auto_detect_build_system(
1855 cls,
1856 source_root: VirtualPath,
1857 *args,
1858 **kwargs,
1859 ) -> bool:
1860 return any(p.name.endswith(".pro") for p in source_root.iterdir)
1862 @classmethod
1863 def os_mkspec_mapping(cls) -> Mapping[str, str]:
1864 return {
1865 "linux": "linux-g++",
1866 "kfreebsd": "gnukfreebsd-g++",
1867 "hurd": "hurd-g++",
1868 }
1870 def qmake_command(self) -> str:
1871 raise NotImplementedError
1873 def configure_impl(
1874 self,
1875 context: "BuildContext",
1876 manifest: "HighLevelManifest",
1877 **kwargs,
1878 ) -> None:
1880 configure_args = [
1881 "-makefile",
1882 ]
1883 qmake_cmd = context.cross_tool(self.qmake_command())
1885 if context.is_cross_compiling:
1886 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"]
1887 os2mkspec = self.os_mkspec_mapping()
1888 try:
1889 spec = os2mkspec[host_os]
1890 except KeyError:
1891 _error(
1892 f'Sorry, `debputy` cannot cross build this package for "{host_os}".'
1893 f' Missing a "DEB OS -> qmake -spec <VALUE>" mapping.'
1894 )
1895 configure_args.append("-spec")
1896 configure_args.append(spec)
1898 _add_qmake_flag(configure_args, "CFLAGS", include_cppflags=True)
1899 _add_qmake_flag(configure_args, "CXXFLAGS", include_cppflags=True)
1900 _add_qmake_flag(configure_args, "LDFLAGS", include_cppflags=False)
1902 configure_args.append("QMAKE_STRIP=:")
1903 configure_args.append("PREFIX=/usr")
1905 if self.configure_args:
1906 substitution = self.substitution
1907 attr_path = self.attribute_path["configure_args"]
1908 configure_args.extend(
1909 substitution.substitute(v, attr_path[i].path)
1910 for i, v in enumerate(self.configure_args)
1911 )
1913 self.ensure_build_dir_exists()
1914 if not self.out_of_source_build:
1915 configure_args.append(self.relative_from_builddir_to_source())
1917 with self.dump_logs_on_error("config.log"):
1918 run_build_system_command(
1919 qmake_cmd,
1920 *configure_args,
1921 cwd=self.build_directory,
1922 )
1924 def build_impl(
1925 self,
1926 context: "BuildContext",
1927 manifest: "HighLevelManifest",
1928 **kwargs,
1929 ) -> None:
1930 self._make_support.run_make(context)
1932 def test_impl(
1933 self,
1934 context: "BuildContext",
1935 manifest: "HighLevelManifest",
1936 *,
1937 should_ignore_test_errors: bool = False,
1938 **kwargs,
1939 ) -> None:
1940 limit = context.parallelization_limit(support_zero_as_unlimited=True)
1941 testsuite_flags = [f"-j{limit}"] if limit else ["-j"]
1943 if not context.is_terse_build:
1944 testsuite_flags.append("--verbose")
1945 self._make_support.run_first_existing_target_if_any(
1946 context,
1947 # Order is deliberately inverse compared to debhelper (#924052)
1948 ["check", "test"],
1949 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}",
1950 "VERBOSE=1",
1951 )
1953 def install_impl(
1954 self,
1955 context: "BuildContext",
1956 manifest: "HighLevelManifest",
1957 dest_dir: str,
1958 **kwargs,
1959 ) -> None:
1960 enable_parallelization = not os.path.lexists(self.build_dir_path("libtool"))
1961 self._make_support.run_first_existing_target_if_any(
1962 context,
1963 ["install"],
1964 f"DESTDIR={dest_dir}",
1965 "AM_UPDATE_INFO_DIR=no",
1966 enable_parallelization=enable_parallelization,
1967 )
1969 def clean_impl(
1970 self,
1971 context: "BuildContext",
1972 manifest: "HighLevelManifest",
1973 clean_helper: "CleanHelper",
1974 **kwargs,
1975 ) -> None:
1976 if self.out_of_source_build:
1977 return
1978 self._make_support.run_first_existing_target_if_any(
1979 context,
1980 ["distclean", "realclean", "clean"],
1981 )
1984class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule):
1986 def qmake_command(self) -> str:
1987 return "qmake"
1990class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule):
1992 def qmake_command(self) -> str:
1993 return "qmake6"
1996@debputy_build_system(
1997 "make",
1998 MakefileBuildSystemRule,
1999 auto_detection_shadows_build_systems="debhelper",
2000 online_reference_documentation=reference_documentation(
2001 title="Make Build System",
2002 description=textwrap.dedent(
2003 """\
2004 Run a plain `make` file with nothing else.
2006 This build system will attempt to use `make` to leverage instructions
2007 in a makefile (such as, `Makefile` or `GNUMakefile`).
2009 By default, the makefile build system assumes it should use "in-source"
2010 build semantics. If needed be, an explicit `build-directory` can be
2011 provided if the `Makefile` is not in the source folder but instead in
2012 some other directory.
2013 """
2014 ),
2015 attributes=[
2016 documented_attr(
2017 "directory",
2018 textwrap.dedent(
2019 """\
2020 The directory from which to run make if it is not the source root
2022 This works like using `make -C DIRECTORY ...` (or `cd DIRECTORY && make ...`).
2023 """
2024 ),
2025 ),
2026 documented_attr(
2027 "build_target",
2028 textwrap.dedent(
2029 """\
2030 The target name to use for the "build" step.
2032 If omitted, `make` will be run without any explicit target leaving it to decide
2033 the default.
2034 """
2035 ),
2036 ),
2037 documented_attr(
2038 "test_target",
2039 textwrap.dedent(
2040 """\
2041 The target name to use for the "test" step.
2043 If omitted, `make check` or `make test` will be used if it looks like `make`
2044 will accept one of those targets. Otherwise, the step will be skipped.
2045 """
2046 ),
2047 ),
2048 documented_attr(
2049 "install_target",
2050 textwrap.dedent(
2051 """\
2052 The target name to use for the "install" step.
2054 If omitted, `make install` will be used if it looks like `make` will accept that target.
2055 Otherwise, the step will be skipped.
2056 """
2057 ),
2058 ),
2059 *docs_from(
2060 DebputyParsedContentStandardConditional,
2061 OptionalInstallDirectly,
2062 OptionalTestRule,
2063 BuildRuleParsedFormat,
2064 ),
2065 ],
2066 ),
2067)
2068class ParsedMakeBuildRuleDefinition(
2069 OptionalInstallDirectly,
2070 OptionalTestRule,
2071):
2072 directory: NotRequired[FileSystemExactMatchRule]
2073 build_target: NotRequired[str]
2074 test_target: NotRequired[str]
2075 install_target: NotRequired[str]
2078@debputy_build_system(
2079 "autoconf",
2080 AutoconfBuildSystemRule,
2081 auto_detection_shadows_build_systems=["debhelper", "make"],
2082 online_reference_documentation=reference_documentation(
2083 title="Autoconf Build System (`autoconf`)",
2084 description=textwrap.dedent(
2085 """\
2086 Run an autoconf-based build system as the upstream build system.
2088 This build rule will attempt to use autoreconf to update the `configure`
2089 script before running the `configure` script if needed. Otherwise, it
2090 follows the classic `./configure && make && make install` pattern.
2092 The build rule uses "out of source" builds by default since it is easier
2093 and more reliable for clean and makes it easier to support multiple
2094 builds (that is, two or more build systems for the same source package).
2095 This is in contract to `debhelper`, which defaults to "in source" builds
2096 for `autoconf`. If you need that behavior, please set
2097 `perform-in-source-build: true`.
2098 """
2099 ),
2100 attributes=[
2101 documented_attr(
2102 "configure_args",
2103 textwrap.dedent(
2104 """\
2105 Arguments to be passed to the `configure` script.
2106 """
2107 ),
2108 ),
2109 *docs_from(
2110 DebputyParsedContentStandardConditional,
2111 OptionalInstallDirectly,
2112 OptionalInSourceBuild,
2113 OptionalBuildDirectory,
2114 OptionalTestRule,
2115 BuildRuleParsedFormat,
2116 ),
2117 ],
2118 ),
2119)
2120class ParsedAutoconfBuildRuleDefinition(
2121 OptionalInstallDirectly,
2122 OptionalInSourceBuild,
2123 OptionalBuildDirectory,
2124 OptionalTestRule,
2125):
2126 configure_args: NotRequired[list[str]]
2129@debputy_build_system(
2130 "cmake",
2131 CMakeBuildSystemRule,
2132 auto_detection_shadows_build_systems=["debhelper", "make"],
2133 online_reference_documentation=reference_documentation(
2134 title="CMake Build System (`cmake`)",
2135 description=textwrap.dedent(
2136 """\
2137 Run an cmake-based build system as the upstream build system.
2139 The build rule uses "out of source" builds.
2140 """
2141 ),
2142 attributes=[
2143 documented_attr(
2144 "configure_args",
2145 textwrap.dedent(
2146 """\
2147 Arguments to be passed to the `cmake` command.
2148 """
2149 ),
2150 ),
2151 documented_attr(
2152 "target_build_system",
2153 textwrap.dedent(
2154 """\
2155 Which build system should `cmake` should delegate the build system to.
2156 In `cmake` terms, this is a Generator (the `-G` option).
2158 Supported options are:
2159 * `make` - GNU Make
2160 * `ninja` - Ninja
2161 """
2162 ),
2163 ),
2164 *docs_from(
2165 DebputyParsedContentStandardConditional,
2166 OptionalInstallDirectly,
2167 OptionalBuildDirectory,
2168 OptionalTestRule,
2169 BuildRuleParsedFormat,
2170 ),
2171 ],
2172 ),
2173)
2174class ParsedCMakeBuildRuleDefinition(
2175 OptionalInstallDirectly,
2176 OptionalBuildDirectory,
2177 OptionalTestRule,
2178):
2179 configure_args: NotRequired[list[str]]
2180 target_build_system: Literal["make", "ninja"]
2183@debputy_build_system(
2184 "meson",
2185 MesonBuildSystemRule,
2186 auto_detection_shadows_build_systems=["debhelper", "make"],
2187 online_reference_documentation=reference_documentation(
2188 title="Meson Build System (`meson`)",
2189 description=textwrap.dedent(
2190 """\
2191 Run an meson-based build system as the upstream build system.
2193 The build rule uses "out of source" builds.
2194 """
2195 ),
2196 attributes=[
2197 documented_attr(
2198 "configure_args",
2199 textwrap.dedent(
2200 """\
2201 Arguments to be passed to the `meson` command.
2202 """
2203 ),
2204 ),
2205 *docs_from(
2206 DebputyParsedContentStandardConditional,
2207 OptionalInstallDirectly,
2208 OptionalBuildDirectory,
2209 OptionalTestRule,
2210 BuildRuleParsedFormat,
2211 ),
2212 ],
2213 ),
2214)
2215class ParsedMesonBuildRuleDefinition(
2216 OptionalInstallDirectly,
2217 OptionalBuildDirectory,
2218 OptionalTestRule,
2219):
2220 configure_args: NotRequired[list[str]]
2223@debputy_build_system(
2224 "perl-build",
2225 PerlBuildBuildSystemRule,
2226 auto_detection_shadows_build_systems=[
2227 "debhelper",
2228 "make",
2229 "perl-makemaker",
2230 ],
2231 online_reference_documentation=reference_documentation(
2232 title='Perl "Build.PL" Build System (`perl-build`)',
2233 description=textwrap.dedent(
2234 """\
2235 Build using the `Build.PL` Build system used by some Perl packages.
2237 This build rule will attempt to use the `Build.PL` script to build the
2238 upstream code.
2239 """
2240 ),
2241 attributes=[
2242 documented_attr(
2243 "configure_args",
2244 textwrap.dedent(
2245 """\
2246 Arguments to be passed to the `Build.PL` script.
2247 """
2248 ),
2249 ),
2250 *docs_from(
2251 DebputyParsedContentStandardConditional,
2252 OptionalInstallDirectly,
2253 OptionalTestRule,
2254 BuildRuleParsedFormat,
2255 ),
2256 ],
2257 ),
2258)
2259class ParsedPerlBuildBuildRuleDefinition(
2260 OptionalInstallDirectly,
2261 OptionalTestRule,
2262):
2263 configure_args: NotRequired[list[str]]
2266@debputy_build_system(
2267 "debhelper",
2268 DebhelperBuildSystemRule,
2269 online_reference_documentation=reference_documentation(
2270 title="Debhelper Build System (`debhelper`)",
2271 description=textwrap.dedent(
2272 """\
2273 Delegate to a debhelper provided build system
2275 This build rule will attempt to use the `dh_auto_*` tools to build the
2276 upstream code. By default, `dh_auto_*` will use auto-detection to determine
2277 which build system they will use. This can be overridden by the
2278 `dh-build-system` attribute.
2279 """
2280 ),
2281 attributes=[
2282 documented_attr(
2283 "dh_build_system",
2284 textwrap.dedent(
2285 """\
2286 Which debhelper build system to use. This attribute is passed to
2287 the `dh_auto_*` commands as the `-S` parameter, so any value valid
2288 for that will be accepted.
2290 Note that many debhelper build systems require extra build
2291 dependencies before they can be used. Please consult the documentation
2292 of the relevant debhelper build system for details.
2293 """
2294 ),
2295 ),
2296 documented_attr(
2297 "configure_args",
2298 textwrap.dedent(
2299 """\
2300 Arguments to be passed to underlying configuration command
2301 (via `dh_auto_configure -- <configure-args`).
2302 """
2303 ),
2304 ),
2305 *docs_from(
2306 DebputyParsedContentStandardConditional,
2307 OptionalInstallDirectly,
2308 OptionalBuildDirectory,
2309 OptionalTestRule,
2310 BuildRuleParsedFormat,
2311 ),
2312 ],
2313 ),
2314)
2315class ParsedDebhelperBuildRuleDefinition(
2316 OptionalInstallDirectly,
2317 OptionalBuildDirectory,
2318 OptionalTestRule,
2319):
2320 configure_args: NotRequired[list[str]]
2321 dh_build_system: NotRequired[str]
2324@debputy_build_system(
2325 "perl-makemaker",
2326 PerlMakeMakerBuildSystemRule,
2327 auto_detection_shadows_build_systems=[
2328 "debhelper",
2329 "make",
2330 ],
2331 online_reference_documentation=reference_documentation(
2332 title='Perl "MakeMaker" Build System (`perl-makemaker`)',
2333 description=textwrap.dedent(
2334 """\
2335 Build using the "MakeMaker" Build system used by some Perl packages.
2337 This build rule will attempt to use the `Makefile.PL` script to build the
2338 upstream code.
2339 """
2340 ),
2341 attributes=[
2342 documented_attr(
2343 "configure_args",
2344 textwrap.dedent(
2345 """\
2346 Arguments to be passed to the `Makefile.PL` script.
2347 """
2348 ),
2349 ),
2350 *docs_from(
2351 DebputyParsedContentStandardConditional,
2352 OptionalInstallDirectly,
2353 OptionalTestRule,
2354 BuildRuleParsedFormat,
2355 ),
2356 ],
2357 ),
2358)
2359class ParsedPerlMakeMakerBuildRuleDefinition(
2360 OptionalInstallDirectly,
2361 OptionalTestRule,
2362):
2363 configure_args: NotRequired[list[str]]
2366@debputy_build_system(
2367 "qmake",
2368 QmakeBuildSystemRule,
2369 auto_detection_shadows_build_systems=[
2370 "debhelper",
2371 "make",
2372 # Open question, should this shadow "qmake6" and later?
2373 ],
2374 online_reference_documentation=reference_documentation(
2375 title='QT "qmake" Build System (`qmake`)',
2376 description=textwrap.dedent(
2377 """\
2378 Build using the "qmake" by QT.
2379 """
2380 ),
2381 attributes=[
2382 documented_attr(
2383 "configure_args",
2384 textwrap.dedent(
2385 """\
2386 Arguments to be passed to the `qmake` command.
2387 """
2388 ),
2389 ),
2390 *docs_from(
2391 DebputyParsedContentStandardConditional,
2392 OptionalInstallDirectly,
2393 OptionalInSourceBuild,
2394 OptionalBuildDirectory,
2395 OptionalTestRule,
2396 BuildRuleParsedFormat,
2397 ),
2398 ],
2399 ),
2400)
2401class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition):
2402 pass
2405@debputy_build_system(
2406 "qmake6",
2407 Qmake6BuildSystemRule,
2408 auto_detection_shadows_build_systems=[
2409 "debhelper",
2410 "make",
2411 ],
2412 online_reference_documentation=reference_documentation(
2413 title='QT "qmake6" Build System (`qmake6`)',
2414 description=textwrap.dedent(
2415 """\
2416 Build using the "qmake6" from the `qmake6` package. This is like the `qmake` system
2417 but is specifically for QT6.
2418 """
2419 ),
2420 attributes=[
2421 documented_attr(
2422 "configure_args",
2423 textwrap.dedent(
2424 """\
2425 Arguments to be passed to the `qmake6` command.
2426 """
2427 ),
2428 ),
2429 *docs_from(
2430 DebputyParsedContentStandardConditional,
2431 OptionalInstallDirectly,
2432 OptionalInSourceBuild,
2433 OptionalBuildDirectory,
2434 OptionalTestRule,
2435 BuildRuleParsedFormat,
2436 ),
2437 ],
2438 ),
2439)
2440class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition):
2441 pass
2444def _parse_default_environment(
2445 _name: str,
2446 parsed_data: EnvironmentSourceFormat,
2447 attribute_path: AttributePath,
2448 parser_context: ParserContextData,
2449) -> ManifestProvidedBuildEnvironment:
2450 return ManifestProvidedBuildEnvironment.from_environment_definition(
2451 parsed_data,
2452 attribute_path,
2453 parser_context,
2454 is_default=True,
2455 )
2458def _parse_build_environments(
2459 _name: str,
2460 parsed_data: list[NamedEnvironmentSourceFormat],
2461 attribute_path: AttributePath,
2462 parser_context: ParserContextData,
2463) -> list[ManifestProvidedBuildEnvironment]:
2464 return [
2465 ManifestProvidedBuildEnvironment.from_environment_definition(
2466 value,
2467 attribute_path[idx],
2468 parser_context,
2469 is_default=False,
2470 )
2471 for idx, value in enumerate(parsed_data)
2472 ]
2475def _handle_build_rules(
2476 _name: str,
2477 parsed_data: list[BuildRule],
2478 _attribute_path: AttributePath,
2479 _parser_context: ParserContextData,
2480) -> list[BuildRule]:
2481 return parsed_data