Coverage for src/debputy/manifest_conditions.py: 65%
134 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
1import dataclasses
2from enum import Enum
3from typing import List, Callable, Optional, Sequence
5from debian.debian_support import DpkgArchTable
7from debputy._deb_options_profiles import DebBuildOptionsAndProfiles
8from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable
9from debputy.manifest_parser.base_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 build_env: DebBuildOptionsAndProfiles
19 substitution: Substitution
20 dpkg_architecture_variables: DpkgArchitectureBuildProcessValuesTable
21 dpkg_arch_query_table: DpkgArchTable
24class ManifestCondition(DebputyDispatchableType):
25 __slots__ = ()
27 def describe(self) -> str:
28 raise NotImplementedError
30 def negated(self) -> "ManifestCondition":
31 return NegatedManifestCondition(self)
33 def evaluate(self, context: ConditionContext) -> bool:
34 raise NotImplementedError
36 @classmethod
37 def _manifest_group(
38 cls,
39 match_type: "_ConditionGroupMatchType",
40 conditions: "Sequence[ManifestCondition]",
41 ) -> "ManifestCondition":
42 condition = conditions[0]
43 if ( 43 ↛ 47line 43 didn't jump to line 47
44 isinstance(condition, ManifestConditionGroup)
45 and condition.match_type == match_type
46 ):
47 return condition.extend(conditions[1:])
48 return ManifestConditionGroup(match_type, conditions)
50 @classmethod
51 def any_of(cls, conditions: "Sequence[ManifestCondition]") -> "ManifestCondition":
52 return cls._manifest_group(_ConditionGroupMatchType.ANY_OF, conditions)
54 @classmethod
55 def all_of(cls, conditions: "Sequence[ManifestCondition]") -> "ManifestCondition":
56 return cls._manifest_group(_ConditionGroupMatchType.ALL_OF, conditions)
58 @classmethod
59 def is_cross_building(cls) -> "ManifestCondition":
60 return _IS_CROSS_BUILDING
62 @classmethod
63 def can_execute_compiled_binaries(cls) -> "ManifestCondition":
64 return _CAN_EXECUTE_COMPILED_BINARIES
66 @classmethod
67 def run_build_time_tests(cls) -> "ManifestCondition":
68 return _RUN_BUILD_TIME_TESTS
71class NegatedManifestCondition(ManifestCondition):
72 __slots__ = ("_condition",)
74 def __init__(self, condition: ManifestCondition) -> None:
75 self._condition = condition
77 def negated(self) -> "ManifestCondition":
78 return self._condition
80 def describe(self) -> str:
81 return f"not ({self._condition.describe()})"
83 def evaluate(self, context: ConditionContext) -> bool:
84 return not self._condition.evaluate(context)
87class _ConditionGroupMatchType(Enum):
88 ANY_OF = (any, "At least one of: [{conditions}]")
89 ALL_OF = (all, "All of: [{conditions}]")
91 def describe(self, conditions: Sequence[ManifestCondition]) -> str:
92 return self.value[1].format(
93 conditions=", ".join(x.describe() for x in conditions)
94 )
96 def evaluate(
97 self, conditions: Sequence[ManifestCondition], context: ConditionContext
98 ) -> bool:
99 return self.value[0](c.evaluate(context) for c in conditions)
102class ManifestConditionGroup(ManifestCondition):
103 __slots__ = ("match_type", "_conditions")
105 def __init__(
106 self,
107 match_type: _ConditionGroupMatchType,
108 conditions: Sequence[ManifestCondition],
109 ) -> None:
110 self.match_type = match_type
111 self._conditions = conditions
113 def describe(self) -> str:
114 return self.match_type.describe(self._conditions)
116 def evaluate(self, context: ConditionContext) -> bool:
117 return self.match_type.evaluate(self._conditions, context)
119 def extend(
120 self,
121 conditions: Sequence[ManifestCondition],
122 ) -> "ManifestConditionGroup":
123 combined = list(self._conditions)
124 combined.extend(conditions)
125 return ManifestConditionGroup(
126 self.match_type,
127 combined,
128 )
131class ArchMatchManifestConditionBase(ManifestCondition):
132 __slots__ = ("_arch_spec", "_is_negated")
134 def __init__(self, arch_spec: List[str], *, is_negated: bool = False) -> None:
135 self._arch_spec = arch_spec
136 self._is_negated = is_negated
138 def negated(self) -> "ManifestCondition":
139 return self.__class__(self._arch_spec, is_negated=not self._is_negated)
142class SourceContextArchMatchManifestCondition(ArchMatchManifestConditionBase):
143 def describe(self) -> str:
144 if self._is_negated:
145 return f'architecture (for source package) matches *none* of [{", ".join(self._arch_spec)}]'
146 return f'architecture (for source package) matches any of [{", ".join(self._arch_spec)}]'
148 def evaluate(self, context: ConditionContext) -> bool:
149 arch = context.dpkg_architecture_variables.current_host_arch
150 match = context.dpkg_arch_query_table.architecture_is_concerned(
151 arch, self._arch_spec
152 )
153 return not match if self._is_negated else match
156class BinaryPackageContextArchMatchManifestCondition(ArchMatchManifestConditionBase):
157 def describe(self) -> str:
158 if self._is_negated:
159 return f'architecture (for binary package) matches *none* of [{", ".join(self._arch_spec)}]'
160 return f'architecture (for binary package) matches any of [{", ".join(self._arch_spec)}]'
162 def evaluate(self, context: ConditionContext) -> bool:
163 binary_package = context.binary_package
164 if binary_package is None:
165 raise RuntimeError(
166 "Condition only applies in the context of a BinaryPackage, but was evaluated"
167 " without one"
168 )
169 arch = binary_package.resolved_architecture
170 match = context.dpkg_arch_query_table.architecture_is_concerned(
171 arch, self._arch_spec
172 )
173 return not match if self._is_negated else match
176class BuildProfileMatch(ManifestCondition):
177 __slots__ = ("_profile_spec", "_is_negated")
179 def __init__(self, profile_spec: str, *, is_negated: bool = False) -> None:
180 self._profile_spec = profile_spec
181 self._is_negated = is_negated
183 def negated(self) -> "ManifestCondition":
184 return self.__class__(self._profile_spec, is_negated=not self._is_negated)
186 def describe(self) -> str:
187 if self._is_negated:
188 return f"DEB_BUILD_PROFILES matches *none* of [{self._profile_spec}]"
189 return f"DEB_BUILD_PROFILES matches any of [{self._profile_spec}]"
191 def evaluate(self, context: ConditionContext) -> bool:
192 match = active_profiles_match(
193 self._profile_spec, context.build_env.deb_build_profiles
194 )
195 return not match if self._is_negated else match
198@dataclasses.dataclass(frozen=True, slots=True)
199class _SingletonCondition(ManifestCondition):
200 description: str
201 implementation: Callable[[ConditionContext], bool]
203 def describe(self) -> str:
204 return self.description
206 def evaluate(self, context: ConditionContext) -> bool:
207 return self.implementation(context)
210def _can_run_built_binaries(context: ConditionContext) -> bool:
211 if not context.dpkg_architecture_variables.is_cross_compiling:
212 return True
213 # User / Builder asserted that we could even though we are cross-compiling, so we have to assume it is true
214 return "crossbuildcanrunhostbinaries" in context.build_env.deb_build_options
217_IS_CROSS_BUILDING = _SingletonCondition( 217 ↛ exitline 217 didn't jump to the function exit
218 "Cross Compiling (i.e., DEB_HOST_GNU_TYPE != DEB_BUILD_GNU_TYPE)",
219 lambda c: c.dpkg_architecture_variables.is_cross_compiling,
220)
222_CAN_EXECUTE_COMPILED_BINARIES = _SingletonCondition(
223 "Can run built binaries (natively or via transparent emulation)",
224 _can_run_built_binaries,
225)
227_RUN_BUILD_TIME_TESTS = _SingletonCondition( 227 ↛ exitline 227 didn't jump to the function exit
228 "Run build time tests",
229 lambda c: "nocheck" not in c.build_env.deb_build_options,
230)
232_BUILD_DOCS_BDO = _SingletonCondition(
233 "Build docs (nodocs not in DEB_BUILD_OPTIONS)",
234 lambda c: "nodocs" not in c.build_env.deb_build_options,
235)
238del _SingletonCondition
239del _can_run_built_binaries