debputy.build_support.build_context

src/debputy/build_support/build_context.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from typing import Optional, TYPE_CHECKING
from collections.abc import Mapping

from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable
from debputy.manifest_conditions import _run_build_time_tests

if TYPE_CHECKING:
    from debputy.commands.debputy_cmd.context import CommandContext


class BuildContext:
    @staticmethod
    def from_command_context(
        cmd_context: "CommandContext",
    ) -> "BuildContext":
        return BuildContextImpl(cmd_context)

    @property
    def deb_build_options(self) -> Mapping[str, str | None]:
        raise NotImplementedError

    def parallelization_limit(self, *, support_zero_as_unlimited: bool = False) -> int:
        """Parallelization limit of the build

        This is accessor that reads the `parallel` option from `DEB_BUILD_OPTIONS` with relevant
        fallback behavior.

        :param support_zero_as_unlimited: The debhelper framework allowed `0` to mean unlimited
          in some build systems. If the build system supports this, it should set this option
          to True, which will allow `0` as a possible return value. WHen this option is False
          (which is the default), `0` will be remapped to a high number to preserve the effect
          in spirit (said fallback number is also from `debhelper`).
        """
        limit = self.deb_build_options.get("parallel")
        if limit is None:
            return 1
        try:
            v = int(limit)
        except ValueError:
            return 1
        if v == 0 and not support_zero_as_unlimited:
            # debhelper allowed "0" to be used as unlimited in some cases. Preserve that feature
            # for callers that are prepared for it. For everyone else, remap 0 to an obscene number
            # that de facto has the same behavior
            #
            # The number is taken out of `cmake.pm` from `debhelper` to be "Bug compatible" with
            # debhelper on the fallback as well.
            return 999
        return v

    @property
    def is_terse_build(self) -> bool:
        """Whether the build is terse

        This is a shorthand for testing for `terse` in DEB_BUILD_OPTIONS
        """
        return "terse" in self.deb_build_options

    @property
    def is_cross_compiling(self) -> bool:
        """Whether the build is considered a cross build

        Note: Do **not** use this as indicator for whether tests should run. Use `should_run_tests` instead.
          To the naive eye, they seem like they overlap in functionality, but they do not. There are cross
          builds where tests can be run. Additionally, there are non-cross-builds where tests should be
          skipped.
        """
        return self.dpkg_architecture_variables.is_cross_compiling

    def cross_tool(self, command: str) -> str:
        if not self.is_cross_compiling:
            return command
        cross_prefix = self.dpkg_architecture_variables["DEB_HOST_GNU_TYPE"]
        return f"{cross_prefix}-{command}"

    @property
    def dpkg_architecture_variables(self) -> DpkgArchitectureBuildProcessValuesTable:
        raise NotImplementedError

    @property
    def should_run_tests(self) -> bool:
        return _run_build_time_tests(self.deb_build_options)


class BuildContextImpl(BuildContext):
    def __init__(
        self,
        cmd_context: "CommandContext",
    ) -> None:
        self._cmd_context = cmd_context

    @property
    def deb_build_options(self) -> Mapping[str, str | None]:
        return self._cmd_context.deb_build_options

    @property
    def dpkg_architecture_variables(self) -> DpkgArchitectureBuildProcessValuesTable:
        return self._cmd_context.dpkg_architecture_variables()