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

1import dataclasses 

2from enum import Enum 

3from typing import List, Callable, Optional, Sequence 

4 

5from debian.debian_support import DpkgArchTable 

6 

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 

13 

14 

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 

22 

23 

24class ManifestCondition(DebputyDispatchableType): 

25 __slots__ = () 

26 

27 def describe(self) -> str: 

28 raise NotImplementedError 

29 

30 def negated(self) -> "ManifestCondition": 

31 return NegatedManifestCondition(self) 

32 

33 def evaluate(self, context: ConditionContext) -> bool: 

34 raise NotImplementedError 

35 

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) 

49 

50 @classmethod 

51 def any_of(cls, conditions: "Sequence[ManifestCondition]") -> "ManifestCondition": 

52 return cls._manifest_group(_ConditionGroupMatchType.ANY_OF, conditions) 

53 

54 @classmethod 

55 def all_of(cls, conditions: "Sequence[ManifestCondition]") -> "ManifestCondition": 

56 return cls._manifest_group(_ConditionGroupMatchType.ALL_OF, conditions) 

57 

58 @classmethod 

59 def is_cross_building(cls) -> "ManifestCondition": 

60 return _IS_CROSS_BUILDING 

61 

62 @classmethod 

63 def can_execute_compiled_binaries(cls) -> "ManifestCondition": 

64 return _CAN_EXECUTE_COMPILED_BINARIES 

65 

66 @classmethod 

67 def run_build_time_tests(cls) -> "ManifestCondition": 

68 return _RUN_BUILD_TIME_TESTS 

69 

70 

71class NegatedManifestCondition(ManifestCondition): 

72 __slots__ = ("_condition",) 

73 

74 def __init__(self, condition: ManifestCondition) -> None: 

75 self._condition = condition 

76 

77 def negated(self) -> "ManifestCondition": 

78 return self._condition 

79 

80 def describe(self) -> str: 

81 return f"not ({self._condition.describe()})" 

82 

83 def evaluate(self, context: ConditionContext) -> bool: 

84 return not self._condition.evaluate(context) 

85 

86 

87class _ConditionGroupMatchType(Enum): 

88 ANY_OF = (any, "At least one of: [{conditions}]") 

89 ALL_OF = (all, "All of: [{conditions}]") 

90 

91 def describe(self, conditions: Sequence[ManifestCondition]) -> str: 

92 return self.value[1].format( 

93 conditions=", ".join(x.describe() for x in conditions) 

94 ) 

95 

96 def evaluate( 

97 self, conditions: Sequence[ManifestCondition], context: ConditionContext 

98 ) -> bool: 

99 return self.value[0](c.evaluate(context) for c in conditions) 

100 

101 

102class ManifestConditionGroup(ManifestCondition): 

103 __slots__ = ("match_type", "_conditions") 

104 

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 

112 

113 def describe(self) -> str: 

114 return self.match_type.describe(self._conditions) 

115 

116 def evaluate(self, context: ConditionContext) -> bool: 

117 return self.match_type.evaluate(self._conditions, context) 

118 

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 ) 

129 

130 

131class ArchMatchManifestConditionBase(ManifestCondition): 

132 __slots__ = ("_arch_spec", "_is_negated") 

133 

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 

137 

138 def negated(self) -> "ManifestCondition": 

139 return self.__class__(self._arch_spec, is_negated=not self._is_negated) 

140 

141 

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)}]' 

147 

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 

154 

155 

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)}]' 

161 

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 

174 

175 

176class BuildProfileMatch(ManifestCondition): 

177 __slots__ = ("_profile_spec", "_is_negated") 

178 

179 def __init__(self, profile_spec: str, *, is_negated: bool = False) -> None: 

180 self._profile_spec = profile_spec 

181 self._is_negated = is_negated 

182 

183 def negated(self) -> "ManifestCondition": 

184 return self.__class__(self._profile_spec, is_negated=not self._is_negated) 

185 

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}]" 

190 

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 

196 

197 

198@dataclasses.dataclass(frozen=True, slots=True) 

199class _SingletonCondition(ManifestCondition): 

200 description: str 

201 implementation: Callable[[ConditionContext], bool] 

202 

203 def describe(self) -> str: 

204 return self.description 

205 

206 def evaluate(self, context: ConditionContext) -> bool: 

207 return self.implementation(context) 

208 

209 

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 

215 

216 

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) 

221 

222_CAN_EXECUTE_COMPILED_BINARIES = _SingletonCondition( 

223 "Can run built binaries (natively or via transparent emulation)", 

224 _can_run_built_binaries, 

225) 

226 

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) 

231 

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) 

236 

237 

238del _SingletonCondition 

239del _can_run_built_binaries