Skip to content

Commit 498aa1f

Browse files
committed
lib/mkFlake: Document _file infinite recursion
This infinite recursion first appeared in 2cde01e.
1 parent d4311c0 commit 498aa1f

File tree

3 files changed

+141
-10
lines changed

3 files changed

+141
-10
lines changed

dev/tests/eval-tests.nix

Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ in
4747
prevEvalTests.strongEvalTests
4848
);
4949

50+
exhibitingInfiniteRecursion = false;
51+
exhibitInfiniteRecursion = evalTests.extendEvalTests
52+
(finalEvalTest: prevEvalTests: { exhibitingInfiniteRecursion = true; });
53+
5054
pkg = system: name: derivation {
5155
name = name;
5256
builder = "no-builder";
@@ -79,6 +83,61 @@ in
7983
sourceInfo.outPath = "/unknown_eval-tests_flake";
8084
result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; };
8185
in result;
86+
runEmptyTests = ok:
87+
assert evalTests.empty == evalTests.emptyResult;
88+
ok;
89+
emptyTestsResult = evalTests.runEmptyTests "ok";
90+
91+
tooEmpty = evalTests.callFlake {
92+
inputs.flake-parts = evalTests.flake-parts;
93+
inputs.self = { };
94+
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
95+
};
96+
};
97+
# Shallow evaluation is successful…
98+
weakEvalTests.tooEmptyResultTried0.success = true;
99+
weakEvalTests.tooEmptyResultTried0.value = { };
100+
weakEvalTests.tooEmptyResultTried0TestTried.success = true;
101+
weakEvalTests.tooEmptyResultTried0TestTried.value = false;
102+
# …including for flake outputs…
103+
strongEvalTests.tooEmptyResultTried0 = evalTests.weakEvalTests.tooEmptyResultTried0;
104+
strongEvalTests.tooEmptyResultTried0TestTried = evalTests.weakEvalTests.tooEmptyResultTried0TestTried;
105+
# …but any evaluations of attribute values (flake output values) are not.
106+
weakEvalTests.tooEmptyResultTried1.success = true;
107+
weakEvalTests.tooEmptyResultTried1.value = {
108+
apps = { };
109+
checks = { };
110+
devShells = { };
111+
formatter = { };
112+
legacyPackages = { };
113+
nixosConfigurations = { };
114+
nixosModules = { };
115+
overlays = { };
116+
packages = { };
117+
};
118+
weakEvalTests.tooEmptyResultTried1TestTried.success = false;
119+
weakEvalTests.tooEmptyResultTried1TestTried.value = false;
120+
strongEvalTests.tooEmptyResultTried1.success = true;
121+
strongEvalTests.tooEmptyResultTried1.value = let
122+
_type = "flake";
123+
inputs.flake-parts = evalTests.flake-parts;
124+
inputs.self = { };
125+
outputs = evalTests.weakEvalTests.tooEmptyResultTried1.value;
126+
sourceInfo.outPath = "/unknown_eval-tests_flake";
127+
result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; };
128+
in result;
129+
strongEvalTests.tooEmptyResultTried1TestTried.success = false;
130+
strongEvalTests.tooEmptyResultTried1TestTried.value = false;
131+
runTooEmptyTests = ok:
132+
let
133+
tooEmptyResultTried = builtins.tryEval evalTests.tooEmpty;
134+
tooEmptyResultTried0TestTried = builtins.tryEval (tooEmptyResultTried == evalTests.tooEmptyResultTried0);
135+
tooEmptyResultTried1TestTried = builtins.tryEval (tooEmptyResultTried == evalTests.tooEmptyResultTried1);
136+
in
137+
assert tooEmptyResultTried0TestTried == evalTests.tooEmptyResultTried0TestTried;
138+
assert tooEmptyResultTried1TestTried == evalTests.tooEmptyResultTried1TestTried;
139+
ok;
140+
tooEmptyTestsResult = evalTests.runTooEmptyTests "ok";
82141

83142
example1 = evalTests.callFlake {
84143
inputs.flake-parts = evalTests.flake-parts;
@@ -112,6 +171,64 @@ in
112171
sourceInfo.outPath = "/unknown_eval-tests_flake";
113172
result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; };
114173
in result;
174+
runExample1Tests = ok:
175+
assert evalTests.example1 == evalTests.example1Result;
176+
ok;
177+
example1TestsResult = evalTests.runExample1Tests "ok";
178+
179+
# This test case is a fun one. In the REPL, try `exhibitInfiniteRecursion.*`.
180+
# In the case that `mkFlake` *isn't* called from a flake, `inputs.self` is
181+
# unlikely to refer to the result of the `mkFlake` evaluation. If
182+
# `inputs.self` isn't actually self-referential, evaluating attribute values
183+
# of `self` is not divergent. Evaluation of `self.outPath` is useful for
184+
# paths in documentation & error messages. However, if that evaluation occurs
185+
# in a `builtins.addErrorContext` message forced by an erroring `self`, both
186+
# `self` will never evaluate *and* `builtins.toString self.outPath` must
187+
# evaluate, causing Nix to instead throw an infinite recursion error. Even
188+
# just `inputs.self ? outPath` throws an infinite recursion error.
189+
# (`builtins.tryEval` can only catch errors created by `builtins.throw` or
190+
# `builtins.assert`, so evaluation is guarded with
191+
# `exhibitingInfiniteRecursion` here to keep `runTests` from diverging.)
192+
# In this particular case, `mkFlake` evaluates `self ? outPath` to know if the
193+
# default module location it provides should be generic or specific. As
194+
# explained, this evaluation is unsafe under an uncatchably divergent `self`.
195+
# Thus, `outPath` cannot be safely sourced from `self` at the top-level.
196+
#
197+
# When tests are exhibititing infinite recursion, the abnormally correct
198+
# `self` is provided.
199+
weakEvalTests.nonexistentOption = let result = evalTests.callFlake {
200+
inputs.flake-parts = evalTests.flake-parts;
201+
inputs.self = if !evalTests.exhibitingInfiniteRecursion then { } else result;
202+
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
203+
config.systems = [ ];
204+
config.nonexistentOption = null;
205+
};
206+
}; in result;
207+
# When using actual flakes, this test always diverges. Unless tests are
208+
# exhibiting infinite recursion, the flake is made equivalent to `empty`.
209+
strongEvalTests.nonexistentOption = evalTests.callFlake {
210+
inputs.flake-parts = evalTests.flake-parts;
211+
inputs.self = { };
212+
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } ({
213+
config.systems = [ ];
214+
} // (if !evalTests.exhibitingInfiniteRecursion then { } else {
215+
config.nonexistentOption = null;
216+
}));
217+
};
218+
weakEvalTests.nonexistentOptionResultTried0.success = true;
219+
weakEvalTests.nonexistentOptionResultTried0.value = { };
220+
weakEvalTests.nonexistentOptionResultTried0TestTried.success = true;
221+
weakEvalTests.nonexistentOptionResultTried0TestTried.value = false;
222+
strongEvalTests.nonexistentOptionResultTried0 = evalTests.weakEvalTests.nonexistentOptionResultTried0;
223+
strongEvalTests.nonexistentOptionResultTried0TestTried = evalTests.weakEvalTests.nonexistentOptionResultTried0TestTried;
224+
runNonexistentOptionTests = ok:
225+
let
226+
nonexistentOptionResultTried = builtins.tryEval evalTests.nonexistentOption;
227+
nonexistentOptionResultTried0TestTried = builtins.tryEval (nonexistentOptionResultTried == evalTests.nonexistentOptionResultTried0);
228+
in
229+
assert nonexistentOptionResultTried0TestTried == evalTests.nonexistentOptionResultTried0TestTried;
230+
ok;
231+
nonexistentOptionTestsResult = evalTests.runNonexistentOptionTests "ok";
115232

116233
packagesNonStrictInDevShells = evalTests.callFlake {
117234
inputs.flake-parts = evalTests.flake-parts;
@@ -170,6 +287,10 @@ in
170287
imports = [ inputs.flakeModulesDeclare.flakeModules.default ];
171288
};
172289
};
290+
runFlakeModulesImportTests = ok:
291+
assert evalTests.flakeModulesImport.test123 == "123test";
292+
ok;
293+
flakeModulesImportTestsResult = evalTests.runFlakeModulesImportTests "ok";
173294

174295
flakeModulesDisable = evalTests.callFlake {
175296
inputs.flake-parts = evalTests.flake-parts;
@@ -180,6 +301,10 @@ in
180301
disabledModules = [ inputs.flakeModulesDeclare.flakeModules.extra ];
181302
};
182303
};
304+
runFlakeModulesDisableTests = ok:
305+
assert evalTests.flakeModulesDisable.test123 == "option123";
306+
ok;
307+
flakeModulesDisableTestsResult = evalTests.runFlakeModulesDisableTests "ok";
183308

184309
nixpkgsWithoutEasyOverlay = import evalTests.nixpkgs {
185310
system = "x86_64-linux";
@@ -201,11 +326,17 @@ in
201326
config = { };
202327
};
203328

329+
tryEvalOutputs = outputs: builtins.seq (builtins.attrNames outputs) outputs;
330+
204331
runTests = ok:
205332

206-
assert evalTests.empty == evalTests.emptyResult;
333+
assert evalTests.runEmptyTests true;
207334

208-
assert evalTests.example1 == evalTests.example1Result;
335+
assert evalTests.runTooEmptyTests true;
336+
337+
assert evalTests.runExample1Tests true;
338+
339+
assert evalTests.runNonexistentOptionTests true;
209340

210341
# - exported package becomes part of overlay.
211342
# - perSystem is invoked for the right system, when system is non-memoized
@@ -223,9 +354,9 @@ in
223354
# - `hello_new` shows that the `final` wiring works
224355
assert evalTests.nixpkgsWithEasyOverlay.hello_new == evalTests.nixpkgsWithEasyOverlay.hello;
225356

226-
assert evalTests.flakeModulesImport.test123 == "123test";
357+
assert evalTests.runFlakeModulesImportTests true;
227358

228-
assert evalTests.flakeModulesDisable.test123 == "option123";
359+
assert evalTests.runFlakeModulesDisableTests true;
229360

230361
assert evalTests.packagesNonStrictInDevShells.packages.a.default == evalTests.pkg "a" "hello";
231362

modules/perSystem.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ config, lib, flake-parts-lib, self, ... }:
1+
{ config, lib, flake-parts-lib, inputs, self, ... }:
22
let
33
inherit (lib)
44
genAttrs
@@ -92,7 +92,7 @@ in
9292
type = mkPerSystemType ({ config, system, ... }: {
9393
_file = ./perSystem.nix;
9494
config = {
95-
_module.args.inputs' = mapAttrs (k: rootConfig.perInput system) self.inputs;
95+
_module.args.inputs' = mapAttrs (k: rootConfig.perInput system) inputs;
9696
_module.args.self' = rootConfig.perInput system self;
9797

9898
# Custom error messages

template/multi-module/hello/flake-module.nix

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Definitions can be imported from a separate file like this one
22

3-
{ self, lib, ... }: {
4-
perSystem = { config, self', inputs', pkgs, ... }: {
3+
{ config, lib, inputs, ... }: {
4+
perSystem = { config, inputs', pkgs, ... }: {
55
# Definitions like this are entirely equivalent to the ones
66
# you may have directly in flake.nix.
77
packages.hello = pkgs.hello;
88
};
99
flake = {
1010
nixosModules.hello = { pkgs, ... }: {
1111
environment.systemPackages = [
12-
# or self.inputs.nixpkgs.legacyPackages.${pkgs.stdenv.hostPlatform.system}.hello
13-
self.packages.${pkgs.stdenv.hostPlatform.system}.hello
12+
# or inputs.nixpkgs.legacyPackages.${pkgs.stdenv.hostPlatform.system}.hello
13+
config.flake.packages.${pkgs.stdenv.hostPlatform.system}.hello
1414
];
1515
};
1616
};

0 commit comments

Comments
 (0)