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
« 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."""
3from typing import cast
5import jinja2
6from cargo2rpm.metadata import FeatureFlags, Package
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
13MAX_SUMMARY_WIDTH = 80
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}
25RUST_PACKAGING_TARGET_MIN: dict[str, int] = {
26 "fedora": 24,
27 "mageia": 21,
28 "opensuse": 21,
29 "plain": 21,
30}
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
55 return max((min_dep, RUST_PACKAGING_TARGET_MIN[target]))
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
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.
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.
79 Returns:
80 Formatted string with cargo arguments.
82 """
83 if feature_flags.all_features:
84 cargo_args = " -a"
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)
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)
98 cargo_args_list = ["-n"] if feature_flags.no_default_features else []
100 if len(enabled_features) > 0:
101 cargo_args_list.append(f"-f {','.join(enabled_features)}")
103 cargo_args = " " + " ".join(cargo_args_list) if cargo_args_list else ""
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 []
109 cargo_args_list.append(f"-f {','.join(feature_flags.features)}")
110 cargo_args = " " + " ".join(cargo_args_list)
112 elif feature_flags.no_default_features:
113 cargo_args = " -n"
115 else:
116 cargo_args = ""
118 return cargo_args
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"
139 return rpm_summary
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 )
154 return env.get_template(filename)
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
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 )
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
177 if tests_run == ["none"]:
178 return conf_comments_to_spec_comments(cast(list, conf.tests.comments))
180 # the bcond only has associated comments if no tests are run
181 return []
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 ]
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.
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.
212 Returns:
213 Pre-formatted `%cargo_test` command.
215 """
216 parts = []
218 has_args = len(skip) > 0 or skip_exact
220 if len(skip) <= 1:
221 parts.append("%cargo_test")
222 end = ""
223 else:
224 parts.append("%{cargo_test")
225 end = "}"
227 parts.append(cargo_args)
229 if has_args or kind:
230 parts.append(" --")
232 if kind is not None:
233 parts.append(f" --{kind}")
235 if has_args:
236 parts.append(" --")
238 if skip_exact:
239 parts.append(" --exact")
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("}")
248 parts.append(end)
250 return "".join(line + "\n" for line in conf_comments_to_spec_comments(comments)) + "".join(parts)
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.
256 Arguments:
257 conf: Global rust2rpm configuration.
258 cargo_args: Pre-formatted arguments that are passed to all `%cargo_*`
259 macros.
261 Returns:
262 List of pre-formatted `%cargo_test` commands.
264 """
265 tests_run = [conf.tests.run] if isinstance(conf.tests.run, str) else conf.tests.run
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 ]
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 ]
289 commands = []
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))
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 []
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 )
314 return commands