Skip to content

Commit fce8b01

Browse files
committed
lib: Add lazyDerivation
1 parent 0615155 commit fce8b01

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

lib/default.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ let
2323

2424
# packaging
2525
customisation = callLibs ./customisation.nix;
26+
derivations = callLibs ./derivations.nix;
2627
maintainers = import ../maintainers/maintainer-list.nix;
2728
teams = callLibs ../maintainers/team-list.nix;
2829
meta = callLibs ./meta.nix;
@@ -108,6 +109,7 @@ let
108109
inherit (self.customisation) overrideDerivation makeOverridable
109110
callPackageWith callPackagesWith extendDerivation hydraJob
110111
makeScope makeScopeWithSplicing;
112+
inherit (self.derivations) lazyDerivation;
111113
inherit (self.meta) addMetaAttrs dontDistribute setName updateName
112114
appendToName mapDerivationAttrset setPrio lowPrio lowPrioSet hiPrio
113115
hiPrioSet getLicenseFromSpdxId getExe;

lib/derivations.nix

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{ lib }:
2+
3+
let
4+
inherit (lib) throwIfNot;
5+
in
6+
{
7+
/*
8+
Restrict a derivation to a predictable set of attribute names, so
9+
that the returned attrset is not strict in the actual derivation,
10+
saving a lot of computation when the derivation is non-trivial.
11+
12+
This is useful in situations where a derivation might only be used for its
13+
passthru attributes, improving evaluation performance.
14+
15+
The returned attribute set is lazy in `derivation`. Specifically, this
16+
means that the derivation will not be evaluated in at least the
17+
situations below.
18+
19+
For illustration and/or testing, we define derivation such that its
20+
evaluation is very noticable.
21+
22+
let derivation = throw "This won't be evaluated.";
23+
24+
In the following expressions, `derivation` will _not_ be evaluated:
25+
26+
(lazyDerivation { inherit derivation; }).type
27+
28+
attrNames (lazyDerivation { inherit derivation; })
29+
30+
(lazyDerivation { inherit derivation; } // { foo = true; }).foo
31+
32+
(lazyDerivation { inherit derivation; meta.foo = true; }).meta
33+
34+
In these expressions, it `derivation` _will_ be evaluated:
35+
36+
"${lazyDerivation { inherit derivation }}"
37+
38+
(lazyDerivation { inherit derivation }).outPath
39+
40+
(lazyDerivation { inherit derivation }).meta
41+
42+
And the following expressions are not valid, because the refer to
43+
implementation details and/or attributes that may not be present on
44+
some derivations:
45+
46+
(lazyDerivation { inherit derivation }).buildInputs
47+
48+
(lazyDerivation { inherit derivation }).passthru
49+
50+
(lazyDerivation { inherit derivation }).pythonPath
51+
52+
*/
53+
lazyDerivation =
54+
args@{
55+
# The derivation to be wrapped.
56+
derivation
57+
, # Optional meta attribute.
58+
#
59+
# While this function is primarily about derivations, it can improve
60+
# the `meta` package attribute, which is usually specified through
61+
# `mkDerivation`.
62+
meta ? null
63+
, # Optional extra values to add to the returned attrset.
64+
#
65+
# This can be used for adding package attributes, such as `tests`.
66+
passthru ? { }
67+
}:
68+
let
69+
# These checks are strict in `drv` and some `drv` attributes, but the
70+
# attrset spine returned by lazyDerivation does not depend on it.
71+
# Instead, the individual derivation attributes do depend on it.
72+
checked =
73+
throwIfNot (derivation.type or null == "derivation")
74+
"lazySimpleDerivation: input must be a derivation."
75+
throwIfNot
76+
(derivation.outputs == [ "out" ])
77+
# Supporting multiple outputs should be a matter of inheriting more attrs.
78+
"The derivation ${derivation.name or "<unknown>"} has multiple outputs. This is not supported by lazySimpleDerivation yet. Support could be added, and be useful as long as the set of outputs is known in advance, without evaluating the actual derivation."
79+
derivation;
80+
in
81+
{
82+
# Hardcoded `type`
83+
#
84+
# `lazyDerivation` requires its `derivation` argument to be a derivation,
85+
# so if it is not, that is a programming error by the caller and not
86+
# something that `lazyDerivation` consumers should be able to correct
87+
# for after the fact.
88+
# So, to improve laziness, we assume correctness here and check it only
89+
# when actual derivation values are accessed later.
90+
type = "derivation";
91+
92+
# A fixed set of derivation values, so that `lazyDerivation` can return
93+
# its attrset before evaluating `derivation`.
94+
# This must only list attributes that are available on _all_ derivations.
95+
inherit (checked) outputs out outPath outputName drvPath name system;
96+
97+
# The meta attribute can either be taken from the derivation, or if the
98+
# `lazyDerivation` caller knew a shortcut, be taken from there.
99+
meta = args.meta or checked.meta;
100+
} // passthru;
101+
}

lib/tests/misc.nix

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,59 @@ runTests {
12071207
expected = true;
12081208
};
12091209

1210+
# lazyDerivation
1211+
1212+
testLazyDerivationIsLazyInDerivationForAttrNames = {
1213+
expr = attrNames (lazyDerivation {
1214+
derivation = throw "not lazy enough";
1215+
});
1216+
# It's ok to add attribute names here when lazyDerivation is improved
1217+
# in accordance with its inline comments.
1218+
expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ];
1219+
};
1220+
1221+
testLazyDerivationIsLazyInDerivationForPassthruAttr = {
1222+
expr = (lazyDerivation {
1223+
derivation = throw "not lazy enough";
1224+
passthru.tests = "whatever is in tests";
1225+
}).tests;
1226+
expected = "whatever is in tests";
1227+
};
1228+
1229+
testLazyDerivationIsLazyInDerivationForPassthruAttr2 = {
1230+
# passthru.tests is not a special case. It works for any attr.
1231+
expr = (lazyDerivation {
1232+
derivation = throw "not lazy enough";
1233+
passthru.foo = "whatever is in foo";
1234+
}).foo;
1235+
expected = "whatever is in foo";
1236+
};
1237+
1238+
testLazyDerivationIsLazyInDerivationForMeta = {
1239+
expr = (lazyDerivation {
1240+
derivation = throw "not lazy enough";
1241+
meta = "whatever is in meta";
1242+
}).meta;
1243+
expected = "whatever is in meta";
1244+
};
1245+
1246+
testLazyDerivationReturnsDerivationAttrs = let
1247+
derivation = {
1248+
type = "derivation";
1249+
outputs = ["out"];
1250+
out = "test out";
1251+
outPath = "test outPath";
1252+
outputName = "out";
1253+
drvPath = "test drvPath";
1254+
name = "test name";
1255+
system = "test system";
1256+
meta = "test meta";
1257+
};
1258+
in {
1259+
expr = lazyDerivation { inherit derivation; };
1260+
expected = derivation;
1261+
};
1262+
12101263
testTypeDescriptionInt = {
12111264
expr = (with types; int).description;
12121265
expected = "signed integer";

0 commit comments

Comments
 (0)