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?

1 Like

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…

2 Likes

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