1{ lib }:
2
3let
4 inherit (lib)
5 and
6 any
7 attrByPath
8 attrNames
9 compare
10 concat
11 concatMap
12 elem
13 filter
14 foldl
15 foldr
16 genericClosure
17 head
18 imap1
19 init
20 isAttrs
21 isFunction
22 isInt
23 isList
24 lists
25 listToAttrs
26 mapAttrs
27 mergeAttrs
28 meta
29 nameValuePair
30 tail
31 toList
32 warn
33 ;
34
35 inherit (lib.attrsets) removeAttrs mapAttrsToList;
36
37 # returns default if env var is not set
38 maybeEnv =
39 name: default:
40 let
41 value = builtins.getEnv name;
42 in
43 if value == "" then default else value;
44
45 defaultMergeArg = x: y: if builtins.isAttrs y then y else (y x);
46 defaultMerge = x: y: x // (defaultMergeArg x y);
47 foldArgs =
48 merger: f: init: x:
49 let
50 arg = (merger init (defaultMergeArg init x));
51 # now add the function with composed args already applied to the final attrs
52 base = (
53 setAttrMerge "passthru" { } (f arg) (
54 z:
55 z
56 // {
57 function = foldArgs merger f arg;
58 args = (attrByPath [ "passthru" "args" ] { } z) // x;
59 }
60 )
61 );
62 withStdOverrides = base // {
63 override = base.passthru.function;
64 };
65 in
66 withStdOverrides;
67
68 # shortcut for attrByPath ["name"] default attrs
69 maybeAttrNullable = maybeAttr;
70
71 # shortcut for attrByPath ["name"] default attrs
72 maybeAttr =
73 name: default: attrs:
74 attrs.${name} or default;
75
76 # Return the second argument if the first one is true or the empty version
77 # of the second argument.
78 ifEnable =
79 cond: val:
80 if cond then
81 val
82 else if builtins.isList val then
83 [ ]
84 else if builtins.isAttrs val then
85 { }
86 # else if builtins.isString val then ""
87 else if val == true || val == false then
88 false
89 else
90 null;
91
92 # Return true only if there is an attribute and it is true.
93 checkFlag =
94 attrSet: name:
95 if name == "true" then
96 true
97 else if name == "false" then
98 false
99 else if (elem name (attrByPath [ "flags" ] [ ] attrSet)) then
100 true
101 else
102 attrByPath [ name ] false attrSet;
103
104 # Input : attrSet, [ [name default] ... ], name
105 # Output : its value or default.
106 getValue =
107 attrSet: argList: name:
108 (attrByPath [ name ] (
109 if checkFlag attrSet name then
110 true
111 else if argList == [ ] then
112 null
113 else
114 let
115 x = builtins.head argList;
116 in
117 if (head x) == name then (head (tail x)) else (getValue attrSet (tail argList) name)
118 ) attrSet);
119
120 # Input : attrSet, [[name default] ...], [ [flagname reqs..] ... ]
121 # Output : are reqs satisfied? It's asserted.
122 checkReqs =
123 attrSet: argList: condList:
124 (foldr and true (
125 map (
126 x:
127 let
128 name = (head x);
129 in
130
131 (
132 (checkFlag attrSet name)
133 -> (foldr and true (
134 map (
135 y:
136 let
137 val = (getValue attrSet argList y);
138 in
139 (val != null) && (val != false)
140 ) (tail x)
141 ))
142 )
143 ) condList
144 ));
145
146 # This function has O(n^2) performance.
147 uniqList =
148 {
149 inputList,
150 acc ? [ ],
151 }:
152 let
153 go =
154 xs: acc:
155 if xs == [ ] then
156 [ ]
157 else
158 let
159 x = head xs;
160 y = if elem x acc then [ ] else [ x ];
161 in
162 y ++ go (tail xs) (y ++ acc);
163 in
164 go inputList acc;
165
166 uniqListExt =
167 {
168 inputList,
169 outputList ? [ ],
170 getter ? (x: x),
171 compare ? (x: y: x == y),
172 }:
173 if inputList == [ ] then
174 outputList
175 else
176 let
177 x = head inputList;
178 isX = y: (compare (getter y) (getter x));
179 newOutputList = outputList ++ (if any isX outputList then [ ] else [ x ]);
180 in
181 uniqListExt {
182 outputList = newOutputList;
183 inputList = (tail inputList);
184 inherit getter compare;
185 };
186
187 condConcat =
188 name: list: checker:
189 if list == [ ] then
190 name
191 else if checker (head list) then
192 condConcat (name + (head (tail list))) (tail (tail list)) checker
193 else
194 condConcat name (tail (tail list)) checker;
195
196 lazyGenericClosure =
197 { startSet, operator }:
198 let
199 work =
200 list: doneKeys: result:
201 if list == [ ] then
202 result
203 else
204 let
205 x = head list;
206 key = x.key;
207 in
208 if elem key doneKeys then
209 work (tail list) doneKeys result
210 else
211 work (tail list ++ operator x) ([ key ] ++ doneKeys) ([ x ] ++ result);
212 in
213 work startSet [ ] [ ];
214
215 innerModifySumArgs =
216 f: x: a: b:
217 if b == null then (f a b) // x else innerModifySumArgs f x (a // b);
218 modifySumArgs = f: x: innerModifySumArgs f x { };
219
220 innerClosePropagation =
221 acc: xs:
222 if xs == [ ] then
223 acc
224 else
225 let
226 y = head xs;
227 ys = tail xs;
228 in
229 if !isAttrs y then
230 innerClosePropagation acc ys
231 else
232 let
233 acc' = [ y ] ++ acc;
234 in
235 innerClosePropagation acc' (uniqList {
236 inputList =
237 (maybeAttrNullable "propagatedBuildInputs" [ ] y)
238 ++ (maybeAttrNullable "propagatedNativeBuildInputs" [ ] y)
239 ++ ys;
240 acc = acc';
241 });
242
243 closePropagationSlow = list: (uniqList { inputList = (innerClosePropagation [ ] list); });
244
245 # This is an optimisation of closePropagation which avoids the O(n^2) behavior
246 # Using a list of derivations, it generates the full closure of the propagatedXXXBuildInputs
247 # The ordering / sorting / comparison is done based on the `outPath`
248 # attribute of each derivation.
249 # On some benchmarks, it performs up to 15 times faster than closePropagation.
250 # See https://github.com/NixOS/nixpkgs/pull/194391 for details.
251 closePropagationFast =
252 list:
253 map (x: x.val) (
254 builtins.genericClosure {
255 startSet = map (x: {
256 key = x.outPath;
257 val = x;
258 }) (builtins.filter (x: x != null) list);
259 operator =
260 item:
261 if !builtins.isAttrs item.val then
262 [ ]
263 else
264 builtins.concatMap (
265 x:
266 if x != null then
267 [
268 {
269 key = x.outPath;
270 val = x;
271 }
272 ]
273 else
274 [ ]
275 ) ((item.val.propagatedBuildInputs or [ ]) ++ (item.val.propagatedNativeBuildInputs or [ ]));
276 }
277 );
278
279 closePropagation = if builtins ? genericClosure then closePropagationFast else closePropagationSlow;
280
281 # calls a function (f attr value ) for each record item. returns a list
282 mapAttrsFlatten = warn "lib.misc.mapAttrsFlatten is deprecated, please use lib.attrsets.mapAttrsToList instead." mapAttrsToList;
283
284 # attribute set containing one attribute
285 nvs = name: value: listToAttrs [ (nameValuePair name value) ];
286 # adds / replaces an attribute of an attribute set
287 setAttr =
288 set: name: v:
289 set // (nvs name v);
290
291 # setAttrMerge (similar to mergeAttrsWithFunc but only merges the values of a particular name)
292 # setAttrMerge "a" [] { a = [2];} (x: x ++ [3]) -> { a = [2 3]; }
293 # setAttrMerge "a" [] { } (x: x ++ [3]) -> { a = [ 3]; }
294 setAttrMerge =
295 name: default: attrs: f:
296 setAttr attrs name (f (maybeAttr name default attrs));
297
298 # Using f = a: b = b the result is similar to //
299 # merge attributes with custom function handling the case that the attribute
300 # exists in both sets
301 mergeAttrsWithFunc =
302 f: set1: set2:
303 foldr (n: set: if set ? ${n} then setAttr set n (f set.${n} set2.${n}) else set) (set2 // set1) (
304 attrNames set2
305 );
306
307 # merging two attribute set concatenating the values of same attribute names
308 # eg { a = 7; } { a = [ 2 3 ]; } becomes { a = [ 7 2 3 ]; }
309 mergeAttrsConcatenateValues = mergeAttrsWithFunc (a: b: (toList a) ++ (toList b));
310
311 # merges attributes using //, if a name exists in both attributes
312 # an error will be triggered unless its listed in mergeLists
313 # so you can mergeAttrsNoOverride { buildInputs = [a]; } { buildInputs = [a]; } {} to get
314 # { buildInputs = [a b]; }
315 # merging buildPhase doesn't really make sense. The cases will be rare where appending /prefixing will fit your needs?
316 # in these cases the first buildPhase will override the second one
317 # ! deprecated, use mergeAttrByFunc instead
318 mergeAttrsNoOverride =
319 {
320 mergeLists ? [
321 "buildInputs"
322 "propagatedBuildInputs"
323 ],
324 overrideSnd ? [ "buildPhase" ],
325 }:
326 attrs1: attrs2:
327 foldr (
328 n: set:
329 setAttr set n (
330 if set ? ${n} then # merge
331 if
332 elem n mergeLists # attribute contains list, merge them by concatenating
333 then
334 attrs2.${n} ++ attrs1.${n}
335 else if elem n overrideSnd then
336 attrs1.${n}
337 else
338 throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined"
339 else
340 attrs2.${n} # add attribute not existing in attr1
341 )
342 ) attrs1 (attrNames attrs2);
343
344 # example usage:
345 # mergeAttrByFunc {
346 # inherit mergeAttrBy; # defined below
347 # buildInputs = [ a b ];
348 # } {
349 # buildInputs = [ c d ];
350 # };
351 # will result in
352 # { mergeAttrsBy = [...]; buildInputs = [ a b c d ]; }
353 # is used by defaultOverridableDelayableArgs and can be used when composing using
354 # foldArgs, composedArgsAndFun or applyAndFun. Example: composableDerivation in all-packages.nix
355 mergeAttrByFunc =
356 x: y:
357 let
358 mergeAttrBy2 = {
359 mergeAttrBy = mergeAttrs;
360 }
361 // (maybeAttr "mergeAttrBy" { } x)
362 // (maybeAttr "mergeAttrBy" { } y);
363 in
364 foldr mergeAttrs { } [
365 x
366 y
367 (mapAttrs
368 (
369 a: v: # merge special names using given functions
370 if x ? ${a} then
371 if y ? ${a} then
372 v x.${a} y.${a} # both have attr, use merge func
373 else
374 x.${a} # only x has attr
375 else
376 y.${a} # only y has attr)
377 )
378 (
379 removeAttrs mergeAttrBy2
380 # don't merge attrs which are neither in x nor y
381 (filter (a: !x ? ${a} && !y ? ${a}) (attrNames mergeAttrBy2))
382 )
383 )
384 ];
385 mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; };
386 mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) [ "mergeAttrBy" ];
387
388 # sane defaults (same name as attr name so that inherit can be used)
389 mergeAttrBy = # { buildInputs = concatList; [...]; passthru = mergeAttr; [..]; }
390 listToAttrs (
391 map (n: nameValuePair n concat) [
392 "nativeBuildInputs"
393 "buildInputs"
394 "propagatedBuildInputs"
395 "configureFlags"
396 "prePhases"
397 "postAll"
398 "patches"
399 ]
400 )
401 // listToAttrs (
402 map (n: nameValuePair n mergeAttrs) [
403 "passthru"
404 "meta"
405 "cfg"
406 "flags"
407 ]
408 )
409 // listToAttrs (
410 map (n: nameValuePair n (a: b: "${a}\n${b}")) [
411 "preConfigure"
412 "postInstall"
413 ]
414 );
415
416 nixType =
417 x:
418 if isAttrs x then
419 if x ? outPath then "derivation" else "attrs"
420 else if isFunction x then
421 "function"
422 else if isList x then
423 "list"
424 else if x == true then
425 "bool"
426 else if x == false then
427 "bool"
428 else if x == null then
429 "null"
430 else if isInt x then
431 "int"
432 else
433 "string";
434
435 /**
436 # Deprecated
437
438 For historical reasons, imap has an index starting at 1.
439
440 But for consistency with the rest of the library we want an index
441 starting at zero.
442 */
443 imap = imap1;
444
445 # Fake hashes. Can be used as hash placeholders, when computing hash ahead isn't trivial
446 fakeHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
447 fakeSha256 = "0000000000000000000000000000000000000000000000000000000000000000";
448 fakeSha512 = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
449
450in
451
452# Everything in this attrset is the public interface of the file.
453{
454 inherit
455 checkFlag
456 checkReqs
457 closePropagation
458 closePropagationFast
459 closePropagationSlow
460 condConcat
461 defaultMerge
462 defaultMergeArg
463 fakeHash
464 fakeSha256
465 fakeSha512
466 foldArgs
467 getValue
468 ifEnable
469 imap
470 innerClosePropagation
471 innerModifySumArgs
472 lazyGenericClosure
473 mapAttrsFlatten
474 maybeAttr
475 maybeAttrNullable
476 maybeEnv
477 mergeAttrBy
478 mergeAttrByFunc
479 mergeAttrsByFuncDefaults
480 mergeAttrsByFuncDefaultsClean
481 mergeAttrsConcatenateValues
482 mergeAttrsNoOverride
483 mergeAttrsWithFunc
484 modifySumArgs
485 nixType
486 nvs
487 setAttr
488 setAttrMerge
489 uniqList
490 uniqListExt
491 ;
492}