A cool function to import nix modules automatically

I want to share a cool function. get_import_dir will search hierarchically your directory for .nix modules. This way, you can freely manage your nix configuration in different module files and folders without even having to declare these modules in your main configuration file. You can edit file_to_not_import to add file names to ignore by the function. ./. means the current directory.

Here is the beginning of my configuration file:

{
  pkgs,
  lib,
  ...
}:

let
  file_to_not_import = [
    "flake.nix"
    "configuration.nix"
    "userdata.nix"
  ];
in
let
  userdata = import ./userdata.nix;
  get_import_dir =
    dir:
    lib.flatten (
      lib.pipe dir [
        builtins.readDir
        (lib.filterAttrs (name: type: type == "directory" || lib.hasSuffix ".nix" name))
        (lib.filterAttrs (name: _: !(lib.elem name file_to_not_import)))
        (lib.mapAttrsToList (
          name: type: if type == "directory" then get_import_dir (dir + ("/" + name)) else dir + ("/" + name)
        ))
      ]
    );
in
{
  imports = get_import_dir ./.;

Adapt the code as you need.

6 Likes

Nice! You might also (although it’s more complicated so that comes with its own tradeoffs) be interested in haumea if you are interested in autoloading :slight_smile:

1 Like

I wrote something similar, but it’d be simpler to use listFilesRecursive:

{ lib, ... }:
let
  inherit (builtins) filter map toString;
  inherit (lib.filesystem) listFilesRecursive;
  inherit (lib.strings) hasSuffix;
in
{
  imports = filter (hasSuffix ".nix") (
    map toString (filter (p: p != ./default.nix) (listFilesRecursive ./.))
  );
}

I also created a separate top-level dir modules, so that all files can be imported and I don’t have to write these kinds of exclusion criteria like filtering out flake.nix etc. The code above went into modules/default.nix, and all modules can be imported via adding modules/ to imports.

Of course you can modify it to adapt to your usecase.

3 Likes

Based on your example, I changed my version to:

{
  pkgs,
  lib,
  ...
}:

let
  inherit (builtins) filter map toString;
  inherit (lib) pipe;
  inherit (lib.filesystem) listFilesRecursive;
  inherit (lib.strings) hasSuffix;
in
let
  userdata = import ./userdata.nix;
  import_modules = pipe ./modules [
    listFilesRecursive
    (map toString)
    (filter (hasSuffix ".nix"))
  ];
in
{
  imports = import_modules;

In the last line:

imports = import_modules;

the function is called. But, I am curious, how to assign the function itself to a variable in Nix?

What’s the confusion? Functions can be bound.

The previous line is calling the function itself. How can it bound?

Bound to a variable, e.g. double = x: x*2

How do you create a function taking no parameters?

How can you assign this function to another variable without calling it?

nix-repl> double = x: x*2

nix-repl> x = double

nix-repl> x == double
false

Why false?

because it is assignment not equals.

Assignment to the same thing?

It should be equal then.

let
  f1 = x: x + 1;
  f2 = x: x + 1;
in
  builtins.toString f1 == builtins.toString f2

Is what you’re searching for?

Due to the fact that Nix is functional comparing function behavior rather than function identity is the thing to aim for :dart:

OK, you are comparing the names. It is not really the same.

I don’t get the logic behind that.

This test is not sensible, you cannot compare functions with ==.

toString does not work on functions either.

1 Like

OK. We can do it in Python. I was expecting the same.