This is a nix function that takes an input, stdenv (think of this as coreutils, a basic compiler and set of utilities to build C programs) and it returns a “derivation” which you can think of as a recipe for producing some directory $out, which – in this case – contains a library (libfoo.a) and header (foo.h).

“Installing” in nix is pretty easy: we just dump the things we want into $out.

If we were instead building some binary, we would want to cp the binary to $out/bin.

Eventually $out will refer to something in /nix/store, something like:

/nix/store/ncdxc5m998zipbwwqyqb8idd9di6i76s-libfoo/

where the hash “ncdxc…” is computed using the hashes of all of its dependencies (the inputs to the function, or stdenv) and the source code (a hash of the contents of src).

If we update our nix package repository, and stdenv is updated for some reason, installing libfoo will result in a different hash as it depends on a different version of stdenv. If we made a change to the Makefile (even adding a space, not changing the build products) the hash would change. If we removed that space, the hash would again be “ncdxc…” and – here’s the cool part – if we had previously built the package with that hash, and install it again, nix wouldn’t need to do anything, it knows it has that exact version!

Notice there is no call to make or gcc in our recipe. That’s because stdenv.mkDerivation by default knows how to build projects using Make: it will run Make for us. And if our little project had used autotools, we wouldn’t need to set up the installPhase either, as Nix runs make install. Thankfully we don’t need to use autotools (phew!) and can simply override installPhase with our simple script.

(There are, naturally, other phases you can override, configurePhase, buildPhase, etc. Nix will try to run ./configure since we don’t override configurePhase but since the script doesn’t exist it will just skip that step.)

Nix allows to add this expression to the overall set of nix expressions really easy in ~/.nixpkgs/config.nix; we might have something like:

Ignore the complicated packageOverrides function; what this does is adds a libfoo package to our nix packages (which is defined by importing the function at ~/proj/libfoo/default.nix and calling it with our stdenv package).

We need to reference libfoo.a and foo.h somehow. Let’s try this nix expression. We’re going to add it directly to ~/.nixpkgs/config.nix (inisde the packageOverrides function) rather than import fron a default.nix file:

First of all, we aren’t even using make, so we override buildPhase to call gcc directly:

gcc -o bar main.c $libfoo/lib/libfoo.a

Woah, what’s this reference to $libfoo/lib/libfoo.a? How do we translate $libfoo to the directory installed in /nix/store? That’s what the inherit libfoo line does. In the derivation, each attribute is available to the builder during build/configure/install phases. So $libfoo is dereferenced to its location in the nix store.

$libfoo is the current libfoo attribute in our nixpkgs, meaning that bar will not depend on one specific version. If we happen to change libfoo (tweak the source code, whatever), then the next time we build bar, nix will rebuild libfoo and $libfoo will reference the updated version.

You would need to make a separate package (say, libfoo_0_1) to depend on a specific version.

In addition, gcc knows where the <foo.h> header is from the buildInputs line. This sets up the include path for the compiler so that it knows about $libfoo/include.