Passing options to modules with `lib.nixosSystem`

Consider, for example, the suggested home manager configuration here, particularly this chunk:

 home-manager.nixosModules.home-manager
{
 home-manager.useGlobalPkgs = true;
 home-manager.useUserPackages = true;
 home-manager.users.jdoe = import ./home.nix;
}

This looks like an ordinary function call, but that’s not how it is actually treated. home-manager.nixosModules.home-manager resolves to the module here and, as such, it’s expecting arguments like config, pkgs, lib, etc. In any other context, the “function application” above would raise an error—but not when this code is used with lib.nixosSystem.

I am aware that the module system is responsible for calling modules with the arguments they’re expecting (see this thread for instance). But usually, I see the modules or imports arguments used to list modules by themselves (in the form of functions), not modules that have options passed to them like the code above. I’m trying to figure out how this option-passing mechanism works.

So my question is, what exactly happens to the attribute set shown above? How does the module become aware of these options?

There is clearly some magic happening here. For instance, I can replace the code with this

 home-manager.nixosModules.home-manager {} {} {} {} {} {}

and I do not get an error, which means this code is not being treated like a function application. I am particularly disturbed that if I try to factor the original code like this

 nixosConfigurations = let hm = home-manager.nixosModules.home-manager
{
 home-manager.useGlobalPkgs = true;
 home-manager.useUserPackages = true;
 home-manager.users.jdoe = import ./home.nix;
}; in {
      hostname = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
         hm
        ];
}

then I get this error

error: anonymous function at /nix/store/27g251cjy32c69j4zwz0xlf1imym255n-source/nixos/default.nix:1:1 called without required argument 'lib'

The last error makes perfect sense: I’m trying to call a function without its required arguments. But I’m disturbed that I don’t get this error with the original example. Clearly, some combination of laziness and reflection is being used to take the original code and… do something else.

What exactly is happening here?

I think you’ve just misunderstood Nix’s list syntax.

This is not function call syntax. This is two list elements. In Nix, list elements are delimited by whitespace, not commas or anything like that. So home-manager.nixosModules.home-manager is one element in the list, and the following attrset is an independent element in the list. That’s why this worked:

It’s just a bunch of independent list elements, and all but the home-manager one is just an empty module.

Yes, it’s incredibly annoying that function call syntax and list syntax both use whitespace delimitation. This has bitten me, a fairly experienced Nix user, more times than I can count.

1 Like

To expand a little bit upon the previous answer, you are calling the lib.nixosSystem function, and you are passing it a list of modules. In your case, this list consists of two modules, the first being home-manager.nixosModules.home-manager, the second being a so-called inline module, which looks exactly like what you would write if you’d define a module as a .nix file.

In your example, the inline module was a simple attrset, but you can use the full function form of modules as well (including defining options and everything) and pass them inline, like this:

lib.nixosSystem {
  modules = [
    ({ config, lib, pkgs, ...}: {
      options = {
        myOption = lib.mkOption {
          type = lib.types.str;
        };
      };
      config.home-manager.useGlobalPkgs = true;
    })
    ({ config, lib, pkgs, ...}: {
      home-manager.useUserPackages = true;
      myOption = "hello!";
    })
  ];
}

This can easily become pretty heavy syntax-wise, so it’s often advisable to define modules in their own files, but it can be nice for very short modules.

Also note that we need to use parentheses around each module to avoid nix interpreting every space as a delimiter of list elements.

1 Like

Thanks both repliers.

Yes, it’s incredibly annoying that function call syntax and list syntax both use whitespace delimitation. This has bitten me, a fairly experienced Nix user, more times than I can count.

Don’t worry, this is now permanently etched into my memory.