1{ lib }:
2
3let
4 inherit (lib)
5 genAttrs
6 isString
7 throwIfNot
8 ;
9
10 showMaybeAttrPosPre = prefix: attrName: v:
11 let pos = builtins.unsafeGetAttrPos attrName v;
12 in if pos == null then "" else "${prefix}${pos.file}:${toString pos.line}:${toString pos.column}";
13
14 showMaybePackagePosPre = prefix: pkg:
15 if pkg?meta.position && isString pkg.meta.position
16 then "${prefix}${pkg.meta.position}"
17 else "";
18in
19{
20 /*
21 Restrict a derivation to a predictable set of attribute names, so
22 that the returned attrset is not strict in the actual derivation,
23 saving a lot of computation when the derivation is non-trivial.
24
25 This is useful in situations where a derivation might only be used for its
26 passthru attributes, improving evaluation performance.
27
28 The returned attribute set is lazy in `derivation`. Specifically, this
29 means that the derivation will not be evaluated in at least the
30 situations below.
31
32 For illustration and/or testing, we define derivation such that its
33 evaluation is very noticeable.
34
35 let derivation = throw "This won't be evaluated.";
36
37 In the following expressions, `derivation` will _not_ be evaluated:
38
39 (lazyDerivation { inherit derivation; }).type
40
41 attrNames (lazyDerivation { inherit derivation; })
42
43 (lazyDerivation { inherit derivation; } // { foo = true; }).foo
44
45 (lazyDerivation { inherit derivation; meta.foo = true; }).meta
46
47 In these expressions, `derivation` _will_ be evaluated:
48
49 "${lazyDerivation { inherit derivation }}"
50
51 (lazyDerivation { inherit derivation }).outPath
52
53 (lazyDerivation { inherit derivation }).meta
54
55 And the following expressions are not valid, because the refer to
56 implementation details and/or attributes that may not be present on
57 some derivations:
58
59 (lazyDerivation { inherit derivation }).buildInputs
60
61 (lazyDerivation { inherit derivation }).passthru
62
63 (lazyDerivation { inherit derivation }).pythonPath
64
65 */
66 lazyDerivation =
67 args@{
68 # The derivation to be wrapped.
69 derivation
70 , # Optional meta attribute.
71 #
72 # While this function is primarily about derivations, it can improve
73 # the `meta` package attribute, which is usually specified through
74 # `mkDerivation`.
75 meta ? null
76 , # Optional extra values to add to the returned attrset.
77 #
78 # This can be used for adding package attributes, such as `tests`.
79 passthru ? { }
80 , # Optional list of assumed outputs. Default: ["out"]
81 #
82 # This must match the set of outputs that the returned derivation has.
83 # You must use this when the derivation has multiple outputs.
84 outputs ? [ "out" ]
85 }:
86 let
87 # These checks are strict in `drv` and some `drv` attributes, but the
88 # attrset spine returned by lazyDerivation does not depend on it.
89 # Instead, the individual derivation attributes do depend on it.
90 checked =
91 throwIfNot (derivation.type or null == "derivation")
92 "lazyDerivation: input must be a derivation."
93 throwIfNot
94 # NOTE: Technically we could require our outputs to be a subset of the
95 # actual ones, or even leave them unchecked and fail on a lazy basis.
96 # However, consider the case where an output is added in the underlying
97 # derivation, such as dev. lazyDerivation would remove it and cause it
98 # to fail as a buildInputs item, without any indication as to what
99 # happened. Hence the more stringent condition. We could consider
100 # adding a flag to control this behavior if there's a valid case for it,
101 # but the documentation must have a note like this.
102 (derivation.outputs == outputs)
103 ''
104 lib.lazyDerivation: The derivation ${derivation.name or "<unknown>"} has outputs that don't match the assumed outputs.
105
106 Assumed outputs passed to lazyDerivation${showMaybeAttrPosPre ",\n at " "outputs" args}:
107 ${lib.generators.toPretty { multiline = false; } outputs};
108
109 Actual outputs of the derivation${showMaybePackagePosPre ",\n defined at " derivation}:
110 ${lib.generators.toPretty { multiline = false; } derivation.outputs}
111
112 If the outputs are known ahead of evaluating the derivation,
113 then update the lazyDerivation call to match the actual outputs, in the same order.
114 If lazyDerivation is passed a literal value, just change it to the actual outputs.
115 As a result it will work as before / as intended.
116
117 Otherwise, when the outputs are dynamic and can't be known ahead of time, it won't
118 be possible to add laziness, but lib.lazyDerivation may still be useful for trimming
119 the attributes.
120 If you want to keep trimming the attributes, make sure that the package is in a
121 variable (don't evaluate it twice!) and pass the variable and its outputs attribute
122 to lib.lazyDerivation. This largely defeats laziness, but keeps the trimming.
123 If none of the above works for you, replace the lib.lazyDerivation call by the
124 expression in the derivation argument.
125 ''
126 derivation;
127 in
128 {
129 # Hardcoded `type`
130 #
131 # `lazyDerivation` requires its `derivation` argument to be a derivation,
132 # so if it is not, that is a programming error by the caller and not
133 # something that `lazyDerivation` consumers should be able to correct
134 # for after the fact.
135 # So, to improve laziness, we assume correctness here and check it only
136 # when actual derivation values are accessed later.
137 type = "derivation";
138
139 # A fixed set of derivation values, so that `lazyDerivation` can return
140 # its attrset before evaluating `derivation`.
141 # This must only list attributes that are available on _all_ derivations.
142 inherit (checked) outPath outputName drvPath name system;
143 inherit outputs;
144
145 # The meta attribute can either be taken from the derivation, or if the
146 # `lazyDerivation` caller knew a shortcut, be taken from there.
147 meta = args.meta or checked.meta;
148 }
149 // genAttrs outputs (outputName: checked.${outputName})
150 // passthru;
151
152 /* Conditionally set a derivation attribute.
153
154 Because `mkDerivation` sets `__ignoreNulls = true`, a derivation
155 attribute set to `null` will not impact the derivation output hash.
156 Thus, this function passes through its `value` argument if the `cond`
157 is `true`, but returns `null` if not.
158
159 Type: optionalDrvAttr :: Bool -> a -> a | Null
160
161 Example:
162 (stdenv.mkDerivation {
163 name = "foo";
164 x = optionalDrvAttr true 1;
165 y = optionalDrvAttr false 1;
166 }).drvPath == (stdenv.mkDerivation {
167 name = "foo";
168 x = 1;
169 }).drvPath
170 => true
171 */
172 optionalDrvAttr =
173 # Condition
174 cond:
175 # Attribute value
176 value: if cond then value else null;
177}