From 2cac5e5ad5ff5472923ce333bef59679612bbaa2 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Fri, 26 Oct 2018 11:20:13 +0200 Subject: [PATCH 07/13] split features into subpackages References: https://discussion.fedoraproject.org/t/rfc-new-crates-packaging-design-features-have-their-own-subpackages/563?u=ignatenkobrain Signed-off-by: Igor Gnatenko --- data/cargo.attr | 4 +- data/macros.cargo | 10 + rust2rpm/__main__.py | 11 +- rust2rpm/inspector.py | 22 ++- rust2rpm/metadata.py | 317 ++++++++++++++++---------------- rust2rpm/templates/main.spec | 142 +++++++------- test.py | 347 ++++------------------------------- 7 files changed, 298 insertions(+), 555 deletions(-) diff --git a/data/cargo.attr b/data/cargo.attr index 392a72b..4910b5c 100644 --- a/data/cargo.attr +++ b/data/cargo.attr @@ -1,3 +1,3 @@ -%__cargo_provides %{_bindir}/cargo-inspector --provides -%__cargo_requires %{_bindir}/cargo-inspector --requires +%__cargo_provides %{_bindir}/cargo-inspector --provides --feature=%{__cargo_feature_from_name -n %{name}} +%__cargo_requires %{_bindir}/cargo-inspector --requires --feature=%{__cargo_feature_from_name -n %{name}} %__cargo_path ^%{cargo_registry}/[^/]+/Cargo\\.toml$ diff --git a/data/macros.cargo b/data/macros.cargo index a0c456a..7fb025b 100644 --- a/data/macros.cargo +++ b/data/macros.cargo @@ -84,3 +84,13 @@ if %__cargo_is_bin; then \ %{__rm} %{buildroot}%{_prefix}/.crates.toml \ fi \ ) + +%__cargo_feature_from_name(n:) %{lua: +local name = rpm.expand("%{-n*}") +local feature = string.match(name, "^.+%+(.+)-devel$") +if feature == nil then + print() +else + print(feature) +end +} diff --git a/rust2rpm/__main__.py b/rust2rpm/__main__.py index f23ebbc..d19cb47 100644 --- a/rust2rpm/__main__.py +++ b/rust2rpm/__main__.py @@ -18,15 +18,19 @@ import requests import tqdm from . import Metadata, licensing +from .metadata import normalize_deps DEFAULT_EDITOR = "vi" XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")) CACHEDIR = os.path.join(XDG_CACHE_HOME, "rust2rpm") API_URL = "https://crates.io/api/v1/" JINJA_ENV = jinja2.Environment(loader=jinja2.ChoiceLoader([ - jinja2.FileSystemLoader(["/"]), - jinja2.PackageLoader("rust2rpm", "templates"), ]), - trim_blocks=True, lstrip_blocks=True) + jinja2.FileSystemLoader(["/"]), + jinja2.PackageLoader("rust2rpm", "templates"), + ]), + extensions=["jinja2.ext.do"], + trim_blocks=True, + lstrip_blocks=True) def get_default_target(): # TODO: add fallback for /usr/lib/os-release @@ -227,6 +231,7 @@ def main(): patch=args.patch, store=args.store_crate) + JINJA_ENV.globals["normalize_deps"] = normalize_deps template = JINJA_ENV.get_template("main.spec") if args.patch and len(diff) > 0: diff --git a/rust2rpm/inspector.py b/rust2rpm/inspector.py index 2d488b2..9e79e88 100644 --- a/rust2rpm/inspector.py +++ b/rust2rpm/inspector.py @@ -1,8 +1,8 @@ import argparse -import itertools import sys from . import Metadata +from .metadata import normalize_deps def main(): parser = argparse.ArgumentParser() @@ -10,18 +10,23 @@ def main(): group.add_argument("-n", "--name", action="store_true", help="Print name") group.add_argument("-v", "--version", action="store_true", help="Print version") group.add_argument("-t", "--target-kinds", action="store_true", help="Print target kinds") + group.add_argument("-l", "--list-features", action="store_true", help="Print features") group.add_argument("-P", "--provides", action="store_true", help="Print Provides") group.add_argument("-R", "--requires", action="store_true", help="Print Requires") group.add_argument("-BR", "--build-requires", action="store_true", help="Print BuildRequires") group.add_argument("-TR", "--test-requires", action="store_true", help="Print TestRequires") + parser.add_argument("-f", "--feature", help="Feature to work on") parser.add_argument("file", nargs="*", help="Path(s) to Cargo.toml") args = parser.parse_args() files = args.file or sys.stdin.readlines() + if not args.feature: + args.feature = None + def print_deps(deps): if len(deps) > 0: - print("\n".join(str(dep) for dep in deps)) + print("\n".join(sorted(normalize_deps(deps)))) for f in files: f = f.rstrip() @@ -32,17 +37,20 @@ def main(): print(md.version) if args.target_kinds: print("\n".join(set(tgt.kind for tgt in md.targets))) + if args.list_features: + for f in sorted(f for f in md.dependencies if f is not None): + print(f) if args.provides: - print_deps(md.provides) - if args.requires or args.build_requires: - print_deps(list(itertools.chain(md.requires, md.build_requires))) - if args.test_requires: - print_deps(md.test_requires) + print(md.provides(args.feature)) if args.requires: # Someone should own /usr/share/cargo/registry print("cargo") + print_deps(md.requires(args.feature)) if args.build_requires: print("rust-packaging") + print_deps(md.requires(args.feature or "default", resolve=True)) + if args.test_requires: + print_deps(md.dev_dependencies) if __name__ == "__main__": main() diff --git a/rust2rpm/metadata.py b/rust2rpm/metadata.py index 5adeb65..4929cdd 100644 --- a/rust2rpm/metadata.py +++ b/rust2rpm/metadata.py @@ -1,208 +1,207 @@ __all__ = ["Dependency", "Metadata"] -import itertools +import copy import json import subprocess -import sys import semantic_version as semver import rustcfg -class Target(object): - def __init__(self, kind, name): - self.kind = kind +class Target: + def __init__(self, name, kind): self.name = name + self.kind = kind def __repr__(self): - return "".format(self=self) - - -def _req_to_str(name, spec=None, feature=None): - f_part = "/{}".format(feature) if feature is not None else "" - basestr = "crate({}{})".format(name, f_part) - if spec is None: - return basestr - if spec.kind == spec.KIND_EQUAL: - spec.kind = spec.KIND_SHORTEQ - if spec.kind == spec.KIND_ANY: - if spec.spec == "": - # Just wildcard - return basestr - else: - # Wildcard in string - assert False, spec.spec - version = str(spec.spec).replace("-", "~") - return "{} {} {}".format(basestr, spec.kind, version) + return f"" -class Dependency(object): - def __init__(self, name, req, features=(), provides=False): +class Dependency: + def __init__(self, name, req=None, features=(), optional=False): self.name = name - self.spec = self._parse_req(req) + self.req = req self.features = features - self.provides = provides - if self.provides: - if len(self.spec.specs) > 1 or \ - (len(self.spec.specs) == 1 and self.spec.specs[0].kind != self.spec.specs[0].KIND_EQUAL): - raise Exception("Provides can't be applied to ranged version, {!r}".format(self.spec)) - - def __repr__(self): - if self.provides: - spec = self.spec.specs[0] - provs = [_req_to_str(self.name, spec)] - for feature in self.features: - provs.append(_req_to_str(self.name, spec, feature)) - return " and ".join(provs) - - reqs = [_req_to_str(self.name, spec=req) for req in self.spec.specs] - features = [_req_to_str(self.name, feature=feature) for feature in self.features] + self.optional = optional - use_rich = False - if len(reqs) > 1: - reqstr = "({})".format(" with ".join(reqs)) - use_rich = True - elif len(reqs) == 1: - reqstr = reqs[0] - else: - reqstr = "" - if len(features) > 0: - featurestr = " with ".join(features) - use_rich = True - else: - featurestr = "" - - if use_rich: - if reqstr and featurestr: - return "({} with {})".format(reqstr, featurestr) - elif reqstr and not featurestr: - return reqstr - elif not reqstr and featurestr: - return "({})".format(featurestr) - else: - assert False - else: - return reqstr + @classmethod + def from_json(cls, metadata): + features = set(metadata['features']) + if metadata['uses_default_features']: + features.add('default') + kwargs = {'name': metadata['name'], + 'req': metadata['req'], + 'optional': metadata['optional'], + 'features': features} + return cls(**kwargs) @staticmethod - def _parse_req(s): - if "*" in s and s != "*": - # XXX: https://github.com/rbarrois/python-semanticversion/issues/51 - s = "~{}".format(s.replace(".*", "", 1)) - if ".*" in s: - s = s.replace(".*", "") - spec = semver.Spec(s.replace(" ", "")) - parsed = [] + def _normalize_req(req): + if "*" in req and req != "*": + raise NotImplementedError(f"'*' is not supported: {req}") + spec = semver.Spec(req.replace(" ", "")) + reqs = [] for req in spec.specs: - ver = req.spec if req.kind == req.KIND_ANY: - parsed.append("*") + # Any means any continue + ver = req.spec + if ver.prerelease: + raise NotImplementedError(f"Pre-release requirement is not supported: {ver}") + if req.kind in (req.KIND_NEQ, req.KIND_EMPTY): + raise NotImplementedError(f"'!=' and empty kinds are not supported: {req}") coerced = semver.Version.coerce(str(ver)) - if req.kind in (req.KIND_CARET, req.KIND_TILDE): - if ver.prerelease: - # pre-release versions only match the same x.y.z - if ver.patch is not None: - upper = ver.next_patch() - elif ver.minor is not None: - upper = ver.next_minor() - else: - upper = ver.next_major() - elif req.kind == req.KIND_CARET: - if ver.major == 0: - if ver.minor is not None: - if ver.patch is None or ver.minor != 0: - upper = ver.next_minor() - else: - upper = ver.next_patch() + if req.kind == req.KIND_EQUAL: + req.kind = req.KIND_SHORTEQ + if req.kind in (req.KIND_CARET, req.KIND_COMPATIBLE): + if ver.major == 0: + if ver.minor is not None: + if ver.minor != 0 or ver.patch is None: + upper = ver.next_minor() else: - upper = ver.next_major() + upper = ver.next_patch() else: upper = ver.next_major() - elif req.kind == req.KIND_TILDE: - if ver.minor is None: - upper = ver.next_major() - else: - upper = ver.next_minor() else: - assert False - parsed.append(">={}".format(coerced)) - parsed.append("<{}".format(upper)) - elif req.kind == req.KIND_NEQ: - parsed.append(">{}".format(coerced)) - parsed.append("<{}".format(coerced)) - elif req.kind in (req.KIND_EQUAL, req.KIND_GT, req.KIND_GTE, req.KIND_LT, req.KIND_LTE): - parsed.append("{}{}".format(req.kind, coerced)) + upper = ver.next_major() + reqs.append((">=", coerced)) + reqs.append(("<", upper)) + elif req.kind == req.KIND_TILDE: + if ver.minor is None: + upper = ver.next_major() + else: + upper = ver.next_minor() + reqs.append((">=", coerced)) + reqs.append(("<", upper)) + elif req.kind in (req.KIND_SHORTEQ, + req.KIND_GT, + req.KIND_GTE, + req.KIND_LT, + req.KIND_LTE): + reqs.append((str(req.kind), coerced)) else: - assert False, req.kind - return semver.Spec(",".join(parsed)) + raise AssertionError(f"Found unhandled kind: {req.kind}") + return reqs -class Metadata(object): - def __init__(self): - self.name = None + @staticmethod + def _apply_reqs(name, reqs, feature=None): + fstr = f"/{feature}" if feature is not None else "" + cap = f"crate({name}{fstr})" + if not reqs: + return cap + deps = " with ".join(f"{cap} {op} {version}" for op, version in reqs) + if len(reqs) > 1: + return f"({deps})" + else: + return deps + + def normalize(self): + return [self._apply_reqs(self.name, self._normalize_req(self.req), feature) + for feature in self.features or (None,)] + + def __repr__(self): + return f"" + + def __str__(self): + return "\n".join(self.normalize()) + +class Metadata: + def __init__(self, name, version): + self.name = name + self.version = version self.license = None self.license_file = None self.readme = None self.description = None - self.version = None - self._targets = [] - self.provides = [] - self.requires = [] - self.build_requires = [] - self.test_requires = [] + self.targets = set() + self.dependencies = {} + self.dev_dependencies = set() @classmethod def from_json(cls, metadata): - self = cls() - md = metadata - self.name = md["name"] + self = cls(md["name"], md["version"]) + self.license = md["license"] self.license_file = md["license_file"] self.readme = md["readme"] self.description = md.get("description") - self.version = md["version"] - version = "={}".format(self.version) - - # Targets - self.targets = [Target(tgt["kind"][0], tgt["name"]) for tgt in md["targets"]] - - # Provides - # All optional dependencies are also features - # https://github.com/rust-lang/cargo/issues/4911 - features = itertools.chain((x["name"] for x in md["dependencies"] if x["optional"]), - md["features"]) - provides = Dependency(self.name, version, features=features, provides=True) - self.provides = str(provides).split(" and ") - - ev = rustcfg.Evaluator.platform() - - # Dependencies - for dep in md["dependencies"]: - kind = dep["kind"] - if kind is None: - requires = self.requires - elif kind == "build": - requires = self.build_requires - elif kind == "dev": - requires = self.test_requires - else: - raise ValueError("Unknown kind: {!r}, please report bug.".format(kind)) - target = dep["target"] - if target is None: - pass + # dependencies + build-dependencies → runtime + deps_by_name = {dep["name"]: Dependency.from_json(dep) + for dep in md["dependencies"] + if dep["kind"] != "dev"} + + deps_by_feature = {} + for feature, f_deps in md["features"].items(): + features = {None} + deps = set() + for dep in f_deps: + if dep in md["features"]: + features.add(dep) + else: + pkg, _, f = dep.partition("/") + dep = copy.deepcopy(deps_by_name[pkg]) + if f: + dep.features = {f} + deps.add(dep) + deps_by_feature[feature] = (features, deps) + + mandatory_deps = set() + for dep in deps_by_name.values(): + if dep.optional: + deps_by_feature[dep.name] = ({None}, {copy.deepcopy(dep)}) else: - cond = ev.parse_and_eval(target) - if not cond: - print(f'Dependency {dep["name"]} for target {target!r} is not needed, ignoring.', - file=sys.stderr) - continue + mandatory_deps.add(copy.deepcopy(dep)) + deps_by_feature[None] = (set(), mandatory_deps) + + if "default" not in deps_by_feature: + deps_by_feature["default"] = ({None}, set()) - requires.append(Dependency(dep["name"], dep["req"], features=dep["features"])) + self.dependencies = deps_by_feature + self.dev_dependencies = {Dependency.from_json(dep) + for dep in md["dependencies"] + if dep["kind"] == "dev"} + + self.targets = {Target(tgt["name"], tgt["kind"][0]) + for tgt in md["targets"]} return self @classmethod def from_file(cls, path): metadata = subprocess.check_output(["cargo", "read-manifest", - "--manifest-path={}".format(path)]) + f"--manifest-path={path}"]) return cls.from_json(json.loads(metadata)) + + @property + def all_dependencies(self): + return set().union(*(x[1] for x in self.dependencies.values())) + + def provides(self, feature=None): + if feature not in self.dependencies: + raise KeyError(f"Feature {feature!r} doesn't exist") + return Dependency(self.name, f"={self.version}", features={feature}) + + @classmethod + def _resolve(cls, deps_by_feature, feature): + all_features = set() + all_deps = set() + ff, dd = copy.deepcopy(deps_by_feature[feature]) + all_features |= ff + all_deps |= dd + for f in ff: + ff1, dd1 = cls._resolve(deps_by_feature, f) + all_features |= ff1 + all_deps |= dd1 + return all_features, all_deps + + def requires(self, feature=None, resolve=False): + if resolve: + return self._resolve(self.dependencies, feature)[1] + else: + features, deps = self.dependencies[feature] + fdeps = set(Dependency(self.name, f"={self.version}", features={feature}) + for feature in features) + return fdeps | deps + +def normalize_deps(deps): + return set().union(*(d.normalize() for d in deps)) diff --git a/rust2rpm/templates/main.spec b/rust2rpm/templates/main.spec index 7dbcc3f..0d9a80b 100644 --- a/rust2rpm/templates/main.spec +++ b/rust2rpm/templates/main.spec @@ -48,82 +48,102 @@ Patch0: {{ patch_file }} ExclusiveArch: %{rust_arches} BuildRequires: rust-packaging -{% if include_build_requires %} -{% if md.requires|length > 0 %} -# [dependencies] -{% for req in md.requires|sort(attribute="name") %} +{# We will put all non-optional and optional dependencies until + https://github.com/rust-lang/cargo/issues/5133 + is solved +{% set buildrequires = normalize_deps(md.requires("default", resolve=True))|sort %} +#} +{% set buildrequires = normalize_deps(md.all_dependencies)|sort %} +{% for req in buildrequires %} BuildRequires: {{ req }} {% endfor %} -{% endif %} -{% if md.build_requires|length > 0 %} -# [build-dependencies] -{% for req in md.build_requires|sort(attribute="name") %} -BuildRequires: {{ req }} -{% endfor %} -{% endif %} -{% if md.test_requires|length > 0 %} +{% set testrequires = normalize_deps(md.dev_dependencies)|sort %} +{% if testrequires|length > 0 %} %if %{with check} -# [dev-dependencies] -{% for req in md.test_requires|sort(attribute="name") %} + {% for req in testrequires %} BuildRequires: {{ req }} -{% endfor %} + {% endfor %} %endif {% endif %} -{% endif %} -%description +%global _description \ +{% if md.description is none %} %{summary}. +{% else %} +{{ md.description|wordwrap(wrapstring="\\\n")|trim }} +{% endif %} + +%description %{_description} {% if include_main %} %package -n %{crate} Summary: %{summary} -{% if rust_group is defined %} + {% if rust_group is defined %} Group: # FIXME -{% endif %} + {% endif %} %description -n %{crate} %{summary}. -{% endif %} +%files -n %{crate} + {% if md.license_file is not none %} +%license {{ md.license_file }} + {% endif %} + {% if md.readme is not none %} +%doc {{ md.readme }} + {% endif %} + {% for bin in bins %} +%{_bindir}/{{ bin.name }} + {% endfor %} + +{% endif -%} + {% if include_devel %} -%package devel + {% set features = md.dependencies.keys()|list %} + {% do features.remove(None) %} + {% do features.remove("default") %} + {% set features = features|sort %} + {% do features.insert(0, None) %} + {% do features.insert(1, "default") %} + {% for feature in features %} + {% set pkg = "-n %%{name}+%s-devel"|format(feature) if feature is not none else " devel" %} +%package {{ pkg }} Summary: %{summary} -{% if rust_group is defined %} + {% if rust_group is defined %} Group: {{ rust_group }} -{% endif %} + {% endif %} BuildArch: noarch -{% if include_provides %} -{% for prv in md.provides %} -Provides: {{ prv }} -{% endfor %} -{% endif %} -{% if include_requires %} + {% if include_provides %} +Provides: {{ md.provides(feature) }} + {% endif %} + {% if include_requires %} Requires: cargo -{% if md.requires|length > 0 %} -# [dependencies] -{% for req in md.requires|sort(attribute="name") %} -Requires: {{ req }} -{% endfor %} -{% endif %} -{% if md.build_requires|length > 0 %} -# [build-dependencies] -{% for req in md.build_requires|sort(attribute="name") %} + {% for req in md.requires(feature)|map("string")|sort %} Requires: {{ req }} -{% endfor %} -{% endif %} -{% endif %} + {% endfor %} + {% endif %} -%description devel -{% if md.description is none %} -%{summary}. -{% else %} -{{ md.description|wordwrap|trim }} -{% endif %} +%description {{ pkg }} %{_description} This package contains library source intended for building other packages -which use %{crate} from crates.io. +which use {% if feature is not none %}"{{ feature }}" feature of {% endif %}"%{crate}" crate. + +%files {{ pkg }} + {% if feature is none %} + {% if md.license_file is not none %} +%license {{ md.license_file }} + {% endif %} + {% if md.readme is not none %} +%doc {{ md.readme }} + {% endif %} +%{cargo_registry}/%{crate}-%{version}/ + {% else %} +%ghost %{cargo_registry}/%{crate}-%{version}/Cargo.toml + {% endif %} + + {% endfor %} +{% endif -%} -{% endif %} %prep {% if md.name != crate %} %autosetup -n %{real_crate}-%{version} -p1 @@ -143,29 +163,5 @@ which use %{crate} from crates.io. %cargo_test %endif -{% if include_main %} -%files -n %{crate} -{% if md.license_file is not none %} -%license {{ md.license_file }} -{% endif %} -{% if md.readme is not none %} -%doc {{ md.readme }} -{% endif %} -{% for bin in bins %} -%{_bindir}/{{ bin.name }} -{% endfor %} - -{% endif %} -{% if include_devel %} -%files devel -{% if md.license_file is not none %} -%license {{ md.license_file }} -{% endif %} -{% if md.readme is not none %} -%doc {{ md.readme }} -{% endif %} -%{cargo_registry}/%{crate}-%{version}/ - -{% endif %} %changelog {% include target ~ "-changelog.spec.inc" %} diff --git a/test.py b/test.py index b856fdd..30263b4 100644 --- a/test.py +++ b/test.py @@ -1,318 +1,43 @@ -import os -import shutil -import subprocess -import sys -import tempfile -import textwrap - import pytest import rust2rpm -DUMMY_LIB = """ -pub fn say_hello() { - println!("Hello, World!"); -} -""" -DEPGEN = os.path.join(os.path.dirname(__file__), "cargodeps.py") - - -@pytest.mark.parametrize("req, features, rpmdep", [ - ("=1.0.0", [], - "crate(test) = 1.0.0"), - ("=1.0.0", ["feature"], - "(crate(test) = 1.0.0 with crate(test/feature))"), - (">=1.0.0,<2.0.0", [], +@pytest.mark.parametrize("req, rpmdep", [ + ("^1.2.3", + "(crate(test) >= 1.2.3 with crate(test) < 2.0.0)"), + ("^1.2", + "(crate(test) >= 1.2.0 with crate(test) < 2.0.0)"), + ("^1", "(crate(test) >= 1.0.0 with crate(test) < 2.0.0)"), - (">=1.0.0,<2.0.0", ["feature"], - "((crate(test) >= 1.0.0 with crate(test) < 2.0.0) with crate(test/feature))"), + ("^0.2.3", + "(crate(test) >= 0.2.3 with crate(test) < 0.3.0)"), + ("^0.2", + "(crate(test) >= 0.2.0 with crate(test) < 0.3.0)"), + ("^0.0.3", + "(crate(test) >= 0.0.3 with crate(test) < 0.0.4)"), + ("^0.0", + "(crate(test) >= 0.0.0 with crate(test) < 0.1.0)"), + ("^0", + "(crate(test) >= 0.0.0 with crate(test) < 1.0.0)"), + ("~1.2.3", + "(crate(test) >= 1.2.3 with crate(test) < 1.3.0)"), + ("~1.2", + "(crate(test) >= 1.2.0 with crate(test) < 1.3.0)"), + ("~1", + "(crate(test) >= 1.0.0 with crate(test) < 2.0.0)"), + ("*", + "crate(test)"), + (">= 1.2.0", + "crate(test) >= 1.2.0"), + ("> 1", + "crate(test) > 1.0.0"), + ("< 2", + "crate(test) < 2.0.0"), + ("= 1.2.3", + "crate(test) = 1.2.3"), + (">= 1.2, < 1.5", + "(crate(test) >= 1.2.0 with crate(test) < 1.5.0)"), ]) -def test_dependency(req, features, rpmdep): - dep = rust2rpm.Dependency("test", req, features) +def test_dependency(req, rpmdep): + dep = rust2rpm.Dependency("test", req) assert str(dep) == rpmdep - -@pytest.fixture -def cargo_toml(request): - def make_cargo_toml(contents): - toml = os.path.join(tmpdir, "Cargo.toml") - with open(toml, "w") as fobj: - fobj.write(textwrap.dedent(contents)) - return toml - - tmpdir = tempfile.mkdtemp(prefix="cargo-deps-") - srcdir = os.path.join(tmpdir, "src") - os.mkdir(srcdir) - with open(os.path.join(srcdir, "lib.rs"), "w") as fobj: - fobj.write(DUMMY_LIB) - - def finalize(): - shutil.rmtree(tmpdir) - request.addfinalizer(finalize) - - return make_cargo_toml - -@pytest.mark.parametrize("toml, provides, requires", [ - - # Basic provides - (""" - [package] - name = "hello" - version = "0.0.0" - """, - ["crate(hello) = 0.0.0"], - []), - - # Basic provides for feature - (""" - [package] - name = "hello" - version = "1.2.3" - - [features] - color = [] - """, - ["crate(hello) = 1.2.3", - "crate(hello/color) = 1.2.3"], - []), - - # Provides for optional dependencies - (""" - [package] - name = "hello" - version = "1.2.3" - - [dependencies] - non_optional = "1" - serde = { version = "1", optional = true } - rand = { version = "0.4", optional = true } - - [features] - std = [] - v1 = ["rand"] - """, - ["crate(hello) = 1.2.3", - "crate(hello/rand) = 1.2.3", - "crate(hello/serde) = 1.2.3", - "crate(hello/std) = 1.2.3", - "crate(hello/v1) = 1.2.3"], - ["(crate(non_optional) >= 1.0.0 with crate(non_optional) < 2.0.0)", - "(crate(rand) >= 0.4.0 with crate(rand) < 0.5.0)", - "(crate(serde) >= 1.0.0 with crate(serde) < 2.0.0)"]), - - # Caret requirements - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^0" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 0.0.0 with crate(libc) < 1.0.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^0.0" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 0.0.0 with crate(libc) < 0.1.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^0.0.3" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 0.0.3 with crate(libc) < 0.0.4)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^0.2.3" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 0.2.3 with crate(libc) < 0.3.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^1" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.0.0 with crate(libc) < 2.0.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^1.2" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.2.0 with crate(libc) < 2.0.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "^1.2.3" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.2.3 with crate(libc) < 2.0.0)"]), - - # Tilde requirements - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "~1" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.0.0 with crate(libc) < 2.0.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "~1.2" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.2.0 with crate(libc) < 1.3.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "~1.2.3" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.2.3 with crate(libc) < 1.3.0)"]), - - # Wildcard requirements - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "*" - """, - ["crate(hello) = 0.0.0"], - ["crate(libc)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "1.*" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.0.0 with crate(libc) < 2.0.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "1.2.*" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.2.0 with crate(libc) < 1.3.0)"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "1.*.*" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.0.0 with crate(libc) < 2.0.0)"]), - - # Inequality requirements - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = ">= 1.2.0" - """, - ["crate(hello) = 0.0.0"], - ["crate(libc) >= 1.2.0"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "> 1" - """, - ["crate(hello) = 0.0.0"], - ["crate(libc) > 1.0.0"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "< 2" - """, - ["crate(hello) = 0.0.0"], - ["crate(libc) < 2.0.0"]), - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = "= 1.2.3" - """, - ["crate(hello) = 0.0.0"], - ["crate(libc) = 1.2.3"]), - - # Multiple requirements - (""" - [package] - name = "hello" - version = "0.0.0" - - [dependencies] - libc = ">= 1.2, < 1.5" - """, - ["crate(hello) = 0.0.0"], - ["(crate(libc) >= 1.2.0 with crate(libc) < 1.5.0)"]), - - # Pre-release requirements - (""" - [package] - name = "hello" - version = "0.0.0-alpha" - - [dependencies] - foo-bar = "1.2.3-beta" - """, - ["crate(hello) = 0.0.0~alpha"], - ["(crate(foo-bar) >= 1.2.3~beta with crate(foo-bar) < 1.2.3)"]), - -]) -def test_depgen(toml, provides, requires, cargo_toml): - md = rust2rpm.Metadata.from_file(cargo_toml(toml)) - assert [str(x) for x in md.provides] == provides - assert [str(x) for x in md.requires] == requires -- 2.20.0.rc2