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