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 builtins.map (x: x.val) (
254 builtins.genericClosure {
255 startSet = builtins.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; } // (maybeAttr "mergeAttrBy" { } x) // (maybeAttr "mergeAttrBy" { } y);
360 in
361 foldr mergeAttrs { } [
362 x
363 y
364 (mapAttrs
365 (
366 a: v: # merge special names using given functions
367 if x ? ${a} then
368 if y ? ${a} then
369 v x.${a} y.${a} # both have attr, use merge func
370 else
371 x.${a} # only x has attr
372 else
373 y.${a} # only y has attr)
374 )
375 (
376 removeAttrs mergeAttrBy2
377 # don't merge attrs which are neither in x nor y
378 (filter (a: !x ? ${a} && !y ? ${a}) (attrNames mergeAttrBy2))
379 )
380 )
381 ];
382 mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; };
383 mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) [ "mergeAttrBy" ];
384
385 # sane defaults (same name as attr name so that inherit can be used)
386 mergeAttrBy = # { buildInputs = concatList; [...]; passthru = mergeAttr; [..]; }
387 listToAttrs (
388 map (n: nameValuePair n concat) [
389 "nativeBuildInputs"
390 "buildInputs"
391 "propagatedBuildInputs"
392 "configureFlags"
393 "prePhases"
394 "postAll"
395 "patches"
396 ]
397 )
398 // listToAttrs (
399 map (n: nameValuePair n mergeAttrs) [
400 "passthru"
401 "meta"
402 "cfg"
403 "flags"
404 ]
405 )
406 // listToAttrs (
407 map (n: nameValuePair n (a: b: "${a}\n${b}")) [
408 "preConfigure"
409 "postInstall"
410 ]
411 );
412
413 nixType =
414 x:
415 if isAttrs x then
416 if x ? outPath then "derivation" else "attrs"
417 else if isFunction x then
418 "function"
419 else if isList x then
420 "list"
421 else if x == true then
422 "bool"
423 else if x == false then
424 "bool"
425 else if x == null then
426 "null"
427 else if isInt x then
428 "int"
429 else
430 "string";
431
432 /**
433 # Deprecated
434
435 For historical reasons, imap has an index starting at 1.
436
437 But for consistency with the rest of the library we want an index
438 starting at zero.
439 */
440 imap = imap1;
441
442 # Fake hashes. Can be used as hash placeholders, when computing hash ahead isn't trivial
443 fakeHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
444 fakeSha256 = "0000000000000000000000000000000000000000000000000000000000000000";
445 fakeSha512 = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
446
447in
448
449# Everything in this attrset is the public interface of the file.
450{
451 inherit
452 checkFlag
453 checkReqs
454 closePropagation
455 closePropagationFast
456 closePropagationSlow
457 condConcat
458 defaultMerge
459 defaultMergeArg
460 fakeHash
461 fakeSha256
462 fakeSha512
463 foldArgs
464 getValue
465 ifEnable
466 imap
467 innerClosePropagation
468 innerModifySumArgs
469 lazyGenericClosure
470 mapAttrsFlatten
471 maybeAttr
472 maybeAttrNullable
473 maybeEnv
474 mergeAttrBy
475 mergeAttrByFunc
476 mergeAttrsByFuncDefaults
477 mergeAttrsByFuncDefaultsClean
478 mergeAttrsConcatenateValues
479 mergeAttrsNoOverride
480 mergeAttrsWithFunc
481 modifySumArgs
482 nixType
483 nvs
484 setAttr
485 setAttrMerge
486 uniqList
487 uniqListExt
488 ;
489}