After about one year of using nix and nixos, I’m starting to “get” it. I think about the whole ecosystem as basically three layers. The top most layer is the nix language layer, which I think of as the haskell layer or the functional-programming layer. Without prior functional programming knowledge, the language is eventually going to cause you problems. That top layer and the nix language expressions written in it then feed into the middle layer, which I think of as the shell layer. The top layer creates a perfectly bespoke shell environment for you to run all kinds of shell things and build-y things like make and autoconf and all that to create your package. Then that middle layer feeds into the bottom layer which is the nix store, which is closest to a file-system layer. But really it’s an abstraction atop the file system. I usually just think of the filesystem as “the filesystem”, but with nix-store it’s best to think of it as a layer atop of it.

So to recap, the three layers are

  1. the haskell layer (nix language layer)
  2. the bash / shell layer (the bespoke shell with env vars and tools create from layer 1)
  3. the file system layer (the result of builds from layer 2)

Now this can kind of go fractal, as the module system is kind-of atop the haskell-layer, as it’s a domain specific language written in nix. But eventually the module system descends downward and goes through the shell-layer and file-system layer.

Nixpkgs is a huge piece of code that is clearly in the haskell-layer. You can get away with not knowing how to write nix by using lots of boilerplate from nixpkgs, but you might hit a wall eventually when trying to do something custom.

The shell layer is inhabited by things like composing $PATH, $RPATH, environment variables, the “standard environment” package, and lots of wild bash-isms. I mean the standard environment basically is a 2000-line clever bash script that’s meant to be an abstraction atop all kinds of build systems. Also in this layer is autoconf, a build tool populated by monstrous shell scripts and perl scripts and the m4 macro language. It’s what’s used to build nearly all GNU projects, so it’s inescapable. This layer seems to be the least discussed and documented. But if you ever find some project that isn’t in nixpkgs, you’ll probably spend a lot of time debugging in this layer. If you find yourself here, you best hope your bash skills and build-tools knowledge will see you through to the final layer.

Finally we get to the filesystem layer where everything eventually goes to live. Coding against this layer usually happens between machines, as in via the substitute method where you can download a build from a remote /nix/store. In essence you’re going straight from layer 1 to layer 3, by letting some other machine do the layer 2 for you.

I don’t know if this is helpful or not, but if it is I can make a better presentation on this. It’s helped me understand the wild world that is nix and nixos and think about what layers different tools live in. I learned all this by forcing myself to go through the QEMU 2020 advent calendar and build nix expressions to make all of the images run. Sadly that site doesn’t seem to be up anymore https://www.qemu-advent-calendar.org/. My code is here if anyone is interested: https://github.com/idrisr/qemu-advent