Coverage for src/debputy/plugin/debputy/build_system_rules.py: 40%
676 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +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"-G{self.cmake_generator(self.target_build_system)}",
1387 ]
1388 if not context.is_terse_build:
1389 cmake_flags.append("-DCMAKE_VERBOSE_MAKEFILE=ON")
1390 if not context.should_run_tests:
1391 cmake_flags.append("-DBUILD_TESTING:BOOL=OFF")
1393 self._compiler_and_cross_flags(context, cmake_flags)
1395 if self.configure_args:
1396 substitution = self.substitution
1397 attr_path = self.attribute_path["configure_args"]
1398 cmake_flags.extend(
1399 substitution.substitute(v, attr_path[i].path)
1400 for i, v in enumerate(self.configure_args)
1401 )
1403 env_mod = self._default_cmake_env(context)
1404 if "CPPFLAGS" in os.environ:
1405 # CMake doesn't respect CPPFLAGS, see #653916.
1406 cppflags = os.environ["CPPFLAGS"]
1407 cflags = f"{os.environ.get('CFLAGS', '')} {cppflags}".lstrip()
1408 cxxflags = f"{os.environ.get('CXXFLAGS', '')} {cppflags}".lstrip()
1409 env_mod = env_mod.combine(
1410 # The debhelper build system never showed this delta, so people might find it annoying.
1411 EnvironmentModification(
1412 replacements=(
1413 ("CFLAGS", cflags),
1414 ("CXXFLAGS", cxxflags),
1415 )
1416 )
1417 )
1418 if "ASMFLAGS" not in os.environ and "ASFLAGS" in os.environ:
1419 env_mod = env_mod.combine(
1420 # The debhelper build system never showed this delta, so people might find it annoying.
1421 EnvironmentModification(
1422 replacements=(("ASMFLAGS", os.environ["ASFLAGS"]),),
1423 )
1424 )
1425 self.ensure_build_dir_exists()
1426 source_dir_from_build_dir = self.relative_from_builddir_to_source()
1428 with self.dump_logs_on_error(
1429 "CMakeCache.txt",
1430 "CMakeFiles/CMakeOutput.log",
1431 "CMakeFiles/CMakeError.log",
1432 ):
1433 run_build_system_command(
1434 "cmake",
1435 *cmake_flags,
1436 source_dir_from_build_dir,
1437 cwd=self.build_directory,
1438 env_mod=env_mod,
1439 )
1441 def build_impl(
1442 self,
1443 context: "BuildContext",
1444 manifest: "HighLevelManifest",
1445 **kwargs,
1446 ) -> None:
1447 if self.target_build_system == "make":
1448 make_flags = []
1449 if not context.is_terse_build:
1450 make_flags.append("VERBOSE=1")
1451 self._make_support.run_make(context, *make_flags)
1452 else:
1453 self._ninja_support.run_ninja_build(context)
1455 def test_impl(
1456 self,
1457 context: "BuildContext",
1458 manifest: "HighLevelManifest",
1459 **kwargs,
1460 ) -> None:
1461 env_mod = EnvironmentModification(
1462 replacements=(("CTEST_OUTPUT_ON_FAILURE", "1"),),
1463 )
1464 if self.target_build_system == "make":
1465 # Unlike make, CTest does not have "unlimited parallel" setting (-j implies
1466 # -j1). Therefore, we do not set "allow zero as unlimited" here.
1467 make_flags = [f"ARGS+=-j{context.parallelization_limit()}"]
1468 if not context.is_terse_build:
1469 make_flags.append("ARGS+=--verbose")
1470 self._make_support.run_first_existing_target_if_any(
1471 context,
1472 ["check", "test"],
1473 *make_flags,
1474 env_mod=env_mod,
1475 )
1476 else:
1477 self._ninja_support.run_ninja_test(context, env_mod=env_mod)
1479 limit = context.parallelization_limit(support_zero_as_unlimited=True)
1480 testsuite_flags = [f"-j{limit}"] if limit else ["-j"]
1482 if not context.is_terse_build:
1483 testsuite_flags.append("--verbose")
1484 self._make_support.run_first_existing_target_if_any(
1485 context,
1486 # Order is deliberately inverse compared to debhelper (#924052)
1487 ["check", "test"],
1488 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}",
1489 "VERBOSE=1",
1490 )
1492 def install_impl(
1493 self,
1494 context: "BuildContext",
1495 manifest: "HighLevelManifest",
1496 dest_dir: str,
1497 **kwargs,
1498 ) -> None:
1499 env_mod = EnvironmentModification(
1500 replacements=(
1501 ("LC_ALL", "C.UTF-8"),
1502 ("DESTDIR", dest_dir),
1503 )
1504 ).combine(self._default_cmake_env(context))
1505 run_build_system_command(
1506 "cmake",
1507 "--install",
1508 self.build_directory,
1509 env_mod=env_mod,
1510 )
1512 def clean_impl(
1513 self,
1514 context: "BuildContext",
1515 manifest: "HighLevelManifest",
1516 clean_helper: "CleanHelper",
1517 **kwargs,
1518 ) -> None:
1519 if self.out_of_source_build:
1520 return
1521 if self.target_build_system == "make":
1522 # Keep it here in case we change the `required` "out of source" to "supported-default"
1523 self._make_support.run_first_existing_target_if_any(
1524 context,
1525 ["distclean", "realclean", "clean"],
1526 )
1527 else:
1528 self._ninja_support.run_ninja_clean(context)
1531class MesonBuildSystemRule(StepBasedBuildSystemRule):
1533 __slots__ = (
1534 "configure_args",
1535 "_ninja_support",
1536 )
1538 def __init__(
1539 self,
1540 parsed_data: "ParsedMesonBuildRuleDefinition",
1541 attribute_path: AttributePath,
1542 parser_context: Union[ParserContextData, "HighLevelManifest"],
1543 ) -> None:
1544 super().__init__(parsed_data, attribute_path, parser_context)
1545 configure_args = [a for a in parsed_data.get("configure_args", [])]
1546 self.configure_args = configure_args
1547 self._ninja_support = NinjaBuildSupport.from_build_system(self)
1549 @classmethod
1550 def characteristics(cls) -> BuildSystemCharacteristics:
1551 return BuildSystemCharacteristics(
1552 out_of_source_builds="required",
1553 )
1555 @classmethod
1556 def auto_detect_build_system(
1557 cls,
1558 source_root: VirtualPath,
1559 *args,
1560 **kwargs,
1561 ) -> bool:
1562 return "meson.build" in source_root
1564 @staticmethod
1565 def _default_meson_env() -> EnvironmentModification:
1566 replacements = {
1567 "LC_ALL": "C.UTF-8",
1568 }
1569 if "DEB_PYTHON_INSTALL_LAYOUT" not in os.environ:
1570 replacements["DEB_PYTHON_INSTALL_LAYOUT"] = "deb"
1571 return EnvironmentModification(
1572 replacements=tuple((k, v) for k, v in replacements.items())
1573 )
1575 @classmethod
1576 def cmake_generator(cls, target_build_system: Literal["make", "ninja"]) -> str:
1577 cmake_generators = {
1578 "make": "Unix Makefiles",
1579 "ninja": "Ninja",
1580 }
1581 return cmake_generators[target_build_system]
1583 @staticmethod
1584 def _cross_flags(
1585 context: "BuildContext",
1586 meson_flags: List[str],
1587 ) -> None:
1588 if not context.is_cross_compiling:
1589 return
1590 # Needs a cross-file http://mesonbuild.com/Cross-compilation.html
1591 cross_files_dir = os.path.abspath(
1592 generated_content_dir(
1593 subdir_key="meson-cross-files",
1594 )
1595 )
1596 host_arch = context.dpkg_architecture_variables.current_host_arch
1597 cross_file = os.path.join(cross_files_dir, f"meson-cross-file-{host_arch}.conf")
1598 if not os.path.isfile(cross_file):
1599 env = os.environ
1600 if env.get("LC_ALL") != "C.UTF-8":
1601 env = dict(env)
1602 env["LC_ALL"] = "C.UTF-8"
1603 else:
1604 env = None
1605 subprocess.check_call(
1606 [
1607 "/usr/share/meson/debcrossgen",
1608 f"--arch={host_arch}",
1609 f"-o{cross_file}",
1610 ],
1611 stdout=subprocess.DEVNULL,
1612 env=env,
1613 )
1615 meson_flags.append("--cross-file")
1616 meson_flags.append(cross_file)
1618 def configure_impl(
1619 self,
1620 context: "BuildContext",
1621 manifest: "HighLevelManifest",
1622 **kwargs,
1623 ) -> None:
1624 meson_version = Version(
1625 subprocess.check_output(
1626 ["meson", "--version"],
1627 encoding="utf-8",
1628 ).strip()
1629 )
1630 dpkg_architecture_variables = context.dpkg_architecture_variables
1632 meson_flags = [
1633 "--wrap-mode=nodownload",
1634 "--buildtype=plain",
1635 "--prefix=/usr",
1636 "--sysconfdir=/etc",
1637 "--localstatedir=/var",
1638 f"--libdir=lib/{dpkg_architecture_variables.current_host_multiarch}",
1639 "--auto-features=enabled",
1640 ]
1641 if meson_version >= Version("1.2.0"):
1642 # There was a behavior change in Meson 1.2.0: previously
1643 # byte-compilation wasn't supported, but since 1.2.0 it is on by
1644 # default. We can only use this option to turn it off in versions
1645 # where the option exists.
1646 meson_flags.append("-Dpython.bytecompile=-1")
1648 self._cross_flags(context, meson_flags)
1650 if self.configure_args:
1651 substitution = self.substitution
1652 attr_path = self.attribute_path["configure_args"]
1653 meson_flags.extend(
1654 substitution.substitute(v, attr_path[i].path)
1655 for i, v in enumerate(self.configure_args)
1656 )
1658 env_mod = self._default_meson_env()
1660 self.ensure_build_dir_exists()
1661 source_dir_from_build_dir = self.relative_from_builddir_to_source()
1663 with self.dump_logs_on_error("meson-logs/meson-log.txt"):
1664 run_build_system_command(
1665 "meson",
1666 "setup",
1667 source_dir_from_build_dir,
1668 *meson_flags,
1669 cwd=self.build_directory,
1670 env_mod=env_mod,
1671 )
1673 def build_impl(
1674 self,
1675 context: "BuildContext",
1676 manifest: "HighLevelManifest",
1677 **kwargs,
1678 ) -> None:
1679 self._ninja_support.run_ninja_build(context)
1681 def test_impl(
1682 self,
1683 context: "BuildContext",
1684 manifest: "HighLevelManifest",
1685 **kwargs,
1686 ) -> None:
1687 env_mod = EnvironmentModification(
1688 replacements=(("MESON_TESTTHREADS", f"{context.parallelization_limit()}"),),
1689 ).combine(self._default_meson_env())
1690 meson_args = []
1691 if not context.is_terse_build:
1692 meson_args.append("--verbose")
1693 with self.dump_logs_on_error("meson-logs/testlog.txt"):
1694 run_build_system_command(
1695 "meson",
1696 "test",
1697 *meson_args,
1698 env_mod=env_mod,
1699 cwd=self.build_directory,
1700 )
1702 def install_impl(
1703 self,
1704 context: "BuildContext",
1705 manifest: "HighLevelManifest",
1706 dest_dir: str,
1707 **kwargs,
1708 ) -> None:
1709 run_build_system_command(
1710 "meson",
1711 "install",
1712 "--destdir",
1713 dest_dir,
1714 cwd=self.build_directory,
1715 env_mod=self._default_meson_env(),
1716 )
1718 def clean_impl(
1719 self,
1720 context: "BuildContext",
1721 manifest: "HighLevelManifest",
1722 clean_helper: "CleanHelper",
1723 **kwargs,
1724 ) -> None:
1725 # `debputy` will handle all the cleanup for us by virtue of "out of source build"
1726 assert self.out_of_source_build
1729def _add_qmake_flag(options: List[str], envvar: str, *, include_cppflags: bool) -> None:
1730 value = os.environ.get(envvar)
1731 if value is None:
1732 return
1733 if include_cppflags:
1734 cppflags = os.environ.get("CPPFLAGS")
1735 if cppflags:
1736 value = f"{value} {cppflags}"
1738 options.append(f"QMAKE_{envvar}_RELEASE={value}")
1739 options.append(f"QMAKE_{envvar}_DEBUG={value}")
1742class ParsedGenericQmakeBuildRuleDefinition(
1743 OptionalInstallDirectly,
1744 OptionalInSourceBuild,
1745 OptionalBuildDirectory,
1746):
1747 configure_args: NotRequired[List[str]]
1750class AbstractQmakeBuildSystemRule(StepBasedBuildSystemRule):
1752 __slots__ = ("configure_args", "_make_support")
1754 def __init__(
1755 self,
1756 parsed_data: "ParsedGenericQmakeBuildRuleDefinition",
1757 attribute_path: AttributePath,
1758 parser_context: Union[ParserContextData, "HighLevelManifest"],
1759 ) -> None:
1760 super().__init__(parsed_data, attribute_path, parser_context)
1761 configure_args = [a for a in parsed_data.get("configure_args", [])]
1762 self.configure_args = configure_args
1763 self._make_support = MakefileSupport.from_build_system(self)
1765 @classmethod
1766 def characteristics(cls) -> BuildSystemCharacteristics:
1767 return BuildSystemCharacteristics(
1768 out_of_source_builds="supported-and-default",
1769 )
1771 @classmethod
1772 def auto_detect_build_system(
1773 cls,
1774 source_root: VirtualPath,
1775 *args,
1776 **kwargs,
1777 ) -> bool:
1778 return any(p.name.endswith(".pro") for p in source_root.iterdir)
1780 @classmethod
1781 def os_mkspec_mapping(cls) -> Mapping[str, str]:
1782 return {
1783 "linux": "linux-g++",
1784 "kfreebsd": "gnukfreebsd-g++",
1785 "hurd": "hurd-g++",
1786 }
1788 def qmake_command(self) -> str:
1789 raise NotImplementedError
1791 def configure_impl(
1792 self,
1793 context: "BuildContext",
1794 manifest: "HighLevelManifest",
1795 **kwargs,
1796 ) -> None:
1798 configure_args = [
1799 "-makefile",
1800 ]
1801 qmake_cmd = context.cross_tool(self.qmake_command())
1803 if context.is_cross_compiling:
1804 host_os = context.dpkg_architecture_variables["DEB_HOST_ARCH_OS"]
1805 os2mkspec = self.os_mkspec_mapping()
1806 try:
1807 spec = os2mkspec[host_os]
1808 except KeyError:
1809 _error(
1810 f'Sorry, `debputy` cannot cross build this package for "{host_os}".'
1811 f' Missing a "DEB OS -> qmake -spec <VALUE>" mapping.'
1812 )
1813 configure_args.append("-spec")
1814 configure_args.append(spec)
1816 _add_qmake_flag(configure_args, "CFLAGS", include_cppflags=True)
1817 _add_qmake_flag(configure_args, "CXXFLAGS", include_cppflags=True)
1818 _add_qmake_flag(configure_args, "LDFLAGS", include_cppflags=False)
1820 configure_args.append("QMAKE_STRIP=:")
1821 configure_args.append("PREFIX=/usr")
1823 if self.configure_args:
1824 substitution = self.substitution
1825 attr_path = self.attribute_path["configure_args"]
1826 configure_args.extend(
1827 substitution.substitute(v, attr_path[i].path)
1828 for i, v in enumerate(self.configure_args)
1829 )
1831 self.ensure_build_dir_exists()
1832 if not self.out_of_source_build:
1833 configure_args.append(self.relative_from_builddir_to_source())
1835 with self.dump_logs_on_error("config.log"):
1836 run_build_system_command(
1837 qmake_cmd,
1838 *configure_args,
1839 cwd=self.build_directory,
1840 )
1842 def build_impl(
1843 self,
1844 context: "BuildContext",
1845 manifest: "HighLevelManifest",
1846 **kwargs,
1847 ) -> None:
1848 self._make_support.run_make(context)
1850 def test_impl(
1851 self,
1852 context: "BuildContext",
1853 manifest: "HighLevelManifest",
1854 **kwargs,
1855 ) -> None:
1856 limit = context.parallelization_limit(support_zero_as_unlimited=True)
1857 testsuite_flags = [f"-j{limit}"] if limit else ["-j"]
1859 if not context.is_terse_build:
1860 testsuite_flags.append("--verbose")
1861 self._make_support.run_first_existing_target_if_any(
1862 context,
1863 # Order is deliberately inverse compared to debhelper (#924052)
1864 ["check", "test"],
1865 f"TESTSUITEFLAGS={' '.join(testsuite_flags)}",
1866 "VERBOSE=1",
1867 )
1869 def install_impl(
1870 self,
1871 context: "BuildContext",
1872 manifest: "HighLevelManifest",
1873 dest_dir: str,
1874 **kwargs,
1875 ) -> None:
1876 enable_parallelization = not os.path.lexists(self.build_dir_path("libtool"))
1877 self._make_support.run_first_existing_target_if_any(
1878 context,
1879 ["install"],
1880 f"DESTDIR={dest_dir}",
1881 "AM_UPDATE_INFO_DIR=no",
1882 enable_parallelization=enable_parallelization,
1883 )
1885 def clean_impl(
1886 self,
1887 context: "BuildContext",
1888 manifest: "HighLevelManifest",
1889 clean_helper: "CleanHelper",
1890 **kwargs,
1891 ) -> None:
1892 if self.out_of_source_build:
1893 return
1894 self._make_support.run_first_existing_target_if_any(
1895 context,
1896 ["distclean", "realclean", "clean"],
1897 )
1900class QmakeBuildSystemRule(AbstractQmakeBuildSystemRule):
1902 def qmake_command(self) -> str:
1903 return "qmake"
1906class Qmake6BuildSystemRule(AbstractQmakeBuildSystemRule):
1908 def qmake_command(self) -> str:
1909 return "qmake6"
1912@debputy_build_system(
1913 "make",
1914 MakefileBuildSystemRule,
1915 auto_detection_shadows_build_systems="debhelper",
1916 online_reference_documentation=reference_documentation(
1917 title="Make Build System",
1918 description=textwrap.dedent(
1919 """\
1920 Run a plain `make` file with nothing else.
1922 This build system will attempt to use `make` to leverage instructions
1923 in a makefile (such as, `Makefile` or `GNUMakefile`).
1925 By default, the makefile build system assumes it should use "in-source"
1926 build semantics. If needed be, an explicit `build-directory` can be
1927 provided if the `Makefile` is not in the source folder but instead in
1928 some other directory.
1929 """
1930 ),
1931 attributes=[
1932 documented_attr(
1933 "directory",
1934 textwrap.dedent(
1935 """\
1936 The directory from which to run make if it is not the source root
1938 This works like using `make -C DIRECTORY ...` (or `cd DIRECTORY && make ...`).
1939 """
1940 ),
1941 ),
1942 documented_attr(
1943 "build_target",
1944 textwrap.dedent(
1945 """\
1946 The target name to use for the "build" step.
1948 If omitted, `make` will be run without any explicit target leaving it to decide
1949 the default.
1950 """
1951 ),
1952 ),
1953 documented_attr(
1954 "test_target",
1955 textwrap.dedent(
1956 """\
1957 The target name to use for the "test" step.
1959 If omitted, `make check` or `make test` will be used if it looks like `make`
1960 will accept one of those targets. Otherwise, the step will be skipped.
1961 """
1962 ),
1963 ),
1964 documented_attr(
1965 "install_target",
1966 textwrap.dedent(
1967 """\
1968 The target name to use for the "install" step.
1970 If omitted, `make install` will be used if it looks like `make` will accept that target.
1971 Otherwise, the step will be skipped.
1972 """
1973 ),
1974 ),
1975 *docs_from(
1976 DebputyParsedContentStandardConditional,
1977 OptionalInstallDirectly,
1978 BuildRuleParsedFormat,
1979 ),
1980 ],
1981 ),
1982)
1983class ParsedMakeBuildRuleDefinition(
1984 OptionalInstallDirectly,
1985):
1986 directory: NotRequired[FileSystemExactMatchRule]
1987 build_target: NotRequired[str]
1988 test_target: NotRequired[str]
1989 install_target: NotRequired[str]
1992@debputy_build_system(
1993 "autoconf",
1994 AutoconfBuildSystemRule,
1995 auto_detection_shadows_build_systems=["debhelper", "make"],
1996 online_reference_documentation=reference_documentation(
1997 title="Autoconf Build System (`autoconf`)",
1998 description=textwrap.dedent(
1999 """\
2000 Run an autoconf-based build system as the upstream build system.
2002 This build rule will attempt to use autoreconf to update the `configure`
2003 script before running the `configure` script if needed. Otherwise, it
2004 follows the classic `./configure && make && make install` pattern.
2006 The build rule uses "out of source" builds by default since it is easier
2007 and more reliable for clean and makes it easier to support multiple
2008 builds (that is, two or more build systems for the same source package).
2009 This is in contract to `debhelper`, which defaults to "in source" builds
2010 for `autoconf`. If you need that behavior, please set
2011 `perform-in-source-build: true`.
2012 """
2013 ),
2014 attributes=[
2015 documented_attr(
2016 "configure_args",
2017 textwrap.dedent(
2018 """\
2019 Arguments to be passed to the `configure` script.
2020 """
2021 ),
2022 ),
2023 *docs_from(
2024 DebputyParsedContentStandardConditional,
2025 OptionalInstallDirectly,
2026 OptionalInSourceBuild,
2027 OptionalBuildDirectory,
2028 BuildRuleParsedFormat,
2029 ),
2030 ],
2031 ),
2032)
2033class ParsedAutoconfBuildRuleDefinition(
2034 OptionalInstallDirectly,
2035 OptionalInSourceBuild,
2036 OptionalBuildDirectory,
2037):
2038 configure_args: NotRequired[List[str]]
2041@debputy_build_system(
2042 "cmake",
2043 CMakeBuildSystemRule,
2044 auto_detection_shadows_build_systems=["debhelper", "make"],
2045 online_reference_documentation=reference_documentation(
2046 title="CMake Build System (`cmake`)",
2047 description=textwrap.dedent(
2048 """\
2049 Run an cmake-based build system as the upstream build system.
2051 The build rule uses "out of source" builds.
2052 """
2053 ),
2054 attributes=[
2055 documented_attr(
2056 "configure_args",
2057 textwrap.dedent(
2058 """\
2059 Arguments to be passed to the `cmake` command.
2060 """
2061 ),
2062 ),
2063 documented_attr(
2064 "target_build_system",
2065 textwrap.dedent(
2066 """\
2067 Which build system should `cmake` should delegate the build system to.
2068 In `cmake` terms, this is a Generator (the `-G` option).
2070 Supported options are:
2071 * `make` - GNU Make
2072 * `ninja` - Ninja
2073 """
2074 ),
2075 ),
2076 *docs_from(
2077 DebputyParsedContentStandardConditional,
2078 OptionalInstallDirectly,
2079 OptionalBuildDirectory,
2080 BuildRuleParsedFormat,
2081 ),
2082 ],
2083 ),
2084)
2085class ParsedCMakeBuildRuleDefinition(
2086 OptionalInstallDirectly,
2087 OptionalBuildDirectory,
2088):
2089 configure_args: NotRequired[List[str]]
2090 target_build_system: Literal["make", "ninja"]
2093@debputy_build_system(
2094 "meson",
2095 MesonBuildSystemRule,
2096 auto_detection_shadows_build_systems=["debhelper", "make"],
2097 online_reference_documentation=reference_documentation(
2098 title="Meson Build System (`meson`)",
2099 description=textwrap.dedent(
2100 """\
2101 Run an meson-based build system as the upstream build system.
2103 The build rule uses "out of source" builds.
2104 """
2105 ),
2106 attributes=[
2107 documented_attr(
2108 "configure_args",
2109 textwrap.dedent(
2110 """\
2111 Arguments to be passed to the `meson` command.
2112 """
2113 ),
2114 ),
2115 *docs_from(
2116 DebputyParsedContentStandardConditional,
2117 OptionalInstallDirectly,
2118 OptionalBuildDirectory,
2119 BuildRuleParsedFormat,
2120 ),
2121 ],
2122 ),
2123)
2124class ParsedMesonBuildRuleDefinition(
2125 OptionalInstallDirectly,
2126 OptionalBuildDirectory,
2127):
2128 configure_args: NotRequired[List[str]]
2131@debputy_build_system(
2132 "perl-build",
2133 PerlBuildBuildSystemRule,
2134 auto_detection_shadows_build_systems=[
2135 "debhelper",
2136 "make",
2137 "perl-makemaker",
2138 ],
2139 online_reference_documentation=reference_documentation(
2140 title='Perl "Build.PL" Build System (`perl-build`)',
2141 description=textwrap.dedent(
2142 """\
2143 Build using the `Build.PL` Build system used by some Perl packages.
2145 This build rule will attempt to use the `Build.PL` script to build the
2146 upstream code.
2147 """
2148 ),
2149 attributes=[
2150 documented_attr(
2151 "configure_args",
2152 textwrap.dedent(
2153 """\
2154 Arguments to be passed to the `Build.PL` script.
2155 """
2156 ),
2157 ),
2158 *docs_from(
2159 DebputyParsedContentStandardConditional,
2160 OptionalInstallDirectly,
2161 BuildRuleParsedFormat,
2162 ),
2163 ],
2164 ),
2165)
2166class ParsedPerlBuildBuildRuleDefinition(
2167 OptionalInstallDirectly,
2168):
2169 configure_args: NotRequired[List[str]]
2172@debputy_build_system(
2173 "debhelper",
2174 DebhelperBuildSystemRule,
2175 online_reference_documentation=reference_documentation(
2176 title="Debhelper Build System (`debhelper`)",
2177 description=textwrap.dedent(
2178 """\
2179 Delegate to a debhelper provided build system
2181 This build rule will attempt to use the `dh_auto_*` tools to build the
2182 upstream code. By default, `dh_auto_*` will use auto-detection to determine
2183 which build system they will use. This can be overridden by the
2184 `dh-build-system` attribute.
2185 """
2186 ),
2187 attributes=[
2188 documented_attr(
2189 "dh_build_system",
2190 textwrap.dedent(
2191 """\
2192 Which debhelper build system to use. This attribute is passed to
2193 the `dh_auto_*` commands as the `-S` parameter, so any value valid
2194 for that will be accepted.
2196 Note that many debhelper build systems require extra build
2197 dependencies before they can be used. Please consult the documentation
2198 of the relevant debhelper build system for details.
2199 """
2200 ),
2201 ),
2202 documented_attr(
2203 "configure_args",
2204 textwrap.dedent(
2205 """\
2206 Arguments to be passed to underlying configuration command
2207 (via `dh_auto_configure -- <configure-args`).
2208 """
2209 ),
2210 ),
2211 *docs_from(
2212 DebputyParsedContentStandardConditional,
2213 OptionalInstallDirectly,
2214 OptionalBuildDirectory,
2215 BuildRuleParsedFormat,
2216 ),
2217 ],
2218 ),
2219)
2220class ParsedDebhelperBuildRuleDefinition(
2221 OptionalInstallDirectly,
2222 OptionalBuildDirectory,
2223):
2224 configure_args: NotRequired[List[str]]
2225 dh_build_system: NotRequired[str]
2228@debputy_build_system(
2229 "perl-makemaker",
2230 PerlMakeMakerBuildSystemRule,
2231 auto_detection_shadows_build_systems=[
2232 "debhelper",
2233 "make",
2234 ],
2235 online_reference_documentation=reference_documentation(
2236 title='Perl "MakeMaker" Build System (`perl-makemaker`)',
2237 description=textwrap.dedent(
2238 """\
2239 Build using the "MakeMaker" Build system used by some Perl packages.
2241 This build rule will attempt to use the `Makefile.PL` script to build the
2242 upstream code.
2243 """
2244 ),
2245 attributes=[
2246 documented_attr(
2247 "configure_args",
2248 textwrap.dedent(
2249 """\
2250 Arguments to be passed to the `Makefile.PL` script.
2251 """
2252 ),
2253 ),
2254 *docs_from(
2255 DebputyParsedContentStandardConditional,
2256 OptionalInstallDirectly,
2257 BuildRuleParsedFormat,
2258 ),
2259 ],
2260 ),
2261)
2262class ParsedPerlMakeMakerBuildRuleDefinition(
2263 OptionalInstallDirectly,
2264):
2265 configure_args: NotRequired[List[str]]
2268@debputy_build_system(
2269 "qmake",
2270 QmakeBuildSystemRule,
2271 auto_detection_shadows_build_systems=[
2272 "debhelper",
2273 "make",
2274 # Open question, should this shadow "qmake6" and later?
2275 ],
2276 online_reference_documentation=reference_documentation(
2277 title='QT "qmake" Build System (`qmake`)',
2278 description=textwrap.dedent(
2279 """\
2280 Build using the "qmake" by QT.
2281 """
2282 ),
2283 attributes=[
2284 documented_attr(
2285 "configure_args",
2286 textwrap.dedent(
2287 """\
2288 Arguments to be passed to the `qmake` command.
2289 """
2290 ),
2291 ),
2292 *docs_from(
2293 DebputyParsedContentStandardConditional,
2294 OptionalInstallDirectly,
2295 OptionalInSourceBuild,
2296 OptionalBuildDirectory,
2297 BuildRuleParsedFormat,
2298 ),
2299 ],
2300 ),
2301)
2302class ParsedQmakeBuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition):
2303 pass
2306@debputy_build_system(
2307 "qmake6",
2308 Qmake6BuildSystemRule,
2309 auto_detection_shadows_build_systems=[
2310 "debhelper",
2311 "make",
2312 ],
2313 online_reference_documentation=reference_documentation(
2314 title='QT "qmake6" Build System (`qmake6`)',
2315 description=textwrap.dedent(
2316 """\
2317 Build using the "qmake6" from the `qmake6` package. This is like the `qmake` system
2318 but is specifically for QT6.
2319 """
2320 ),
2321 attributes=[
2322 documented_attr(
2323 "configure_args",
2324 textwrap.dedent(
2325 """\
2326 Arguments to be passed to the `qmake6` command.
2327 """
2328 ),
2329 ),
2330 *docs_from(
2331 DebputyParsedContentStandardConditional,
2332 OptionalInstallDirectly,
2333 OptionalInSourceBuild,
2334 OptionalBuildDirectory,
2335 BuildRuleParsedFormat,
2336 ),
2337 ],
2338 ),
2339)
2340class ParsedQmake6BuildRuleDefinition(ParsedGenericQmakeBuildRuleDefinition):
2341 pass
2344def _parse_default_environment(
2345 _name: str,
2346 parsed_data: EnvironmentSourceFormat,
2347 attribute_path: AttributePath,
2348 parser_context: ParserContextData,
2349) -> ManifestProvidedBuildEnvironment:
2350 return ManifestProvidedBuildEnvironment.from_environment_definition(
2351 parsed_data,
2352 attribute_path,
2353 parser_context,
2354 is_default=True,
2355 )
2358def _parse_build_environments(
2359 _name: str,
2360 parsed_data: List[NamedEnvironmentSourceFormat],
2361 attribute_path: AttributePath,
2362 parser_context: ParserContextData,
2363) -> List[ManifestProvidedBuildEnvironment]:
2364 return [
2365 ManifestProvidedBuildEnvironment.from_environment_definition(
2366 value,
2367 attribute_path[idx],
2368 parser_context,
2369 is_default=False,
2370 )
2371 for idx, value in enumerate(parsed_data)
2372 ]
2375def _handle_build_rules(
2376 _name: str,
2377 parsed_data: List[BuildRule],
2378 _attribute_path: AttributePath,
2379 _parser_context: ParserContextData,
2380) -> List[BuildRule]:
2381 return parsed_data