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