Infinite recursion on optional import

Hello,

I’m trying to write the following code, in order to have a configuration that can adapt to virtual machines (by removing the hardware configuration). So I wrote:

{ config, pkgs, lib, isVM ? false, ... }:
{
  imports =
    (lib.optional (!isVM) ./hardware_configuration.nix) ++
    [
      ./users_management.nix
    ];
}

But it fails with:

error: infinite recursion encountered, at /nix/store/m59d3v89nhp9207hgvf6v21wxq7lhq64-source/lib/modules.nix:217:28`

I tried to put an empty file inside hardware_configuration.nix, and the error is present, until I remove the line (lib.optional (!isVM) ./hardware_configuration.nix). Any idea what’s wrong?

– EDIT –
It’s actually weird enough that, with a nearly empty bidon.nix file:

(if (!isVM) then [ ./bidon.nix ] else [ ./bidon.nix ]) ++

also fails, but:

[ ./bidon.nix ] ++

works. So it’s like if nix has trouble to give a value to isVM, but the isVM ? false should make it clear no?

Unfortunately this won’t work.

The module system separate between special arguments (e.g., lib, config, options) and “regular” arguments (the rest). In this case isVM is a regular argument.

When a module is loaded its arguments are introspected and regular arguments mapped to config._module.args.${name} where ${name} is the name of the argument. In other words,

lib.optional (!isVM) ./hardware_configuration.nix

is effectively the same as

lib.optional (!config._module.args.isVM) ./hardware_configuration.nix

which would explain the infinite recursion. Namely, to build the configuration we have to import the modules and to import the modules we have have to evaluate config._module.args.isVM, but to evaluate config._module.args.isVM we have to build the configuration…

Just to offer some alternative solution. Try importing ./hardware_configuration.nix unconditionally, i.e.,

import = [
  ./hardware_configuration.nix
  ./users_management.nix
];

Then edit ./hardware_configuration.nix to something like

{ lib, isVM, ... }:

lib.mkIf (!isVM) {
  …
}

with a bit of luck that should be sufficiently lazy to avoid the infinite recursion.

2 Likes

Hum too bad, thanks for the answer. I tried, but I get an error:

error: attribute 'isVM' missing, at /nix/store/m59d3v89nhp9207hgvf6v21wxq7lhq64-source/lib/modules.nix:217:28

What is the proper way to “transmit” the value of isVM to the module? I even tried to set it one more time in the arguments:

{ config, pkgs, lib, isVM ? false, ... }:
lib.mkIf (!isVM) {
 # ...

Thanks to clever and q3k[m] on irc, I finally made it. The idea is to define instead a small module containing an option, and then the VM code will just enable that option. For example, you can create a file module_isVM.nix containing:

{ lib, pkgs, config, ... }:
with lib;                      
let
  cfg = config.custom.isVM;
in {
  # Enable it with "custom.isVM.enable = true;"
  options.custom.isVM = {
    enable = mkEnableOption "if you are inside a VM.";
  };

  # config = mkIf cfg.enable {
     # You can even put here some code specific to the VM:
  # };
}

Then you import that file in your configuration.nix:

{ config, pkgs, lib, ... }:
{
  imports =
    [
      # Defines a custom module to check if we are in a VM or not
      ./module_isVM.nix
      # Other files
      ./hardware_configuration.nix
      # ....
    ];
    # ...
}

and then you can refer to your option using config.custom.isVM.enable, for example in hardware_configuration.nix:

{ config, pkgs, lib, ... }:
# Do not use if we are in a virtual machine
lib.mkIf (!config.custom.isVM.enable) {
    boot.kernelParams = ["console=ttyS1,115200n8"];
}

Finally, you can enable this for your VM by defining in your network.nix something like:

{
  test = {pkgs, config, ...}:
    {
      imports = [ ./configuration.nix ];
      custom.isVM.enable = true;
    };
}
1 Like