-
Notifications
You must be signed in to change notification settings - Fork 0
/
gen.py
274 lines (206 loc) · 7.92 KB
/
gen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
import requests
import hashlib
import jinja2
from glob import glob
import json
import base64
import sys
import tarfile
import subprocess
import toml
from io import BytesIO
from joblib import Memory
memory = Memory("~/.cache/python_cache", verbose=0)
base_url = "https://crates.io/"
def crate_url(name, version = None):
ret = "https://crates.io/api/v1/crates/" + name
if version is not None:
ret += "/" + version
return ret
@memory.cache
def get(url, *args, **kwargs):
return requests.get(url, *args, **kwargs)
@memory.cache
def local_manifests():
manifests = {}
if len(sys.argv) >= 4:
path = sys.argv[3][:-10]
for cargo_toml in glob(path + "**" + "/Cargo.toml", recursive=True):
cargo_toml = toml.load(cargo_toml)
if "package" in cargo_toml:
manifests[cargo_toml["package"]["name"]] = cargo_toml
return manifests
@memory.cache
def crate_json(name, version = None):
# prioritize local crates if we have a local lock file
if name in local_manifests():
cargo_toml = local_manifests()[name]
if "package" in cargo_toml:
if cargo_toml["package"]["name"] == name:
if version is None or cargo_toml["package"]["version"] == version:
if "license" in cargo_toml["package"]:
license = cargo_toml["package"]["license"]
else:
license = None
if "homepage" in cargo_toml["package"]:
homepage = cargo_toml["package"]["homepage"]
elif "repository" in cargo_toml["package"]:
homepage = cargo_toml["package"]["repository"]
else:
homepage = "FILLMEIN"
if "description" in cargo_toml["package"]:
desc = cargo_toml["package"]["description"]
else:
desc = ""
ret = { "crate" :
{ "description" : desc,
"homepage": homepage },
"version":
{ "license" : license } }
return ret
# ok, nothing local matches, just go to crates.io
return get(crate_url(name, version)).json()
@memory.cache
def crate_max_version(name):
return crate_json(name)["crate"]["max_version"]
@memory.cache
def crate_download(name, version):
try:
dl_path = crate_json(name, version)["version"]["dl_path"]
while True:
try:
return get(base_url + dl_path, stream=True)
except:
continue
except:
return None
@memory.cache
def crate_hash(name, version):
content = crate_download(name, version)
if content:
m = hashlib.sha256()
m.update(content.content)
return nix_base32(m.digest())
else:
return "FILLMEIN"
def crate_dependencies(name, version, deps = None):
crate_meta = crate_json(name, version)
if "version" in crate_meta and "links" in crate_meta["version"]:
deps = get(base_url + crate_meta["version"]["links"]["dependencies"]).json()
return { dep["crate_id"] : dep for dep in deps["dependencies"] }
else:
assert deps is not None, "crate not found on crates.io and package is None"
return { name : { "kind": "normal" } for name, version in deps }
def nix_base32(h):
chars = "0123456789abcdfghijklmnpqrsvwxyz"
l = int((len(h) * 8 - 1) / 5) + 1
s = ""
for n in range(l - 1, -1, -1):
b = n * 5
i = int(b / 8)
j = b % 8
c = h[i] >> j
if i < (len(h) - 1):
c |= h[i + 1] << (8 - j)
s += chars[c & 0x1f]
return s
name = sys.argv[1]
if len(sys.argv) == 2:
version = crate_max_version(name)
elif len(sys.argv) == 3:
version = sys.argv[2]
if len(sys.argv) == 4:
version = sys.argv[2]
lockfile = toml.load(sys.argv[3])
else:
tar = crate_download(name, version).content
tar = BytesIO(tar)
tar = tarfile.open(fileobj=tar)
tar.extractall("crates_downloads/")
crate_dir = "crates_downloads/" + name + "-" + version
subprocess.run(["cargo", "generate-lockfile"], cwd=crate_dir)
lockfile = toml.load(crate_dir + "/Cargo.lock")
def guix_name(name, version):
return name + "_" + version.replace(".", "_")
def gen_package(package, packages):
print(package, file=sys.stderr)
if "dependencies" in package:
real_deps = [real_dep.split()[:2] if len(real_dep.split()) >= 2 else [real_dep, [p["version"] for p in packages if p["name"] == real_dep][0]] for real_dep in package["dependencies"]]
else:
real_deps = []
deps = crate_dependencies(package["name"], package["version"], real_deps)
normal = ["normal", "build"]
normal_deps = [ d for d in real_deps if deps[d[0]]["kind"] in normal]
dev_deps = [ d for d in real_deps if deps[d[0]]["kind"] not in normal]
desc = crate_json(package["name"])["crate"]["description"]
homepage = crate_json(package["name"])["crate"]["homepage"]
license = crate_json(package["name"], package["version"])["version"]["license"]
if homepage is not None:
homepage = homepage.strip()
if desc is not None:
desc = desc.strip()
if license is None:
license = '#f'
else:
licenses = license.split('/')
if len(licenses) == 1:
license = "(spdx-string->license \"{}\")".format(license)
else:
license = "`("
for l in licenses[:-1]:
license += "(spdx-string->license \"{}\")\n ".format(l)
license += "(spdx-string->license \"{}\"))".format(licenses[-1])
t = \
r"""(define-public rust-{{guix_name(package["name"], package["version"])}}
(package
(name "rust-{{package["name"]}}")
(version "{{package["version"]}}")
(source
(origin
(method url-fetch)
(uri (crate-uri "{{package["name"]}}" version))
(file-name
(string-append name "-" version ".tar.gz"))
(sha256
(base32
"{{crate_hash(package["name"], package["version"])}}"))))
(build-system cargo-build-system)
{% if normal_deps|length + dev_deps|length > 0 %}
(arguments
`({% if normal_deps|length > 0 %}
#:cargo-inputs
({% for dep in normal_deps %}{% if not loop.first %} {% endif %}("rust-{{dep[0]}}" ,rust-{{guix_name(dep[0], dep[1])}}){% if not loop.last %}
{% endif %}{% endfor %}){% endif %}
{% if dev_deps|length > 0 %}
{% if normal_deps|length > 0 %}{{"\n "}}{% else %}{% endif %}#:cargo-development-inputs
({% for dep in dev_deps %}{% if not loop.first %} {% endif %}("rust-{{dep[0]}}" ,rust-{{guix_name(dep[0], dep[1])}}){% if not loop.last %}
{% endif %}{% endfor %}){% endif %}))
{% endif %}
(home-page "{{homepage|default("")}}")
(synopsis {{desc|tojson}})
(description
(beautify-description {{desc|tojson}}))
(license {{license}})))
"""
compiled = jinja2.Template(t, trim_blocks=True, lstrip_blocks=True)
return compiled.render({**globals(), **locals()})
header = \
"""(use-modules (guix build-system cargo))
(use-modules (guix licenses))
(use-modules (guix packages))
(use-modules (guix download))
(use-modules ((guix import utils)
#:select (beautify-description spdx-string->license)))
"""
print(header)
from multiprocessing import Pool
def gen(x):
return gen_package(*x)
with Pool(processes=24) as pool:
for p in pool.map(gen, zip(lockfile["package"], [lockfile["package"]] * len(lockfile["package"]))):
print(p)
print()
print()
print("rust-" + name + "_" + version.replace(".", "_"))
# TODO(robin): rust-time-0.1.42 is broken???
# also rand-0.7.0 (because it has rand_hc as dev and as normal dependency)