Is my understanding about nixos modules correct?

Hi everyone, I’m new to nix ecosystem. I’m trying to understand more about module system.
After some reading, guessing and testing, I get some some conclusions which I’m not sure if they are true.
Please let me know if they are wrong.

hostname = nixpkgs.lib.nixosSystem {
  system = "x86-64_linux";
  modules = [
    # ...
  ];
};
  1. lib.nixosSystem collects all modules recursively, including module-list.nix
  2. If a module is function, a attribute set will be passed to it. All module functions will get the same attribute set.
  3. Modules are declared but not totally evaluted, a module’s attributes are not evaluted.
  4. All modules are merged togather, become one module. So Environment.systemPackages may looks like <chunk1> ++ <chunk2>
  5. Every attribute in this module is evaluted recursively, a lot of low-level options defined in it and I get my system configuration.
  6. The order of modules doesn’t matter, moduleA imports moduleB works the same a moduleB imports moduleA.

I’m not a English speaker, sorry if I did not express myself well.
Happy Chinese New Year!

Pretty sure you have it right. Some small points:

I’m not entirely sure what you mean here. If a module is to be turned into something meaningful it has to be “evaluated”, which means parsing all its attributes and computing their values. Are you trying to describe nix’ laziness?

Yes, but note that different data types have different rules for “merging”. If you define a list option - like environment.systemPackages - twice, the two lists will be concatenated.

Two number options on the other hand will result in a collision error instead, because there is no natural way to “merge” them.

In either case, you can force a priority for definitions with the library functions lib.mkDefault, lib.mkForce or lib.mkOverride.

This also allows forcing lists not to merge, which is sometimes useful (though probably not for environment.systemPackages).

Thank you man, I’m more confident after your approval.
I will change my pc to nixos tomorrow.

Modules are declared but not totally evaluted, a module’s attributes are not evaluted.

I mean functions can’t be merged, right? So if module is a function, it’s return value’s attribute is some like a lazy viriable() at step3, the attribute set is declared, we konw it’s attributes, and a attribute is defined by some chunks, but the attribute’s value isn’t evaluted yet, so we can use one option’s defination in another option’s defination later, at eval time, after all modules merged.

The merging rules doesn’t scare me, some other languages do the some thing.

I’m so drunk man, I’ll try to explain myself better tomorrow.
Happy Chinese New Year!

2 Likes

What you wrote is mostly correct.

One thing to note is that 1 & 2 are happening at the same time, as each module can have an imports list, which is used to aggregate all modules before the evaluation of options.

Then 3, 4 & 5 are happening together. The resolution of options causes the recursive evaluation and merging of modules into one. The non-evaluated config is given as argument in 1 & 2, as well as being returned such that callers of evalModules, like nixosSystem, trigger the evaluation of the configuration.

Understanding the lazy evaluation from an imperative point of view is a mind bending process, as you are mixing the construction of a function waiting to be evaluated with the computation to be evaluated later on.

However, the order of modules does matter. The order is defined as being undefined, as this is a non-local resolution which cannot easily be understood by looking at a single module. This is why lib.mkOrder / lib.mkBefore and lib.mkAfter are for, to provide some ordering when necessary.