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