Import as a default argument doesn't work

Hi,

I’ve started to create my own library with some usual functions

# mylyb.nix
rec {
  mkAllowIp = ip: "allow ${ip};";
  allowBlock = iplist:
    builtins.concatStringsSep "\n" (builtins.map mkAllowIp iplist) +
    "\ndeny all;";
}

I thought I could import my lib with the following code in a module like this

# mymodule.nix
{ lib, config, mylib ? import ./mylib.nix, ... }:

let
  allowedIps = mylib.allowBlock [ "1.2.3.4" "4.5.6.7" ];
  cfg = config.mod-syncthing;
  inherit (config.networking) hostName;
in {
imports = [ ];
options = { };
config = { 
    services.nginx = {
      enable = true;
      virtualHosts."${hostName}" = {
        locations."/" = {
          extraConfig = ''
            ${allowedIps}
          '';
        };
      };
    };
  };
}

But I got this error message at build error: attribute 'mylib' missing

this code works with an import in let / in

# mymodule.nix
{ lib, config,  ... }:

let 
  mylyb = import ./mylib.nix;
  allowedIps = mylib.allowBlock [ "1.2.3.4" "4.5.6.7" ];
  cfg = config.mod-syncthing;
  inherit (config.networking) hostName;
in {
...
}

The module.nix file is added to my nixos configuration in another module with
imports = [ ./mymodule.nix ];

Why does the { lib, config, mylib ? import ./mylib.nix, ... }: import method not work ?

The reason this doesn’t work is that you can’t just add arguments to a module, since those are populated by the module system.

I recommend working though the documentation team‘s new module system deep dive tutorial to get a better feeling for how modules work: Module system deep dive — nix.dev documentation

It is possible to extend the regularly passed lib with your own functions by adding those as an overlay along these lines:

{ config, pkgs, ... }:

let
  mylibOverlay = self: super: {
    lib = super.lib // {
      myNewFunction = args: ...;  # Your custom function
    };
  };
in
{
  nixpkgs.overlays = [ mylibOverlay ];
}

The module system semantics are quite particular, so it’s not unusual to run into this sort of confusion. Unfortunately the way nixos-rebuild encourages users to think about how modules end up determining the system configuration obscures the actual mechanism, and the NixOS code itself doesn’t necessarily help clarifying how it actually works. The best piece of explanation I’m aware of is @infinisil‘s The Nix Hour: https://m.youtube.com/watch?v=cZjOzOHb2ow&pp=ygUbVGhlIG5peCBob3VycyBtb2R1bGUgc3lzdGVt

That should eventually become a written article, someone just has to sit down and do it.

2 Likes

Thank you for your detailled answer.

I tried your suggestion. It works if I use pkgs.lib.myNewFunction but not if I use lib.myNewFunction.
What is the difference between lib and pkgs.lib ?

Ah, that surprised me actually, but as I suspected it’s because how it’s wired up. lib is spliced in at the module system level (independent of NixOS and before any actual modules come into play), but pkgs is added later and set up to actually use overlays configured via module. See here how exactly it’s done, and especially that it doesn’t touch lib: https://github.com/NixOS/nixpkgs/blob/432b9bd6208071be5035efa7e275f906f252861c/nixos/modules/misc/nixpkgs.nix

I guess that could be changed.

I recommend taking a look at the code, especially trying to wrap your head around how evalModules gets called and how it’s defined. Start from here: https://github.com/NixOS/nixpkgs/blob/432b9bd6208071be5035efa7e275f906f252861c/nixos/lib/eval-config.nix

Be warned that there are lots of historical artifacts for compatibility that may make it hard to read in some corners.

You would need to also add this to specialArgs when calling nixos, in order for this to work well.
When using classic nix, this is problematic, since you cannot easily control the call to the nixos entry point, as it’s made by nixos-rebuild. With flakes, you can pass this into the usual nixosSystem function.

This has nothing to do with classic Nix or flakes, it’s just that nixos-rebuild happened to do it Wrong™ before flakes (by obscuring the actual invocation of evalModules with two magical layers of indirection and lots of impurity), and somewhat less wrong with flakes. All it really needs to do is build an expression you point it to and then run the resulting activate script on the target machine. There are some devilish details such as Nix version migrations, but it really doesn’t need to be as obfuscated as it is now.

That was my point indeed.