NixOS module を書いているときに、入力の config
の値によって出力の config
の値を変えるようなものを書くと infinite recursion encountered
と言われてエラーになってしまいます。具体的には以下のような例で困ります:
{
config,
pkgs,
lib,
...
}: {
options = {
hoge = { # hoge.foo というオプションを用意する。
enable = lib.mkEnableOption "hoge enable";
foo = lib.mkOption {
type = lib.types.enum ["opt1" "opt2"];
default = "value1";
};
};
};
config = let
cfg = config.hoge;
value = {
opt1 = {
environment.systemPackages = [pkgs.firefox];
};
opt2 = {
environment.systemPackages = [pkgs.thunderbird];
};
};
in
# hoge.foo の値によって何を設定するか変える。
lib.mkIf cfg.enable value."${cfg.foo}";
}
これを実行すると以下のような infinite recursion encountered
のエラーが出ます:
error: infinite recursion encountered
at /nix/store/nzkmbbqv7r856kckd7v8ywz23lkmgh0w-source/lib/modules.nix:257:21:
256| options
257| config
| ^
258| specialArgs
なぜ inifinite recursion になるのかは正直よく分からないのですが、色々変えながらテストしてみると、どうやら出力の config
の一番外側の attrset を評価するときに入力の config
を使うとダメなようです。逆に、attrset の内側で使うようにすればエラーにはなりません。例えば、今回の例では environment
の値しか設定しないので、このフィールドを明示的に展開するとうまくいきます:
# ...
lib.mkIf cfg.enable {
# environment の value の部分でのみ config を使うと大丈夫。
environment = value."${cfg.foo}".environment;
};
}
ただこの方法だと、設定したい全てのフィールドについて展開する必要があり、フィールドの数が増えると面倒になってしまいます。
フィールドの数が増えてもスケールする方法として lib.mkMerge
と lib.mkIf
を組み合わせる方法があります。自分が試した限りではこの方法が一番良さそうです:
# ...
lib.mkIf cfg.enable (lib.mkMerge [
(lib.mkIf (cfg.foo == "opt1") value.opt1)
(lib.mkIf (cfg.foo == "opt2") value.opt2)
]);
}
opt1
や opt2
に当たるものを二回書く必要があるのが微妙なのですが、とりあえずこれで運用してみます。もっと良い方法を知っている方は @[email protected] までぜひ教えてください。