When I try to build as part of a nix-darwin configuration, I get an infinite recursion encountered error:
building the system configuration...
error: while evaluating 'evalModules' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:62:17, called from /nix/store/0882sb0p0lhf4b964apsbbfpijl94ngz-darwin/darwin/eval-config.nix:25:10:
while evaluating the attribute '_module.freeformType' at undefined position:
while evaluating 'g' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/attrsets.nix:276:19, called from undefined position:
while evaluating anonymous function at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:139:72, called from /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/attrsets.nix:279:20:
while evaluating the attribute 'value' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:512:9:
while evaluating the option `_module.freeformType':
while evaluating the attribute 'mergedValue' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:544:5:
while evaluating the attribute 'values' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:538:9:
while evaluating the attribute 'values' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:637:7:
while evaluating 'byName' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:362:25, called from /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:378:22:
while evaluating 'byName' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:362:25, called from /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:374:21:
while evaluating anonymous function at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:337:19, called from /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:337:8:
while evaluating 'pushDownProperties' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:576:24, called from /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:337:73:
while evaluating 'pushDownProperties' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:576:24, called from /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:578:7:
while evaluating the module argument `config' in "/Users/eturkeltaub/tilde/modules/configuration":
while evaluating the attribute 'config' at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:130:21:
infinite recursion encountered, at /nix/store/pci4ssg62nr8mg3kax4ccrpzbz4vn5lc-nixpkgs-21.03pre262944.f18ba0425d8/nixpkgs/lib/modules.nix:130:21
It seems like I can’t refer to cfg inside of mkMerge, but I have no idea why that’s the case. Can anyone help me out?
In theory the result of mkmerge could contain new options for tilde.modules.configuration therefore it’s recursing infinitely. You need to make sure that this overlap can not happen.
As it potentially can have those keys, it would mean that those options would need to be merged again due to how the nodule system and final merging works.
Merge them under a key that is not tilde and it should work without infinite recursion.
No, I meant something like config.foo = mkMerhe ...
As long as you merge directly into the top-level config, you will keep the the infinite recursion as your merged results still could contain the keys you want to merge over.
Is there any way to apply this sort of pattern then, where some configurations get merged into the top level? I could use an example, I’m a bit of a Nix newcomer
Basically I’d like to have definitions for machines (nix-darwin or NixOS) and definitions for users (home-manager) and then use combinations of those to create a configuration. I could do this with just imports but I like the module pattern because it allows for a set of required options and merges applies that to a default configuration (so I can pass the networking hostname to a machine definition, and it will merge that in with the rest of the default machine configuration).
Common approach is to provide different entrypoint for the config using nixos-config entry in the nix path. Or even provide host and user entries to import from.
The actual module I was trying to make was larger, but I broke it down to this.
What I don’t really understand, why would it cause an infinite recursion error, if it could contain options for itself? Shouldn’t the infinite recursion be caused by the containing itself instead of the possibility?
no. the infinite recursion is caused by some config attribute depending, in some way, on itself in a way the module system cannot resolve. config (the argument) is (to first approximation) a fixed point of mkMerge, so try to imagine the config (attribute) you write as though every config (argument) had been replaced with the entire value bound to config (attribute). in your first case this process goes on forever, so you get an infinite recursion. in your second example it stops after one iteration, so you don’t get an infinite recursion.
importantly note that nix is a lazy language, so only values that are actually used count towards producing an infinite recursion. that’s why you get an infinite recursion out of
the first case uses the attribute names (and thus the value) of config, the second one does not use config at all. if we introduce a use of config with a trace we get an infinite recursion again:
Just last week I held a Nix Hour relating to recursion in the module system, and it also covered getting infinite recursion with mkMerge and how to work around it. Feel free to check it out, comes with timestamps https://www.youtube.com/live/cZjOzOHb2ow
@Infinisil Thanks for sharing the tips. To avoid mkMerge recursion, you recommend pushing the list one level down. In my case, since I use quite a lot top level attrs, that’s a bit cumbersome.
Do you have any idea why @udf’s approach (posted above) could work? The last step where the top-level attrs are pulled, seems to be a no-op, since the list of attrs don’t contain any recursive value at all.
mkMerge is a red herring here. The issue is that you’re using an attribute insideconfig to define the set of attribute names inconfig. Values of an attrset cannot be retrieved until after the set of attributes have been determined.
nix-repl> let attrs = { foo = { bar = 1; }; } // attrs.foo; in attrs
error:
… in the right operand of the update (//) operator
at «string»:1:37:
1| let attrs = { foo = { bar = 1; }; } // attrs.foo; in attrs
| ^
error: infinite recursion encountered
at «string»:1:40:
1| let attrs = { foo = { bar = 1; }; } // attrs.foo; in attrs
| ^
udf’s version works because it frontloads the information about which attrs need to get into config, allowing that to be determined without knowing the value of the last mapAttrsToList arg (which is an option, and thus needs config to have already figured out its set of attribute names in order to access it).
Note that udf couldn’t make it work until he explicitly listed the options he wanted to set, rather than trying to get them out of the mapAttrsToList result somehow.
I suppose the most direct translation of udf’s fix to my example would be:
nix-repl> :p let attrs = { foo = { bar = 1; }; } // { bar = attrs.foo.bar; }; in attrs
{
bar = 1;
foo = { bar = 1; };
}
Do you think “values of an attrset cannot be retrieved until after the set of attributes have been determined” is a limitation of the nix implementation itself, since it’s pretty clear what the result of let attrs = { foo = { bar = 1; }; } // attrs.foo; in attrs should be, and it’s not really recursive by nature.
Given this limitation, do you think explicitly listing the attribute names as udf did is currently the best solution? Other than pushing the list value down, I can’t think of another one. But listing the top attribute names statically doesn’t always work, since it can change based on a config value.
It’s fair to call it a limitation of the nix implementation. Attrsets could have been defined with finer-grained laziness, allowing access to some attributes without having yet realized whether other attributes exist. That may have had unpleasant performance implications, with more thunks being generated and evaluated for the same code.
I think I’d prefer a solution involving defining a helper function and then using it in each top level attr, at least where you were writing the map function’s output as a literal attrset in the file anyway, as in udf’s case:
let
merger = f: lib.mkMerge (mapAttrsToList f cfg.whatever);
in
{
options = ...;
config = {
foo = merger (n: v: {
...
});
bar = merger (n: v: {
...
});
};
}