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