I'm working to understand as much as I can about Nix flakes. I'm puzzled by the fact that a nixpkgs
input is usually imported, and the imported value is called as a function. How does the result of import nixpkgs
map to code in the nixpkgs
flake?
It looks like this use of nixpkgs
is common practice in flakes:
# flake.nix
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
/* ... */
};
outputs = { self, flake-utils, nixpkgs /*, ... */ }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = (import nixpkgs) {
inherit system;
};
in
{
/* ... */
}
);
}
My understanding is that the nixpkgs
value in this flake's outputs
function is the attribute set produced by the nixpkgs
flake. I understand that flake output is a derivation, and a derivation can be imported. But how does the imported value become a function? I expected it to be an attribute set.
I see that the nixpkgs flake includes a lib
output. Is there some mechanism where an attribute with a lib
attribute path is callable? I have been looking for information on this, but I have not found anything.
If (import nixpkgs) {}
is effectively calling that lib
attribute, then how does importing differ from calling nixpkgs.lib
directly? From what I've read importing a derivation has some effect on either forcing evaluating, or not forcing evaluation of something. I don't understand the details yet.
Flakes behave like paths because of their .outPath
attribute, which contains the flake source path that is automatically added to all flakes by Nix.
This lets import
load the file as a Nix value.
Specifically, because the path is a directory, it loads the default.nix
in it, which then loads impure.nix
, which contains a function.
Note that the way flakes are evaluated (without --impure
), the evaluation is always pure, despite the "impure.nix" file name above.
The .outPath
attribute got its name because it is also responsible for making derivations coercible to strings.