以下是构建nixos配置时无限递归错误的最小再现器:
(import <nixpkgs/nixos>) {
configuration = { pkgs, ... }: {
options = builtins.trace "Building a system with system ${pkgs.system}" {};
};
system = "x86_64-linux";
}
当评估时,它失败如下,除非删除对pkgs.system
的引用:
$ 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);
如果我们查看nixos/lib/eval-config.nix:33
的实现,我们会发现为system
参数传递的值在pkgs
中被设置为可重写的默认值。这是否意味着我们要到评估过程的后期才能访问它?
(在真实世界的用例中,我正在内省一个薄片——研究someFlake.packages.${pkgs.system}
,以找到要为其生成配置选项的包。(
这已经被交叉发布到NixOS话语;看见https://discourse.nixos.org/t/accessing-target-system-when-building-options-for-a-module/
为了让模块系统构建配置,它需要知道存在哪些config
和options
项,至少到了生成配置的根属性集所需的程度。
循环如下:
- 评估
config
中的属性名称 - 评估
options
的属性名称 - 评估
pkgs
(您的代码( - 评估
config._module.args.pkgs
(模块参数的定义( - 评估
config
中的属性名称(循环(
可以通过删除或减少对pkgs
的依赖来破坏它。例如,您可以定义您的";动态的";选项为type = attrsOf foo
,而不是将薄片中的每个项目枚举为单独的选项。
另一个潜在的解决方案是将选项定义移动到子模块中。attrsOf (submodule x)
中没有attrsOf
的子模块通常是无用的,但它可能会创建一个必要的间接方法,将动态pkgs
相关的options
与具有pkgs
的模块固定点分离。
(import <nixpkgs/nixos>) {
configuration = { pkgs, lib, ... }: {
options.foo = lib.mkOption {
type = lib.types.submodule {
options = builtins.trace "Building a system with system ${pkgs.system}" { };
};
default = { };
};
};
system = "x86_64-linux";
}
nix-repl> config.foo
trace: Building a system with system x86_64-linux
{ }
作为避免递归不可行的情况的替代方法,可以在调用nixos/lib/eval-config.nix
时使用specialArgs
来传递无法通过模块系统覆盖的最终值:
let
configuration = { pkgs, forcedSystem, ... }: {
options = builtins.trace "Building a system with system ${forcedSystem}" {};
};
in
(import <nixpkgs/nixos/lib/eval-config.nix>) {
modules = [ configuration ];
system = "x86_64-linux";
specialArgs = { forcedSystem = "x86_64-linux"; };
}