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