Coverage for src/debputy/manifest_conditions.py: 66%
142 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
2from enum import Enum
3from typing import List, Callable, Optional, Sequence, Any, Self, Mapping
5from debian.debian_support import DpkgArchTable
7from debputy._deb_options_profiles import DebBuildOptionsAndProfiles
8from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable
9from debputy.manifest_parser.tagging_types import DebputyDispatchableType
10from debputy.packages import BinaryPackage
11from debputy.substitution import Substitution
12from debputy.util import active_profiles_match
15@dataclasses.dataclass(slots=True, frozen=True)
16class ConditionContext:
17 binary_package: Optional[BinaryPackage]
18 deb_options_and_profiles: DebBuildOptionsAndProfiles
19 substitution: Substitution
20 dpkg_architecture_variables: DpkgArchitectureBuildProcessValuesTable
21 dpkg_arch_query_table: DpkgArchTable
23 def replace(self, /, **changes: Any) -> "Self":
24 return dataclasses.replace(self, **changes)
27class ManifestCondition(DebputyDispatchableType):
28 __slots__ = ()
30 def describe(self) -> str:
31 raise NotImplementedError
33 def negated(self) -> "ManifestCondition":
34 return NegatedManifestCondition(self)
36 def evaluate(self, context: ConditionContext) -> bool:
37 raise NotImplementedError
39 @classmethod
40 def _manifest_group(
41 cls,
42 match_type: "_ConditionGroupMatchType",
43 conditions: "Sequence[ManifestCondition]",
44 ) -> "ManifestCondition":
45 condition = conditions[0]
46 if ( 46 ↛ 50line 46 didn't jump to line 50
47 isinstance(condition, ManifestConditionGroup)
48 and condition.match_type == match_type
49 ):
50 return condition.extend(conditions[1:])
51 return ManifestConditionGroup(match_type, conditions)
53 @classmethod
54 def any_of(cls, conditions: "Sequence[ManifestCondition]") -> "ManifestCondition":
55 return cls._manifest_group(_ConditionGroupMatchType.ANY_OF, conditions)
57 @classmethod
58 def all_of(cls, conditions: "Sequence[ManifestCondition]") -> "ManifestCondition":
59 return cls._manifest_group(_ConditionGroupMatchType.ALL_OF, conditions)
61 @classmethod
62 def is_cross_building(cls) -> "ManifestCondition":
63 return _IS_CROSS_BUILDING
65 @classmethod
66 def can_execute_compiled_binaries(cls) -> "ManifestCondition":
67 return _CAN_EXECUTE_COMPILED_BINARIES
69 @classmethod
70 def run_build_time_tests(cls) -> "ManifestCondition":
71 return _RUN_BUILD_TIME_TESTS
74class NegatedManifestCondition(ManifestCondition):
75 __slots__ = ("_condition",)
77 def __init__(self, condition: ManifestCondition) -> None:
78 super().__init__()
79 self._condition = condition
81 def negated(self) -> "ManifestCondition":
82 return self._condition
84 def describe(self) -> str:
85 return f"not ({self._condition.describe()})"
87 def evaluate(self, context: ConditionContext) -> bool:
88 return not self._condition.evaluate(context)
91class _ConditionGroupMatchType(Enum):
92 ANY_OF = (any, "At least one of: [{conditions}]")
93 ALL_OF = (all, "All of: [{conditions}]")
95 def describe(self, conditions: Sequence[ManifestCondition]) -> str:
96 return self.value[1].format(
97 conditions=", ".join(x.describe() for x in conditions)
98 )
100 def evaluate(
101 self, conditions: Sequence[ManifestCondition], context: ConditionContext
102 ) -> bool:
103 return self.value[0](c.evaluate(context) for c in conditions)
106class ManifestConditionGroup(ManifestCondition):
107 __slots__ = ("match_type", "_conditions")
109 def __init__(
110 self,
111 match_type: _ConditionGroupMatchType,
112 conditions: Sequence[ManifestCondition],
113 ) -> None:
114 super().__init__()
115 self.match_type = match_type
116 self._conditions = conditions
118 def describe(self) -> str:
119 return self.match_type.describe(self._conditions)
121 def evaluate(self, context: ConditionContext) -> bool:
122 return self.match_type.evaluate(self._conditions, context)
124 def extend(
125 self,
126 conditions: Sequence[ManifestCondition],
127 ) -> "ManifestConditionGroup":
128 combined = list(self._conditions)
129 combined.extend(conditions)
130 return ManifestConditionGroup(
131 self.match_type,
132 combined,
133 )
136class ArchMatchManifestConditionBase(ManifestCondition):
137 __slots__ = ("_arch_spec", "_is_negated")
139 def __init__(self, arch_spec: List[str], *, is_negated: bool = False) -> None:
140 super().__init__()
141 self._arch_spec = arch_spec
142 self._is_negated = is_negated
144 def negated(self) -> "ManifestCondition":
145 return self.__class__(self._arch_spec, is_negated=not self._is_negated)
148class SourceContextArchMatchManifestCondition(ArchMatchManifestConditionBase):
149 def describe(self) -> str:
150 if self._is_negated:
151 return f'architecture (for source package) matches *none* of [{", ".join(self._arch_spec)}]'
152 return f'architecture (for source package) matches any of [{", ".join(self._arch_spec)}]'
154 def evaluate(self, context: ConditionContext) -> bool:
155 arch = context.dpkg_architecture_variables.current_host_arch
156 match = context.dpkg_arch_query_table.architecture_is_concerned(
157 arch, self._arch_spec
158 )
159 return not match if self._is_negated else match
162class BinaryPackageContextArchMatchManifestCondition(ArchMatchManifestConditionBase):
163 def describe(self) -> str:
164 if self._is_negated:
165 return f'architecture (for binary package) matches *none* of [{", ".join(self._arch_spec)}]'
166 return f'architecture (for binary package) matches any of [{", ".join(self._arch_spec)}]'
168 def evaluate(self, context: ConditionContext) -> bool:
169 binary_package = context.binary_package
170 if binary_package is None:
171 raise RuntimeError(
172 "Condition only applies in the context of a BinaryPackage, but was evaluated"
173 " without one"
174 )
175 arch = binary_package.resolved_architecture
176 match = context.dpkg_arch_query_table.architecture_is_concerned(
177 arch, self._arch_spec
178 )
179 return not match if self._is_negated else match
182class BuildProfileMatch(ManifestCondition):
183 __slots__ = ("_profile_spec", "_is_negated")
185 def __init__(self, profile_spec: str, *, is_negated: bool = False) -> None:
186 super().__init__()
187 self._profile_spec = profile_spec
188 self._is_negated = is_negated
190 def negated(self) -> "ManifestCondition":
191 return self.__class__(self._profile_spec, is_negated=not self._is_negated)
193 def describe(self) -> str:
194 if self._is_negated:
195 return f"DEB_BUILD_PROFILES matches *none* of [{self._profile_spec}]"
196 return f"DEB_BUILD_PROFILES matches any of [{self._profile_spec}]"
198 def evaluate(self, context: ConditionContext) -> bool:
199 match = active_profiles_match(
200 self._profile_spec, context.deb_options_and_profiles.deb_build_profiles
201 )
202 return not match if self._is_negated else match
205@dataclasses.dataclass(frozen=True, slots=True)
206class _SingletonCondition(ManifestCondition):
207 description: str
208 implementation: Callable[[ConditionContext], bool]
210 def describe(self) -> str:
211 return self.description
213 def evaluate(self, context: ConditionContext) -> bool:
214 return self.implementation(context)
217def _can_run_built_binaries(context: ConditionContext) -> bool:
218 if not context.dpkg_architecture_variables.is_cross_compiling:
219 return True
220 # User / Builder asserted that we could even though we are cross-compiling, so we have to assume it is true
221 return (
222 "crossbuildcanrunhostbinaries"
223 in context.deb_options_and_profiles.deb_build_options
224 )
227def _run_build_time_tests(deb_build_options: Mapping[str, Optional[str]]) -> bool:
228 return "nocheck" not in deb_build_options
231_IS_CROSS_BUILDING = _SingletonCondition( 231 ↛ exitline 231 didn't jump to the function exit
232 "Cross Compiling (i.e., DEB_HOST_GNU_TYPE != DEB_BUILD_GNU_TYPE)",
233 lambda c: c.dpkg_architecture_variables.is_cross_compiling,
234)
236_CAN_EXECUTE_COMPILED_BINARIES = _SingletonCondition(
237 "Can run built binaries (natively or via transparent emulation)",
238 _can_run_built_binaries,
239)
241_RUN_BUILD_TIME_TESTS = _SingletonCondition( 241 ↛ exitline 241 didn't jump to the function exit
242 "Run build time tests",
243 lambda c: _run_build_time_tests(c.deb_options_and_profiles.deb_build_options),
244)
246_BUILD_DOCS_BDO = _SingletonCondition(
247 "Build docs (nodocs not in DEB_BUILD_OPTIONS)",
248 lambda c: "nodocs" not in c.deb_options_and_profiles.deb_build_options,
249)
252del _SingletonCondition
253del _can_run_built_binaries