rust2rpm/rust2rpm.py
Igor Gnatenko 6923fb107d add support for generating spec files with binaries
Closes: https://pagure.io/fedora-rust/rust2rpm/issue/11
Signed-off-by: Igor Gnatenko <ignatenkobrain@fedoraproject.org>
2017-02-11 19:24:30 +01:00

206 lines
6.1 KiB
Python

import argparse
import os
import tarfile
import tempfile
import subprocess
import sys
import jinja2
import jinja2.ext
import jinja2.exceptions
import requests
import tqdm
from rust2rpm import Metadata
# See: http://jinja.pocoo.org/docs/latest/extensions/#example-extension
class RaiseExtension(jinja2.ext.Extension):
# a set of names that trigger the extension.
tags = set(["raise"])
def parse(self, parser):
# the first token is the token that started the tag. In our case
# we only listen to ``'raise'`` so this will be a name token with
# `raise` as value. We get the line number so that we can give
# that line number to the nodes we create by hand.
lineno = next(parser.stream).lineno
# Extract the message from the template
message_node = parser.parse_expression()
return jinja2.nodes.CallBlock(
self.call_method("_raise", [message_node], lineno=lineno),
[], [], [], lineno=lineno)
def _raise(self, msg, caller):
raise jinja2.exceptions.TemplateRuntimeError(msg)
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/"
TEMPLATE = """# Generated by rust2rpm
{% set bins = md.targets|selectattr("kind", "equalto", "bin")|list() %}
{% set libs = md.targets|selectattr("kind", "equalto", "lib")|list() %}
{% set is_bin = bins|length > 0 %}
{% set is_lib = libs|length > 0 %}
{% if is_bin and not is_lib %}
{% set include_debug = True %}
{% set name = "%{crate}" %}
{% set include_main = True %}
{% set name_devel = None %}
{% elif is_lib and not is_bin %}
{% set include_debug = False %}
{% set name = "rust-%{crate}" %}
{% set include_main = False %}
{% set name_devel = " devel" %}
{% elif is_bin and is_lib %}
{% set include_debug = True %}
{% set name = "%{crate}" %}
{% set include_main = True %}
{% set name_devel = "-n rust-%{crate}-devel" %}
{% else %}
{% raise "No bins and no libs" %}
{% endif %}
%bcond_without check
{% if not include_debug %}
%global debug_package %{nil}
{% endif %}
%global crate {{ md.name }}
Name: {{ name }}
Version: {{ md.version }}
Release: 1%{?dist}
Summary: # FIXME
License: {{ md.license|default("# FIXME") }}
URL: https://crates.io/crates/{{ md.name }}
Source0: https://crates.io/api/v1/crates/%{crate}/%{version}/download#/%{crate}-%{version}.crate
ExclusiveArch: %{rust_arches}
BuildRequires: rust
BuildRequires: cargo
{% for req in md.build_requires|sort(attribute="name") %}
BuildRequires: {{ req }}
{% endfor %}
{% for con in md.build_conflicts|sort(attribute="name") %}
BuildConflicts: {{ con }}
{% endfor %}
{% if md.test_requires|length > 0 %}
%if %{with check}
{% for req in md.test_requires|sort(attribute="name") %}
BuildRequires: {{ req }}
{% endfor %}
{% for con in md.test_conflicts|sort(attribute="name") %}
BuildConflicts: {{ con }}
{% endfor %}
%endif
{% endif %}
%description
%{summary}.
{% if name_devel is not none %}
%package {{ name_devel }}
Summary: %{summary}
BuildArch: noarch
{% if target == "epel-7" %}
{% for prv in md.provides %}
Provides: {{ prv }}
{% endfor %}
{% for req in md.requires|sort(attribute="name") %}
Requires: {{ req }}
{% endfor %}
{% for con in md.conflicts|sort(attribute="name") %}
Conflicts: {{ con }}
{% endfor %}
{% endif %}
%description {{ name_devel }}
%{summary}.
{% endif %}
%prep
%autosetup -n %{crate}-%{version}
%cargo_prep
%build
%cargo_build
%install
%cargo_install
%if %{with check}
%check
%cargo_test
%endif
{% if include_main %}
%files
{% for bin in bins %}
%{_bindir}/{{ bin.name }}
{% endfor %}
{% endif %}
{% if name_devel is not none %}
%files {{ name_devel }}
{% if md.license_file is not none %}
%license {{ md.license_file }}
{% endif %}
%{cargo_registry}/%{crate}-%{version}/
{% endif %}
%changelog
"""
JINJA_ENV = jinja2.Environment(undefined=jinja2.StrictUndefined,
extensions=[RaiseExtension],
trim_blocks=True, lstrip_blocks=True)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", choices=("epel-7", "fedora-26"), required=True,
help="Distribution target")
parser.add_argument("crate", help="crates.io name")
parser.add_argument("version", nargs="?", help="crates.io version")
args = parser.parse_args()
if args.version is None:
# Now we need to get latest version
url = requests.compat.urljoin(API_URL, "crates/{}/versions".format(args.crate))
req = requests.get(url)
req.raise_for_status()
args.version = req.json()["versions"][0]["num"]
if not os.path.isdir(CACHEDIR):
os.mkdir(CACHEDIR)
cratef_base = "{}-{}.crate".format(args.crate, args.version)
cratef = os.path.join(CACHEDIR, cratef_base)
if not os.path.isfile(cratef):
url = requests.compat.urljoin(API_URL, "crates/{}/{}/download#".format(args.crate, args.version))
req = requests.get(url, stream=True)
req.raise_for_status()
total = int(req.headers["Content-Length"])
with 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)
files = []
with tempfile.TemporaryDirectory() as tmpdir:
target_dir = "{}/".format(tmpdir)
with tarfile.open(cratef, "r") as archive:
for n in archive.getnames():
if not os.path.abspath(os.path.join(target_dir, n)).startswith(target_dir):
raise Exception("Unsafe filenames!")
archive.extractall(target_dir)
toml = "{}/{}-{}/Cargo.toml".format(tmpdir, args.crate, args.version)
assert os.path.isfile(toml)
metadata = Metadata.from_file(toml)
template = JINJA_ENV.from_string(TEMPLATE)
print(template.render(target=args.target, md=metadata))
if __name__ == "__main__":
main()