The following is a minimal reproducer for an infinite recursion error when building a nixos configuration:
(import <nixpkgs/nixos>) {
configuration = { pkgs, ... }: {
options = builtins.trace "Building a system with system ${pkgs.system}" {};
};
system = "x86_64-linux";
}
When evaluated it fails as follows, unless the reference to pkgs.system is removed:
$ nix-build
error: infinite recursion encountered
at /Users/charles/.nix-defexpr/channels/nixpkgs/lib/modules.nix:496:28:
495| builtins.addErrorContext (context name)
496| (args.${name} or config._module.args.${name})
| ^
497| ) (lib.functionArgs f);
If we look at the implementation of nixos/lib/eval-config.nix:33, we see that the value passed for the system argument is set as an overridable default in pkgs. Does this mean we can't access it until later in the evaluation process?
(In the real-world use case, I'm introspecting a flake -- investigating someFlake.packages.${pkgs.system} to find packages for which to generate configuration options.)
This has been cross-posted to NixOS Discourse; see https://discourse.nixos.org/t/accessing-target-system-when-building-options-for-a-module/
In order for the module system to construct the configuration, it needs to know which
configandoptionsitems exist, at least to the degree necessary to produce the root attribute set of configuration.The loop is as follows:
configoptionspkgs(your code)config._module.args.pkgs(definition of module argument)config(loop)It can be broken by removing or reducing the dependency on
pkgs. For instance, you could define your "dynamic" options astype = attrsOf fooinstead of enumerating the each item from your flake as individual options.Another potential solution is to move the option definitions into a submodule. A submodule without
attrsOfas inattrsOf (submodule x)is generally quite useless, but it may create a necessary indirection that separates your dynamicpkgs-dependentoptionsfrom the module fixpoint that haspkgs.