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
.outPathattribute, which contains the flake source path that is automatically added to all flakes by Nix.This lets
importload the file as a Nix value.Specifically, because the path is a directory, it loads the
default.nixin it, which then loadsimpure.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
.outPathattribute got its name because it is also responsible for making derivations coercible to strings.