Coverage for rust2rpm/generator/common.py: 94%

115 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-26 13:52 +0100

1"""Module containing common functionality for preparing template parameters.""" 

2 

3from typing import cast 

4 

5import jinja2 

6from cargo2rpm.metadata import FeatureFlags, Package 

7 

8from rust2rpm import log 

9from rust2rpm.conf import TomlConf 

10from rust2rpm.conf.toml import TestsComments, TestsSkip, TestsSkipExact, conf_comments_to_spec_comments 

11from rust2rpm.metadata import package_uses_rust_1_60_feature_syntax 

12 

13MAX_SUMMARY_WIDTH = 80 

14 

15RUST_PACKAGING_DEPS: dict[int, str] = { 

16 0: "rust-toolset", 

17 21: "rust-packaging >= 21", 

18 23: "rust-packaging >= 23", 

19 24: "cargo-rpm-macros >= 24", 

20 25: "cargo-rpm-macros >= 25", 

21 26: "cargo-rpm-macros >= 26", 

22} 

23 

24 

25RUST_PACKAGING_TARGET_MIN: dict[str, int] = { 

26 "fedora": 24, 

27 "mageia": 21, 

28 "opensuse": 21, 

29 "plain": 21, 

30} 

31 

32 

33def min_rust_packaging_dep( # noqa: PLR0913 

34 package: Package, 

35 target: str, 

36 vendor_tarball: str | None, 

37 *, 

38 is_bin: bool, 

39 is_cdylib: bool, 

40 cargo_install_lib: bool, 

41 cargo_install_bin: bool, 

42) -> int: 

43 """Compute the minimum version of rust-packaging supported by this package.""" 

44 if (not cargo_install_lib) or (not cargo_install_bin): 

45 min_dep = 26 

46 elif vendor_tarball: 

47 min_dep = 25 

48 elif package_uses_rust_1_60_feature_syntax(package.features): 

49 min_dep = 24 

50 elif is_bin or is_cdylib: 

51 min_dep = 23 

52 else: 

53 min_dep = 21 

54 

55 return max((min_dep, RUST_PACKAGING_TARGET_MIN[target])) 

56 

57 

58def normalize_spdx_expr(expr: str) -> str: 

59 """Normalze SPDX expression (i.e. replace deprecated "/" operator with "OR").""" 

60 if "/" in expr: 60 ↛ 61line 60 didn't jump to line 61 because the condition on line 60 was never true

61 log.info("Replacing deprecated '/' operator in SPDX license expression with 'OR'") 

62 parts = [part.strip() for part in expr.split("/")] 

63 return " OR ".join(parts) 

64 return expr 

65 

66 

67def cargo_args_from_flags( 

68 feature_flags: FeatureFlags, 

69 required_features: set[str], 

70 enabled_by_default: set[str], 

71) -> str: 

72 """Format cargo arguments for the Rust macros from rust-packaging. 

73 

74 Arguments: 

75 feature_flags: Feature flags from configuration. 

76 required_features: Features required by binary targets. 

77 enabled_by_default: Transitive closure of the "default" feature. 

78 

79 Returns: 

80 Formatted string with cargo arguments. 

81 

82 """ 

83 if feature_flags.all_features: 

84 cargo_args = " -a" 

85 

86 elif required_features: 

87 # remove required features that are already part of the enabled feature set 

88 for f in enabled_by_default: 

89 if f in required_features: 

90 required_features.remove(f) 

91 

92 # merge, de-duplicate, and re-sort lists of enabled features 

93 if features_enable := feature_flags.features: 

94 enabled_features = sorted(set.union(set(required_features), set(features_enable))) 

95 else: 

96 enabled_features = sorted(required_features) 

97 

98 cargo_args_list = ["-n"] if feature_flags.no_default_features else [] 

99 

100 if len(enabled_features) > 0: 

101 cargo_args_list.append(f"-f {','.join(enabled_features)}") 

102 

103 cargo_args = " " + " ".join(cargo_args_list) if cargo_args_list else "" 

104 

105 elif feature_flags.features: 

106 # list of features is already sorted when parsing the configuration file 

107 cargo_args_list = ["-n"] if feature_flags.no_default_features else [] 

108 

109 cargo_args_list.append(f"-f {','.join(feature_flags.features)}") 

110 cargo_args = " " + " ".join(cargo_args_list) 

111 

112 elif feature_flags.no_default_features: 

113 cargo_args = " -n" 

114 

115 else: 

116 cargo_args = "" 

117 

118 return cargo_args 

119 

120 

121def make_rpm_summary(tomlconf: TomlConf, package: Package) -> str: 

122 """Collect RPM summary from settings or crate metadata and print warnings if it's too long.""" 

123 if rpm_summary := tomlconf.package.summary: 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true

124 if len(rpm_summary) > MAX_SUMMARY_WIDTH: 

125 log.warn( 

126 f"The package Summary specified in rust2rpm.toml is too long (>{MAX_SUMMARY_WIDTH} characters).", 

127 ) 

128 elif rpm_summary := package.get_summary(): 

129 if len(rpm_summary) > MAX_SUMMARY_WIDTH: 

130 log.warn( 

131 f"Heuristics for generating a short Summary failed. The generated Summary string " 

132 f"is too long (>{MAX_SUMMARY_WIDTH} characters) and needs to be shortened " 

133 f"manually. This setting can be persisted with a 'package.summary' setting in " 

134 f"'rust2rpm.toml'.", 

135 ) 

136 else: 

137 rpm_summary = "# FIXME" 

138 

139 return rpm_summary 

140 

141 

142def spec_file_template(filename: str) -> jinja2.Template: 

143 """Load Jinja2 spec file template.""" 

144 env = jinja2.Environment( 

145 loader=jinja2.ChoiceLoader([jinja2.FileSystemLoader(["/"]), jinja2.PackageLoader("rust2rpm", "templates")]), 

146 extensions=["jinja2.ext.do"], 

147 trim_blocks=True, 

148 lstrip_blocks=True, 

149 autoescape=False, 

150 undefined=jinja2.StrictUndefined, 

151 keep_trailing_newline=True, 

152 ) 

153 

154 return env.get_template(filename) 

155 

156 

157def conf_to_bcond_check(conf: TomlConf) -> int: 

158 """Compute value of the "check" bcond based on the rust2rpm configuration.""" 

159 if tests_run := conf.tests.run: 

160 return 0 if "none" in tests_run else 1 

161 return 1 

162 

163 

164def conf_to_feature_flags(conf: TomlConf) -> FeatureFlags: 

165 """Compute feature flags based on the rust2rpm configuration.""" 

166 return FeatureFlags( 

167 all_features=conf.features.enable_all is True, 

168 no_default_features=conf.features.disable_default is True, 

169 features=sorted(set(conf.features.enable) if conf.features.enable else set()), 

170 ) 

171 

172 

173def conf_to_bcond_check_comments(conf: TomlConf) -> list[str]: 

174 """Compute comments associated with "%bcond check" based on the rust2rpm configuration.""" 

175 tests_run = [conf.tests.run] if isinstance(conf.tests.run, str) else conf.tests.run 

176 

177 if tests_run == ["none"]: 

178 return conf_comments_to_spec_comments(cast(list, conf.tests.comments)) 

179 

180 # the bcond only has associated comments if no tests are run 

181 return [] 

182 

183 

184def conf_to_bin_rename_commands(conf: TomlConf) -> list[str]: 

185 """Compute commands for renaming executables based on the rust2rpm configuration.""" 

186 return [ 

187 f"mv %{ buildroot} /%{ _bindir} /{old} %{ buildroot} /%{ _bindir} /{new}" 

188 for old, new in conf.package.bin_renames.items() 

189 ] 

190 

191 

192def format_cargo_test_command( 

193 *, 

194 kind: str | None, 

195 cargo_args: str, 

196 skip_exact: bool, 

197 skip: list[str], 

198 comments: list[str], 

199) -> str: 

200 """Prepare a pre-formatted %cargo_test command. 

201 

202 Arguments: 

203 kind: Test target kind ("lib", "bin", "doc", "tests", "bins"). 

204 cargo_args: Pre-formatted arguments that are passed to all `%cargo_*` 

205 macros. 

206 skip_exact: Toggle inclusion of the `--exact` flag. 

207 skip: List of names of tests (or substrings of tests) that are 

208 `--skip`ped. 

209 comments: List of comments associated with this `%cargo_test` macro 

210 invocation. 

211 

212 Returns: 

213 Pre-formatted `%cargo_test` command. 

214 

215 """ 

216 parts = [] 

217 

218 has_args = len(skip) > 0 or skip_exact 

219 

220 if len(skip) <= 1: 

221 parts.append("%cargo_test") 

222 end = "" 

223 else: 

224 parts.append("%{cargo_test") 

225 end = "}" 

226 

227 parts.append(cargo_args) 

228 

229 if has_args or kind: 

230 parts.append(" --") 

231 

232 if kind is not None: 

233 parts.append(f" --{kind}") 

234 

235 if has_args: 

236 parts.append(" --") 

237 

238 if skip_exact: 

239 parts.append(" --exact") 

240 

241 if len(skip) == 1: 

242 parts.append(f" --skip {skip[0]}") 

243 elif len(skip) > 1: 

244 parts.append(" %{shrink:\n") 

245 parts.extend(f" --skip {name}\n" for name in skip) 

246 parts.extend("}") 

247 

248 parts.append(end) 

249 

250 return "".join(line + "\n" for line in conf_comments_to_spec_comments(comments)) + "".join(parts) 

251 

252 

253def conf_to_cargo_test_commands(conf: TomlConf, cargo_args: str) -> list[str]: 

254 """Prepare a list of pre-formatted %cargo_test command based on the rust2rpm configuration. 

255 

256 Arguments: 

257 conf: Global rust2rpm configuration. 

258 cargo_args: Pre-formatted arguments that are passed to all `%cargo_*` 

259 macros. 

260 

261 Returns: 

262 List of pre-formatted `%cargo_test` commands. 

263 

264 """ 

265 tests_run = [conf.tests.run] if isinstance(conf.tests.run, str) else conf.tests.run 

266 

267 if tests_run == ["none"]: 

268 return [ 

269 format_cargo_test_command( 

270 kind=None, 

271 cargo_args=cargo_args, 

272 skip=[], 

273 skip_exact=False, 

274 comments=[], 

275 ), 

276 ] 

277 

278 if len(tests_run) == 0 or tests_run == ["all"]: 

279 return [ 

280 format_cargo_test_command( 

281 kind=None, 

282 cargo_args=cargo_args, 

283 skip=cast(list[str], conf.tests.skip), 

284 skip_exact=cast(bool, conf.tests.skip_exact), 

285 comments=cast(list[str], conf.tests.comments), 

286 ), 

287 ] 

288 

289 commands = [] 

290 

291 if isinstance(conf.tests.comments, list): 291 ↛ 294line 291 didn't jump to line 294 because the condition on line 291 was always true

292 commands.extend(conf_comments_to_spec_comments(conf.tests.comments)) 

293 

294 for run in tests_run: 

295 kind = run 

296 skip = getattr(conf.tests.skip, kind) if isinstance(conf.tests.skip, TestsSkip) else conf.tests.skip 

297 skip_exact = ( 

298 getattr(conf.tests.skip_exact, kind) 

299 if isinstance(conf.tests.skip_exact, TestsSkipExact) 

300 else conf.tests.skip_exact 

301 ) 

302 comments = getattr(conf.tests.comments, kind) if isinstance(conf.tests.comments, TestsComments) else [] 

303 

304 commands.append( 

305 format_cargo_test_command( 

306 kind=kind, 

307 cargo_args=cargo_args, 

308 skip=skip, 

309 skip_exact=skip_exact, 

310 comments=comments, 

311 ), 

312 ) 

313 

314 return commands