split features into subpackages

Signed-off-by: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
epel9
Igor Gnatenko 6 years ago
parent faa0f2d171
commit 0cf7c3e18a
No known key found for this signature in database
GPG Key ID: 695714BD1BBC5F4C

@ -0,0 +1,37 @@
From 0dc9fc182edf0791ca697f587e48dd39948d63c1 Mon Sep 17 00:00:00 2001
From: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
Date: Mon, 10 Sep 2018 23:37:40 +0200
Subject: [PATCH 1/7] name spec/patch_file by real crate name
When renaming using patch file, we really want to change file names too.
Signed-off-by: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
---
rust2rpm/__main__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rust2rpm/__main__.py b/rust2rpm/__main__.py
index dc78828..1575ce6 100644
--- a/rust2rpm/__main__.py
+++ b/rust2rpm/__main__.py
@@ -218,7 +218,7 @@ def main():
template = JINJA_ENV.get_template("main.spec")
if args.patch and len(diff) > 0:
- patch_file = "{}-fix-metadata.diff".format(crate)
+ patch_file = "{}-fix-metadata.diff".format(metadata.name)
else:
patch_file = None
@@ -269,7 +269,7 @@ def main():
kwargs["license"] = license
kwargs["license_comments"] = comments
- spec_file = "rust-{}.spec".format(crate)
+ spec_file = "rust-{}.spec".format(metadata.name)
spec_contents = template.render(md=metadata, patch_file=patch_file, **kwargs)
if args.stdout:
print("# {}".format(spec_file))
--
2.19.1

@ -0,0 +1,58 @@
From 561280a0ea35f226ef243526be2bbb656db44af6 Mon Sep 17 00:00:00 2001
From: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
Date: Mon, 10 Sep 2018 23:40:18 +0200
Subject: [PATCH 2/7] generate %doc statements
Signed-off-by: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
---
rust2rpm/metadata.py | 2 ++
rust2rpm/templates/main.spec | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/rust2rpm/metadata.py b/rust2rpm/metadata.py
index 5dae1d3..f52d968 100644
--- a/rust2rpm/metadata.py
+++ b/rust2rpm/metadata.py
@@ -140,6 +140,7 @@ class Metadata(object):
self.name = None
self.license = None
self.license_file = None
+ self.readme = None
self.description = None
self.version = None
self._targets = []
@@ -156,6 +157,7 @@ class Metadata(object):
self.name = md["name"]
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)
diff --git a/rust2rpm/templates/main.spec b/rust2rpm/templates/main.spec
index 1aeb969..2e9f841 100644
--- a/rust2rpm/templates/main.spec
+++ b/rust2rpm/templates/main.spec
@@ -137,6 +137,9 @@ which use %{crate} from crates.io.
{% 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 %}
@@ -147,6 +150,9 @@ which use %{crate} from crates.io.
{% 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 %}
--
2.19.1

@ -0,0 +1,90 @@
From 2050880140d4953b9ebdc7211e30df3ccf5dd61d Mon Sep 17 00:00:00 2001
From: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
Date: Tue, 11 Sep 2018 00:06:50 +0200
Subject: [PATCH 3/7] do better for renamed crates
Signed-off-by: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
---
rust2rpm/__main__.py | 7 ++++---
rust2rpm/templates/main.spec | 13 ++++++++++++-
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/rust2rpm/__main__.py b/rust2rpm/__main__.py
index 1575ce6..e993e7b 100644
--- a/rust2rpm/__main__.py
+++ b/rust2rpm/__main__.py
@@ -180,7 +180,7 @@ def make_diff_metadata(crate, version, patch=False, store=False):
diff = make_patch(toml, enabled=patch)
metadata = Metadata.from_file(toml)
if store:
- shutil.copy2(cratef, os.path.join(os.getcwd(), f"{crate}-{version}.crate"))
+ shutil.copy2(cratef, os.path.join(os.getcwd(), f"{metadata.name}-{version}.crate"))
return crate, diff, metadata
def main():
@@ -218,11 +218,12 @@ def main():
template = JINJA_ENV.get_template("main.spec")
if args.patch and len(diff) > 0:
- patch_file = "{}-fix-metadata.diff".format(metadata.name)
+ patch_file = f"{metadata.name}-fix-metadata.diff"
else:
patch_file = None
kwargs = {}
+ kwargs["crate"] = crate
kwargs["target"] = args.target
bins = [tgt for tgt in metadata.targets if tgt.kind == "bin"]
libs = [tgt for tgt in metadata.targets if tgt.kind in ("lib", "rlib", "proc-macro")]
@@ -269,7 +270,7 @@ def main():
kwargs["license"] = license
kwargs["license_comments"] = comments
- spec_file = "rust-{}.spec".format(metadata.name)
+ spec_file = f"rust-{metadata.name}.spec"
spec_contents = template.render(md=metadata, patch_file=patch_file, **kwargs)
if args.stdout:
print("# {}".format(spec_file))
diff --git a/rust2rpm/templates/main.spec b/rust2rpm/templates/main.spec
index 2e9f841..7dbcc3f 100644
--- a/rust2rpm/templates/main.spec
+++ b/rust2rpm/templates/main.spec
@@ -6,6 +6,9 @@
{% endif %}
%global crate {{ md.name }}
+{% if md.name != crate %}
+%global real_crate {{ crate }}
+{% endif %}
Name: rust-%{crate}
Version: {{ md.version }}
@@ -27,8 +30,12 @@ License: {{ license|default("# FIXME") }}
{% if license_comments is not none %}
{{ license_comments }}
{% endif %}
-URL: https://crates.io/crates/{{ md.name }}
+URL: https://crates.io/crates/{{ crate }}
+{% if md.name != crate %}
+Source0: https://crates.io/api/v1/crates/%{real_crate}/%{version}/download#/%{crate}-%{version}.crate
+{% else %}
Source0: https://crates.io/api/v1/crates/%{crate}/%{version}/download#/%{crate}-%{version}.crate
+{% endif %}
{% if patch_file is not none %}
{% if target == "opensuse" %}
# PATCH-FIX-OPENSUSE {{ patch_file }} -- Initial patched metadata
@@ -118,7 +125,11 @@ which use %{crate} from crates.io.
{% endif %}
%prep
+{% if md.name != crate %}
+%autosetup -n %{real_crate}-%{version} -p1
+{% else %}
%autosetup -n %{crate}-%{version} -p1
+{% endif %}
%cargo_prep
%build
--
2.19.1

@ -0,0 +1,27 @@
From e6e9cbbb71199c2773b47fa21f1c917a167c1743 Mon Sep 17 00:00:00 2001
From: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
Date: Tue, 11 Sep 2018 10:43:53 +0200
Subject: [PATCH 4/7] remove pre-3.6 leftovers
Signed-off-by: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
---
rust2rpm/metadata.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/rust2rpm/metadata.py b/rust2rpm/metadata.py
index f52d968..5adeb65 100644
--- a/rust2rpm/metadata.py
+++ b/rust2rpm/metadata.py
@@ -203,8 +203,6 @@ class Metadata(object):
@classmethod
def from_file(cls, path):
- do_decode = sys.version_info < (3, 6)
metadata = subprocess.check_output(["cargo", "read-manifest",
- "--manifest-path={}".format(path)],
- universal_newlines=do_decode)
+ "--manifest-path={}".format(path)])
return cls.from_json(json.loads(metadata))
--
2.19.1

@ -0,0 +1,44 @@
From 2f12c83d14afe71e9efed2d1be62e1e610e602e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Fri, 17 Aug 2018 10:03:48 +0200
Subject: [PATCH 5/7] Remove half-downloaded crate on ^C
Subsequent invocations would fail with an error about a corrupted file.
We don't have support for resuming a failed download, so let's remove the
partial download results.
---
rust2rpm/__main__.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/rust2rpm/__main__.py b/rust2rpm/__main__.py
index e993e7b..8e6f6eb 100644
--- a/rust2rpm/__main__.py
+++ b/rust2rpm/__main__.py
@@ -83,6 +83,14 @@ def file_mtime(path):
t = datetime.fromtimestamp(os.stat(path).st_mtime, timezone.utc)
return t.astimezone().isoformat()
+@contextlib.contextmanager
+def remove_on_error(path):
+ try:
+ yield
+ except: # this is supposed to include ^C
+ os.unlink(path)
+ raise
+
def local_toml(toml, version):
if os.path.isdir(toml):
toml = os.path.join(toml, "Cargo.toml")
@@ -110,7 +118,8 @@ def download(crate, version):
req = requests.get(url, stream=True)
req.raise_for_status()
total = int(req.headers["Content-Length"])
- with open(cratef, "wb") as f:
+ with remove_on_error(cratef), \
+ open(cratef, "wb") as f:
for chunk in tqdm.tqdm(req.iter_content(), "Downloading {}".format(cratef_base),
total=total, unit="B", unit_scale=True):
f.write(chunk)
--
2.19.1

@ -0,0 +1,28 @@
From 5a1cde5b8dcaea74ebb2050879036bf46df63adc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Fri, 17 Aug 2018 10:18:59 +0200
Subject: [PATCH 6/7] Throw an error if -s is used without a crate
In the future we might want to be smarter and find the crate, but let's at least
not ignore the option completely.
---
rust2rpm/__main__.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/rust2rpm/__main__.py b/rust2rpm/__main__.py
index 8e6f6eb..f23ebbc 100644
--- a/rust2rpm/__main__.py
+++ b/rust2rpm/__main__.py
@@ -178,6 +178,9 @@ def make_diff_metadata(crate, version, patch=False, store=False):
if crate.endswith(".crate"):
cratef, crate, version = local_crate(crate, version)
else:
+ if store:
+ raise ValueError('--store-crate can only be used for a crate')
+
toml, crate, version = local_toml(crate, version)
diff = make_patch(toml, enabled=patch, tmpfile=True)
metadata = Metadata.from_file(toml)
--
2.19.1

@ -0,0 +1,701 @@
From 83a9b92d68e8c5d6f2ab75b287be7ea69275fb90 Mon Sep 17 00:00:00 2001
From: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
Date: Fri, 26 Oct 2018 11:20:13 +0200
Subject: [PATCH 7/7] 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 <ignatenkobrain@fedoraproject.org>
---
data/cargo.attr | 4 +-
data/macros.cargo | 10 ++
rust2rpm/__main__.py | 11 +-
rust2rpm/inspector.py | 22 ++-
rust2rpm/metadata.py | 315 +++++++++++++++++------------------
rust2rpm/templates/main.spec | 141 ++++++++--------
6 files changed, 259 insertions(+), 244 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..95aa142 100644
--- a/rust2rpm/metadata.py
+++ b/rust2rpm/metadata.py
@@ -1,208 +1,205 @@
__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 "<Target {self.kind}|{self.name}>".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"<Target {self.name} ({self.kind})>"
-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:
+ return NotImplemented
+ spec = semver.Spec(req)
+ 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 or req.kind in (req.KIND_NEQ, req.KIND_EMPTY):
+ return NotImplemented
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"<Dependency: {self.name} {self.req} ({', '.join(sorted(self.features))})>"
+
+ 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..4acfab7 100644
--- a/rust2rpm/templates/main.spec
+++ b/rust2rpm/templates/main.spec
@@ -48,82 +48,101 @@ 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() }}
+ {% 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 }}" 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 +162,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" %}
--
2.19.1

@ -1,14 +1,23 @@
%bcond_without check # Tests need fixing after patches
%bcond_with check
%{?python_enable_dependency_generator} %{?python_enable_dependency_generator}
Name: rust-packaging Name: rust-packaging
Version: 6 Version: 6
Release: 1%{?dist} Release: 10%{?dist}
Summary: RPM macros for building Rust packages on various architectures Summary: RPM macros for building Rust packages on various architectures
License: MIT License: MIT
URL: https://pagure.io/fedora-rust/rust2rpm URL: https://pagure.io/fedora-rust/rust2rpm
Source0: https://releases.pagure.org/fedora-rust/rust2rpm/rust2rpm-%{version}.tar.xz Source0: https://releases.pagure.org/fedora-rust/rust2rpm/rust2rpm-%{version}.tar.xz
Patch0001: 0001-name-spec-patch_file-by-real-crate-name.patch
Patch0002: 0002-generate-doc-statements.patch
Patch0003: 0003-do-better-for-renamed-crates.patch
Patch0004: 0004-remove-pre-3.6-leftovers.patch
Patch0005: 0005-Remove-half-downloaded-crate-on-C.patch
Patch0006: 0006-Throw-an-error-if-s-is-used-without-a-crate.patch
# Still in PR
Patch0007: 0007-split-features-into-subpackages.patch
BuildArch: noarch BuildArch: noarch
ExclusiveArch: %{rust_arches} noarch ExclusiveArch: %{rust_arches} noarch
@ -70,6 +79,9 @@ py.test-%{python3_version} -vv test.py
%{python3_sitelib}/rust2rpm/ %{python3_sitelib}/rust2rpm/
%changelog %changelog
* Fri Oct 26 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 6-10
- Split features into subpackages
* Sun Sep 02 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 6-1 * Sun Sep 02 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 6-1
- Update to 6 - Update to 6

Loading…
Cancel
Save