I’ve been playing around with nix for little programming projects. Nix is a package manager that can build all sorts of things (including an operating system, check out NixOS!)
Let’s write a little C library,
libfoo. Libfoo is two things that we care about:
foo.h. Here’s our source:
We can build
cc -c -g foo.c ar rs libfoo.a foo.o built libfoo.a!
Now let’s package this up in nix. Here’s a simple nix expression that we can include with the project in a
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
If we were instead building some binary, we would want to cp the binary to
$out will refer to something in
/nix/store, something like:
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
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
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,
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
Now, we can build it with
nix-build spits out a freshly built directory! Let’s see what’s in it:
/nix/store/g7sgsbqf5xffvkrjd2vp5z9a8lr6y0wr-libfoo-0.1 ├── include │ └── foo.h └── lib └── libfoo.a 2 directories, 2 files
Looks good. Now, how do we actually use this? Let’s make another little project,
bar, that depends on
libfoo. Bar is going to be really simple, it’s just going to be a
Trying to build it doesn’t work, it doesn’t know where or what
main.c:1:17: fatal error: foo.h: No such file or directory #include <foo.h> ^ compilation terminated.
We need to reference
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
First of all, we aren’t even using make, so we override
buildPhase to call
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
OK, let’s try it:
Nice! Let’s try running the
bar program which hopefully is inside its
hi from foo: this is a test.