forked from tweag/opam-nix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathopam-evaluator.nix
344 lines (312 loc) · 10.4 KB
/
opam-evaluator.nix
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
lib:
let
inherit (builtins)
compareVersions elem elemAt replaceStrings head isString isList toJSON tail
listToAttrs length attrValues mapAttrs concatStringsSep isBool isInt filter
split foldl' match fromJSON;
inherit (lib)
splitString concatMap nameValuePair concatMapStringsSep all any zipAttrsWith
zipListsWith optionalAttrs escapeShellArg hasInfix stringLength;
inherit (import ./lib.nix lib) md5sri;
in rec {
lexiCompare = a: b:
if a == b then
0
else if isString a && hasInfix "~" a && stringLength a > stringLength b then
-1
else if isString a && hasInfix "~" b && stringLength a < stringLength b then
1
else if a > b then
1
else
(-1);
trimZeroes = s: head (match ("[0]*([0-9]+)") s);
compareVersions' = op: a: b:
let
prepareVersion = version:
map (x: if isList x then fromJSON (trimZeroes (head x)) else x)
(split "([0-9]+)" version);
comp' = filter (x: x != 0)
(zipListsWith lexiCompare (prepareVersion a) (prepareVersion b));
comp = if comp' == [ ] then 0 else head comp';
in if isNull a || isNull b then
false
else if op == "eq" then
a == b
else if op == "lt" then
comp == -1
else if op == "gt" then
comp == 1
else if op == "leq" then
comp < 1
else if op == "geq" then
comp > -1
else
true;
val = x: x.val or x;
hasOpt' = opt: dep: elem opt dep.options or [ ];
hasOpt = opt: hasOpt' { id = opt; };
any' = pred: any (x: !isNull (pred x) && pred x);
all' = pred: all (x: !isNull (pred x) && pred x);
collectAllValuesFromOptionList = v:
if isString v then
[ v ]
else if v ? val && isString v.val then
[ v.val ]
else if v ? val && isList v.val then
concatMap collectAllValuesFromOptionList v.val
else if isList v then
concatMap collectAllValuesFromOptionList v
else
throw "unexpected dependency: ${toJSON v}";
functionArgsFor = pkgdef:
let
# Get _all_ dependencies mentioned in the opam file
allDepends = collectAllValuesFromOptionList pkgdef.depends or [ ];
allDepopts = collectAllValuesFromOptionList pkgdef.depopts or [ ];
genArgs = deps: optional:
listToAttrs (map (name: nameValuePair name optional) deps);
in genArgs allDepends false // genArgs allDepopts true;
envOpToShell = op: vals:
let
fst = (elemAt vals 0).id;
snd = elemAt vals 1;
in if op == "set" || op == "eq" then
"export ${fst}=${escapeShellArg snd}"
else if op == "prepend" then
"export ${fst}=${escapeShellArg snd}\${${fst}+:}\${${fst}-}"
else if op == "append" then
"export ${fst}=\${${fst}-}\${${fst}+:}${escapeShellArg snd}"
else if op == "prepend_trailing" then
"export ${fst}=${escapeShellArg snd}:\${${fst}-}"
else if op == "append_trailing" then
"export ${fst}=\${${fst}-}:${escapeShellArg snd}"
else
throw "Operation ${op} not implemented";
opToShell = op: vals:
let
fst = elemAt vals 0;
snd = elemAt vals 1;
fstS = toShellString fst;
sndS = toShellString snd;
fstC = toCondition fst;
sndC = toCondition snd;
in if op == "eq" then
''[ "${fstS}" = "${sndS}" ]''
else if op == "gt" then
''
( [ ! "${fstS}" = "${sndS}" ] && [ "${sndS}" = "$(( echo "${fstS}"; echo "${sndS}" ) | sort -V | head -n1)" ] )''
else if op == "lt" then
''
( [ ! "${fstS}" = "${sndS}" ] && [ "${fstS}" = "$(( echo "${fstS}"; echo "${sndS}" ) | sort -V | head -n1)" ] )''
else if op == "geq" then
''
[ "${sndS}" = "$(( echo "${fstS}"; echo "${sndS}" ) | sort -V | head -n1)" ]''
else if op == "leq" then
''
[ "${sndS}" = "$(( echo "${fstS}"; echo "${sndS}" ) | sort -V | head -n1)" ]''
else if op == "neq" then
''[ ! "${fstS}" = "${sndS}" ]''
else if op == "not" then
"! ${fstC}"
else if op == "and" then
"${fstC} && ${sndC}"
else if op == "or" then
"${fstC} || ${sndC}"
else
throw "Operation ${op} not implemented";
varToShellVar = var:
let s = splitString ":" var;
in concatMapStringsSep "__" (replaceStrings [ "-" "+" ] [ "_" "_" ])
([ "opam" ] ++ s);
toShellString = { type, value }:
if type == "string" then
value
else if type == "command" then
"$(${value})"
else
throw "Can't convert ${type} to shell string";
toCommand = { type, value }:
if type == "command" then
value
else if type == "string" then
''echo "${value}"''
else
throw "Can't convert ${type} to command";
toCondition = { type, value }@x:
if type == "condition" then
value
else
''[[ "${toShellString x}" == true ]]'';
filterOptionList = vars:
let
getVar = x:
if x ? id then lib.attrByPath (splitString ":" x.id) null vars else x;
checkFilter = pkg: filter:
if filter ? op && length filter.val == 1 then
if filter.op == "not" then
!checkFilter pkg (head filter.val)
else
compareVersions' filter.op (getVar { id = pkg; })
(getVar (head filter.val))
else if filter ? op then
if filter.op == "and" then
all' (checkFilter pkg) filter.val
else if filter.op == "or" then
any' (checkFilter pkg) filter.val
else
compareVersions' filter.op (getVar (head filter.val))
(getVar (head (tail filter.val)))
else if filter ? id then
getVar filter
else if isList filter then
all' (checkFilter pkg) filter
else
throw "Couldn't understand package filter: ${toJSON filter}";
collectAcceptableElements = v:
let
a = elemAt v.val 0;
b = elemAt v.val 1;
a' = collectAcceptableElements a;
b' = collectAcceptableElements b;
in if v ? op then
if v.op == "or" then
if a' != [ ] then a' else if b' != [ ] then b' else [ ]
else if v.op == "and" then
if a' != [ ] && b' != [ ] then a' ++ b' else [ ]
else
throw "Not a logop: ${v.op}"
else if v ? options then
if all' (checkFilter v.val) v.options then [ v ] else [ ]
else if isString v then
if !isNull (getVar { id = v; }) then [ v ] else [ ]
else if isList v then
concatMap collectAcceptableElements v
else
throw "Couldn't understand a part filtered package list: ${toJSON v}";
in collectAcceptableElements;
pkgVarsFor = name: lib.mapAttrs' (var: nameValuePair "${name}:${var}");
varsToShell = vars:
let
v = attrValues (mapAttrs (name: value: ''
export ${varToShellVar name}="''${${varToShellVar name}-${
toJSON value
}}"
'') vars);
in concatStringsSep "" v;
envToShell = env:
concatMapStringsSep ""
(concatMapStringsSep "\n" ({ op, val }: envOpToShell op val))
(normalize' env);
filterOptionListInShell = level: val:
if val ? id then {
type = "string";
value = "$" + varToShellVar val.id;
} else if val ? op then {
type = "condition";
value = opToShell val.op (map (x:
if isList x then
head (map (filterOptionListInShell level) x)
else
filterOptionListInShell level x) val.val);
} else if val == [ ] then {
type = "command";
value = ":";
} else if isList val then { # FIXME EWWWWWW
type = "command";
value = if level == 1 then ''
args=()
${concatMapStringsSep "\n" (part: ''
arg="${toShellString (filterOptionListInShell (level + 1) part)}"
if [[ -n "$arg" ]]; then args+=("$arg"); fi
'') (tail val)}
"${
(toShellString (filterOptionListInShell (level + 1) (head val)))
}" "''${args[@]}"
'' else if level == 0 then
concatMapStringsSep "\n"
(part: toCommand (filterOptionListInShell (level + 1) part)) val
else
throw "Level too big";
} else if val ? options then {
type = "command";
value = ''
if ${
concatMapStringsSep " && "
(x: toCondition (filterOptionListInShell level x)) val.options
}; then
${toCommand (filterOptionListInShell level val.val)}
fi'';
} else {
type = "string";
value = interpolateStringsRec val;
};
filterSectionInShell = section:
let s = filterOptionListInShell 0 (normalize section);
in s.value;
normalize = section:
if !isList (val section) then
[ [ section ] ]
else if (val section) == [ ] || !isList (val (head (val section))) then
[ section ]
else
section;
normalize' = section:
if !isList section then
[ [ section ] ]
else if section == [ ] || !isList (head section) then
[ section ]
else
section;
interpolateStringsRec = val:
if isString val then
interpolateString val
else if isList val then
map interpolateStringsRec val
else if isBool val || isInt val then
toString' val
else
val;
toString' = v:
if isString v then
v
else if isInt v then
toString v
else if isBool v then
if v then "true" else "false"
else if isNull v then
""
else
throw "Don't know how to toString ${toJSON v}";
# FIXME this should be implemented correctly and not using regex
interpolateString = s:
let
pieces = filter isString (split "([%][{]|[}][%])" s);
result = foldl' ({ i, result }:
piece: {
i = !i;
result = result + (if i then
toShellString (filterOptionListInShell 2 { id = piece; })
else
piece);
}) {
i = false;
result = "";
} pieces;
in if length pieces == 1 then s else result.result;
tryHash = method: c:
let m = match "${method}=(.*)" c;
in optionalAttrs (!isNull m) { ${method} = head m; };
# md5 is special in two ways:
# nixpkgs only accepts it as an SRI,
# and checksums without an explicit algo are assumed to be md5 in opam
trymd5 = c:
let
m = match "md5=(.*)" c;
m' = match "([0-9a-f]{32})" c;
success = md5: { hash = md5sri (head md5); };
in if !isNull m then success m else if !isNull m' then success m' else { };
getHashes = checksums:
zipAttrsWith (_: values: head values)
(map (x: tryHash "sha512" x // tryHash "sha256" x // trymd5 x) checksums);
}