conf: factor out rust2rpm.conf file parsing; add validation and tests

This commit is contained in:
Fabio Valentini 2023-03-02 19:35:58 +01:00
parent 539033b196
commit fb1944d558
No known key found for this signature in database
GPG key ID: 5AC5F572E5D410AF
8 changed files with 246 additions and 55 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ __pycache__/
/build/
/venv/
/.idea/
/.coverage

View file

@ -1,4 +1,3 @@
import configparser
import os
import pathlib
import sys
@ -7,6 +6,7 @@ from cargo2rpm.metadata import FeatureFlags
from rust2rpm import log
from rust2rpm.cli import get_parser
from rust2rpm.conf import Rust2RpmConf, Rust2RpmConfError
from rust2rpm.crate import process_project
from rust2rpm.cratesio import NoVersionsError
from rust2rpm.distgit import get_package_info
@ -84,30 +84,28 @@ def main():
packager = detect_packager()
conf = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
confs = conf.read([".rust2rpm.conf", "_rust2rpm.conf", "rust2rpm.conf"])
try:
# known file names (only in the current working directory)
filenames = [".rust2rpm.conf", "_rust2rpm.conf", "rust2rpm.conf"]
# clean up configuration files with deprecated names
if len(confs) > 1:
if not metadata.is_workspace():
distconf = Rust2RpmConf.load(filenames, args.target, metadata.packages[0].get_feature_names())
else:
distconf = Rust2RpmConf.load(filenames, args.target, set())
except FileExistsError:
log.error(
"More than one *rust2rpm.conf file is present in this directory. "
+ "Ensure that there is only one, and that it has the correct contents."
)
sys.exit(1)
if ".rust2rpm.conf" in confs and "rust2rpm.conf" not in confs:
os.rename(".rust2rpm.conf", "rust2rpm.conf")
log.info("Renamed deprecated, hidden .rust2rpm.conf file to rust2rpm.conf.")
except Rust2RpmConfError as exc:
log.error("Invalid rust2rpm configuration file:")
log.error(str(exc))
sys.exit(1)
if "_rust2rpm.conf" in confs and "rust2rpm.conf" not in confs:
os.rename("_rust2rpm.conf", "rust2rpm.conf")
log.info("Renamed deprecated _rust2rpm.conf file to rust2rpm.conf.")
if args.target not in conf:
conf.add_section(args.target)
conf_all_features = conf[args.target].getboolean("all-features")
if conf_all_features is False and args.all_features:
if distconf.all_features is False and args.all_features:
log.warn(
'Conflicting settings for enabling all features: The setting is "false"'
+ 'in rust2rpm.conf but it was enabled with the "--all-features" CLI flag.'
@ -122,13 +120,14 @@ def main():
patch_file_manual=patch_files[1],
license_files=license_files,
doc_files=doc_files,
distconf=conf[args.target],
feature_flags=FeatureFlags(all_features=(conf_all_features or args.all_features)),
distconf=distconf,
feature_flags=FeatureFlags(all_features=(distconf.all_features or args.all_features)),
relative_license_paths=args.relative_license_paths,
rpmautospec=args.rpmautospec,
auto_changelog_entry=args.auto_changelog_entry,
packager=packager,
)
else:
spec_contents = spec_render_workspace(
metadata=metadata,
@ -136,8 +135,8 @@ def main():
rpm_name=rpm_name,
license_files=license_files,
doc_files=doc_files,
distconf=conf[args.target],
feature_flags=FeatureFlags(all_features=(conf_all_features or args.all_features)),
distconf=distconf,
feature_flags=FeatureFlags(all_features=(distconf.all_features or args.all_features)),
rpmautospec=args.rpmautospec,
auto_changelog_entry=args.auto_changelog_entry,
packager=packager,

112
rust2rpm/conf.py Normal file
View file

@ -0,0 +1,112 @@
import configparser
import os
from typing import Optional
from rust2rpm import log
def to_list(s):
if not s:
return []
return list(sorted(filter(None, (l.strip() for l in s.splitlines()))))
class Rust2RpmConfError(ValueError):
pass
class Rust2RpmConf:
def __init__(
self,
*,
all_features: bool = False,
unwanted_features: list[str] = None,
buildrequires: list[str] = None,
testrequires: list[str] = None,
bin_requires: list[str] = None,
lib_requires: dict[Optional[str], list[str]] = None,
):
self.all_features: bool = all_features
self.unwanted_features: list[str] = unwanted_features or list()
self.buildrequires: list[str] = buildrequires or list()
self.testrequires: list[str] = testrequires or list()
self.bin_requires: list[str] = bin_requires or list()
self.lib_requires: dict[Optional[str], list[str]] = lib_requires or dict()
@staticmethod
def load(filenames: list[str], target: str, features: set[str]):
conf = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
confs = conf.read(filenames)
if len(confs) > 1:
raise FileExistsError
# clean up configuration files with deprecated names
if ".rust2rpm.conf" in confs:
os.rename(".rust2rpm.conf", "rust2rpm.conf")
log.info("Renamed deprecated, hidden .rust2rpm.conf file to rust2rpm.conf.")
if "_rust2rpm.conf" in confs:
os.rename("_rust2rpm.conf", "rust2rpm.conf")
log.info("Renamed deprecated _rust2rpm.conf file to rust2rpm.conf.")
# merge target-specific configuration with default configuration
if target not in conf:
conf.add_section(target)
merged = conf[target]
# validate configuration file
valid_targets = ["fedora", "mageia", "opensuse", "plain"]
valid_keys = [
"all-features",
"unwanted-features",
"buildrequires",
"testrequires",
"lib.requires",
"bin.requires",
]
for feature in features:
valid_keys.append(f"lib+{feature}.requires")
# check section names
for section in conf.keys():
if section != "DEFAULT" and section not in valid_targets:
raise Rust2RpmConfError(f"Invalid section: {section!r}")
# check setting keys
for key in merged.keys():
if key not in valid_keys:
raise Rust2RpmConfError(f"Invalid key: {key!r}")
# parse configuration and validate settings
settings = dict()
if all_features := merged.getboolean("all-features") and all_features is not None:
settings["all_features"] = all_features
if unwanted_features := merged.get("unwanted-features"):
settings["unwanted_features"] = to_list(unwanted_features)
for unwanted_feature in settings["unwanted_features"]:
if unwanted_feature not in features:
raise Rust2RpmConfError(f'Unrecognized "unwanted" feature: {unwanted_feature!r}')
if buildrequires := merged.get("buildrequires"):
settings["buildrequires"] = to_list(buildrequires)
if testrequires := merged.get("testrequires"):
settings["testrequires"] = to_list(testrequires)
if bin_requires := merged.get("bin.requires"):
settings["bin_requires"] = to_list(bin_requires)
if lib_requires := merged.get("lib.requires"):
if "lib_requires" not in settings.keys():
settings["lib_requires"] = dict()
settings["lib_requires"][None] = to_list(lib_requires)
for feature in features:
if lib_feature_requires := merged.get(f"lib+{feature}.requires"):
if "lib_requires" not in settings.keys():
settings["lib_requires"] = dict()
settings["lib_requires"][feature] = to_list(lib_feature_requires)
return Rust2RpmConf(**settings)

View file

@ -10,6 +10,7 @@ from cargo2rpm import rpm
import jinja2
from rust2rpm import __version__, log
from rust2rpm.conf import Rust2RpmConf
from rust2rpm.licensing import translate_license
from rust2rpm.metadata import guess_main_package, package_uses_rust_1_60_feature_syntax
@ -108,7 +109,7 @@ def spec_render_crate(
patch_file_manual: Optional[str],
license_files: list[str],
doc_files: list[str],
distconf,
distconf: Rust2RpmConf,
feature_flags: FeatureFlags,
relative_license_paths: bool,
rpmautospec: bool,
@ -149,25 +150,15 @@ def spec_render_crate(
rpm_requires = {feature: list(sorted(rpm.requires(package, feature))) for feature in features}
rpm_provides = {feature: rpm.provides(package, feature) for feature in features}
conf_buildrequires = to_list(distconf.get("buildrequires"))
conf_buildrequires.sort()
conf_test_requires = to_list(distconf.get("testrequires"))
conf_test_requires.sort()
conf_bin_requires = to_list(distconf.get("bin.requires"))
conf_bin_requires.sort()
conf_lib_requires = dict()
for feature in features:
if feature is None:
conf_key = "lib"
else:
conf_key = f"lib+{feature}"
conf_lib_requires[conf_key] = to_list(distconf.get(f"{conf_key}.requires"))
conf_lib_requires[conf_key] = distconf.lib_requires.get(feature) or list()
conf_unwanted_features = to_list(distconf.get("unwanted-features"))
for feature in conf_unwanted_features:
for feature in distconf.unwanted_features:
features.remove(feature)
if package_uses_rust_1_60_feature_syntax(package.features):
@ -219,9 +210,9 @@ def spec_render_crate(
"crate_version": package.version,
"crate_license": package.license,
# Parameters derived from rust2rpm.conf
"conf_buildrequires": conf_buildrequires,
"conf_test_requires": conf_test_requires,
"conf_bin_requires": conf_bin_requires,
"conf_buildrequires": distconf.buildrequires,
"conf_test_requires": distconf.testrequires,
"conf_bin_requires": distconf.bin_requires,
"conf_lib_requires": conf_lib_requires,
# Parameters derived from command-line flags
"cargo_args": cargo_args,
@ -260,7 +251,7 @@ def spec_render_workspace(
rpm_name: str,
license_files: list[str],
doc_files: list[str],
distconf,
distconf: Rust2RpmConf,
feature_flags: FeatureFlags,
rpmautospec: bool,
auto_changelog_entry: bool,
@ -291,15 +282,6 @@ def spec_render_workspace(
rpm_buildrequires = list(sorted(buildrequires))
rpm_test_requires = list(sorted(test_requires))
conf_buildrequires = to_list(distconf.get("buildrequires"))
conf_buildrequires.sort()
conf_test_requires = to_list(distconf.get("testrequires"))
conf_test_requires.sort()
conf_bin_requires = to_list(distconf.get("bin.requires"))
conf_bin_requires.sort()
if any(package_uses_rust_1_60_feature_syntax(package.features) for package in metadata.packages):
rust_packaging_dep = "cargo-rpm-macros >= 24"
else:
@ -346,9 +328,9 @@ def spec_render_workspace(
"rpm_binary_names": binaries,
"rpm_cdylib_package": is_cdylib,
# Parameters derived from rust2rpm.conf
"conf_buildrequires": conf_buildrequires,
"conf_test_requires": conf_test_requires,
"conf_bin_requires": conf_bin_requires,
"conf_buildrequires": distconf.buildrequires,
"conf_test_requires": distconf.testrequires,
"conf_bin_requires": distconf.bin_requires,
# Parameters derived from command-line flags
"cargo_args": cargo_args,
"use_rpmautospec": rpmautospec,

View file

@ -0,0 +1,25 @@
[DEFAULT]
unwanted-features =
v2_76
buildrequires =
pkgconfig(glib-2.0) >= 2.56
lib.requires =
pkgconfig(glib-2.0) >= 2.56
lib+v2_58.requires =
pkgconfig(glib-2.0) >= 2.58
lib+v2_60.requires =
pkgconfig(glib-2.0) >= 2.60
lib+v2_62.requires =
pkgconfig(glib-2.0) >= 2.62
lib+v2_64.requires =
pkgconfig(glib-2.0) >= 2.64
lib+v2_66.requires =
pkgconfig(glib-2.0) >= 2.66
lib+v2_68.requires =
pkgconfig(glib-2.0) >= 2.68
lib+v2_70.requires =
pkgconfig(glib-2.0) >= 2.70
lib+v2_72.requires =
pkgconfig(glib-2.0) >= 2.72
lib+v2_74.requires =
pkgconfig(glib-2.0) >= 2.74

View file

@ -0,0 +1,8 @@
[DEFAULT]
buildrequires =
pkgconfig(sqlcipher)
pkgconfig(sqlite3) >= 3.7.16
lib.requires =
pkgconfig(sqlite3) >= 3.7.16
lib+sqlcipher.requires =
pkgconfig(sqlcipher)

View file

@ -0,0 +1,63 @@
from importlib import resources
from typing import Optional
import pytest
from rust2rpm.conf import Rust2RpmConf
@pytest.mark.parametrize(
"filename,features,all_features,unwanted_features,buildrequires,testrequires,bin_requires,lib_requires",
[
(
"libsqlite3-sys-0.25.2.rust2rpm.conf",
{"sqlcipher"},
False,
list(),
["pkgconfig(sqlcipher)", "pkgconfig(sqlite3) >= 3.7.16"],
list(),
list(),
{None: ["pkgconfig(sqlite3) >= 3.7.16"], "sqlcipher": ["pkgconfig(sqlcipher)"]},
),
(
"glib-sys-0.17.2.rust2rpm.conf",
{"v2_58", "v2_60", "v2_62", "v2_64", "v2_66", "v2_68", "v2_70", "v2_72", "v2_74", "v2_76"},
False,
["v2_76"],
["pkgconfig(glib-2.0) >= 2.56"],
list(),
list(),
{
None: ["pkgconfig(glib-2.0) >= 2.56"],
"v2_58": ["pkgconfig(glib-2.0) >= 2.58"],
"v2_60": ["pkgconfig(glib-2.0) >= 2.60"],
"v2_62": ["pkgconfig(glib-2.0) >= 2.62"],
"v2_64": ["pkgconfig(glib-2.0) >= 2.64"],
"v2_66": ["pkgconfig(glib-2.0) >= 2.66"],
"v2_68": ["pkgconfig(glib-2.0) >= 2.68"],
"v2_70": ["pkgconfig(glib-2.0) >= 2.70"],
"v2_72": ["pkgconfig(glib-2.0) >= 2.72"],
"v2_74": ["pkgconfig(glib-2.0) >= 2.74"],
},
),
],
)
def test_rust2rpm_conf_load(
filename: str,
features: set[str],
all_features: bool,
unwanted_features: list[str],
buildrequires: list[str],
testrequires: list[str],
bin_requires: list[str],
lib_requires: dict[Optional[str], list[str]],
):
path = str(resources.files("rust2rpm.tests.samples").joinpath(filename))
conf = Rust2RpmConf.load(path, "fedora", features)
assert conf.all_features == all_features
assert conf.unwanted_features == unwanted_features
assert conf.buildrequires == buildrequires
assert conf.testrequires == testrequires
assert conf.bin_requires == bin_requires
assert conf.lib_requires == lib_requires

View file

@ -8,6 +8,7 @@ from cargo2rpm.metadata import Metadata, FeatureFlags
import pytest
from rust2rpm.cli import get_parser
from rust2rpm.conf import Rust2RpmConf
from rust2rpm.generator import to_list, spec_render_crate, spec_render_workspace
from rust2rpm.patching import drop_foreign_dependencies
from rust2rpm.utils import package_name_suffixed
@ -51,7 +52,7 @@ def test_spec_file_render_crate(filename: str, target: str, tmp_path: Path):
patch_file_manual=f"{crate}-patch2.diff",
license_files=["LIC1", "LIC2"],
doc_files=["DOC1", "DOC2"],
distconf={},
distconf=Rust2RpmConf(),
feature_flags=FeatureFlags(),
relative_license_paths=False,
rpmautospec=target == "fedora",
@ -64,7 +65,7 @@ def test_spec_file_render_crate(filename: str, target: str, tmp_path: Path):
fixture_path = resources.files("rust2rpm.tests.samples").joinpath(f"{crate_name_version}.{target}.spec")
if os.getenv("UPDATE_FIXTURES") == "1":
if os.getenv("UPDATE_FIXTURES") == "1": # pragma nocover
# helper mode to create test data
fixture_path.write_text(rendered)
@ -88,7 +89,7 @@ def test_spec_file_render_workspace(filename: str, target: str, tmp_path: Path):
rpm_name=crate,
license_files=["LIC1", "LIC2"],
doc_files=["DOC1", "DOC2"],
distconf={},
distconf=Rust2RpmConf(),
feature_flags=FeatureFlags(),
rpmautospec=target == "fedora",
auto_changelog_entry=True,
@ -100,7 +101,7 @@ def test_spec_file_render_workspace(filename: str, target: str, tmp_path: Path):
fixture_path = resources.files("rust2rpm.tests.samples").joinpath(f"{crate_name_version}.{target}.spec")
if os.getenv("UPDATE_FIXTURES") == "1":
if os.getenv("UPDATE_FIXTURES") == "1": # pragma nocover
# helper mode to create test data
fixture_path.write_text(rendered)
@ -189,7 +190,7 @@ def test_drop_foreign_dependencies(filename: str, features: set[str], expected:
toml_before = before_path.read_text().split("\n")
patched = drop_foreign_dependencies(toml_before, features) or toml_before
if os.getenv("UPDATE_FIXTURES") == "1":
if os.getenv("UPDATE_FIXTURES") == "1": # pragma nocover
# helper mode to create / update test fixtures
after_path.write_text("\n".join(patched))