Non-Flaky builds with Nix Flakes and Hakyll

Let’s start with a beautiful quote, which also can act as a great summary of what I want to share:

“If you need a readme.md explaining how to build a project, you already failed” – Eelco Dolstra

With this in mind, try to think back if you ever tried to follow along in a tutorial or workshop just to realize that you need to climb mountains just to get the code to run. Or perhaps you’re even one of those people that gave up on Haskell (or any “non-mainstream” language) due to all the hassle to just make that “Hello World” program run?

The future is here, don’t trust me? Follow along (might take a while the first time and require many GBs of data) and experience it yourself:

Note: I did this on Arch Linux. You might need to adapt based on your OS.

Prerequisite (P1): 

    1. Install Nix, see https://nixos.org/download.html
    2. Activate flakes and commands + add cache server:
      1. nix-env -f '<nixpkgs>' -iA nixUnstable
      2. mkdir -p ~/.config/nix
      3. echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
      4. echo 'allow-import-from-derivation = true' >> ~/.config/nix/nix.conf
      5. sudo su
      6. echo 'substituters = https://cache.nixos.org https://hydra.iohk.io' >> /etc/nix/nix.conf
      7. echo 'trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=' >> /etc/nix/nix.conf
      8. exit
      9. nix run github:nixos/nixpkgs/nixpkgs-unstable#hello
      10. Observe how “Hello, world!” was printed.

Now when the one-off boilerplate has been done, let’s get to action:

  1. nix run github:petercrona/nix-flake-haskell-hello-world
  2. Observe how “Hello, Haskell” is printed. Run it again and enjoy not having to wait.
  3. nix build github:petercrona/nix-flake-haskell-hello-world
  4. Observe how a new symlink “result” is created in the directory you are in, which leads to a directory with the binary.
  5. git clone https://github.com/petercrona/nix-flake-haskell-hello-world.git
  6. cd nix-flake-haskell-hello-world
  7. nix run
  8. Observe how “Hello, Haskell” is printed.
  9. nix build
  10. note the symlink result, which points to a folder with the binary.

You have now compiled “Hello World” in Haskell using the state-of-the-art GHC compiler version 8.10.7, without having to think about which version, if any, you already had locally installed. The same could be done with Python (goodbye Python 2 vs 3 issues) and in theory pretty much anything else. It’s not all red roses and green trees. Some setup was needed and you might have noticed it took a while. But, try again, and enjoy the power of caching! Nix isn’t the only way to accomplish this of course, but my favorite so far.

Side note: While I’m employed by Small Improvements, we do not actually use Hakyll nor Haskell (more than for occasional small ad-hoc projects), and we only use Nix a little. However, as a company we support our employees to be entrepreneurs or work on projects they find meaningful outside of work (for instance by offering part-time and 5 days of good-cause leave). I make use of this to work on several projects, one of them being https://www.babyfriendlyair.com . BabyFriendlyAir is a blog with the mission to spread awareness about air quality. It puts great weight on offering well-referenced information that can help people like you (especially if you have a little baby at home) understand the importance of clean air as well as get ideas of what you can do to reduce your exposure to dirty air. It also contains some experimental stuff, e.g. how I measure air quality (code included) with various sensors and an Arduino. Perhaps a future blog post here will be how I use citeproc for automatic reference management. Let’s jump back to the post now though.

“Hello world” is perhaps not too hard to compile in general, it often doesn’t get messy until you have dependencies. So let’s continue with a slightly more advanced example, namely running a static website generator called Hakyll. This static website generator can be seen as a static website generator generator, you basically compile a static website generator based on your specification. This is then used to generate your static website based on markup (or markdown) and styling. Let’s get to work by looking at how it actually works.

From nothing to a static website running with Hakyll

Firstly, make sure you installed Nix and configured it according to the prerequisite (P1). Once you got that sorted, let’s continue, unfortunately with some more prerequisites firstly:

Prerequisite (P2):

  1. export LOCALE_ARCHIVE=/usr/lib/locale/locale-archive
    OR
  2. add export LOCALE_ARCHIVE=/usr/lib/locale/locale-archive to your “.bashrc”-file and restart the terminal, so you never have to do it again.

Now continue with:

  1. mkdir blog
  2. cd blog
  3. create and edit flake.nix (e.g. nano flake.nix), add content:
{
  description = "Hakyll with Nix";

  inputs.haskellNix.url = "github:input-output-hk/haskell.nix";
  inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils, haskellNix }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        overlay = self: _: {
          hsPkgs =
            self.haskell-nix.project' rec {
              src = ./.;
              compiler-nix-name = "ghc8107";
            };
        };
        pkgs = import nixpkgs {
          inherit system;
          overlays = [
            haskellNix.overlay
            overlay
          ];
        };
        flake = pkgs.hsPkgs.flake { };
      in
        flake // {
          packages.default = flake.packages."blog:exe:site";
          apps.default = {
            type = "app";
            program = "${flake.packages."blog:exe:site"}/bin/site";
          };
        }
    );
}
  1. nix shell nixpkgs#haskellPackages.hakyll -c hakyll-init .
  2. nix run . watch
  3. go to localhost:8000
  4. Enjoy!

Admittedly, the “flake.nix”-file isn’t trivial. At its core the concept is simple though, a flake is a git repository with a “flake.nix”-file, and a “flake.nix”-file specifies inputs (dependencies) and outputs (e.g. packages). There’s also a repo for the above example. So you can do:

  1. nix run github:petercrona/nix-flake-hakyll watch
    (while in the folder with the Hakyll project – so not a big win in this case)

OR just clone it and run:

  1. git clone https://github.com/petercrona/nix-flake-hakyll.git
  2. cd nix-flake-hakyll
  3. nix run . watch

You might have noticed that Haskell.nix was used in the flake. Haskell.nix is very advanced and built to offer a lot of flexibility to Haskell developers, perhaps at the expense of some simplicity and some of the beautiful Nix ideas. The big advantage is that you can easily make an existing complex Haskell project work with Nix. For another more ambitious project of mine (TimelyFeedback – still closed beta), I started without Nix, only using Stack to build the project, but then transitioned to building using Nix with ease thanks to Haskell.nix being able to understand my Stack config.

To conclude, we have shown:

  1. Nix helps you to specify and execute a reproducible build and development environment.
  2. Nix helps you avoid messing around with installing compilers etc. and let’s you run the code without climbing a mountain first.
  3. Nix helps you avoid issues with different versions. If your computer is running python 3 and you need 2, or perhaps the wrong version of node, Nix happily solves that for you.
  4. You had to fulfill some prerequisites and download / compile a lot. But that’s just due to us being early adopters and/or generic one-offs. Keep using Nix and the experience just gets nicer / quicker 🙂

I hope you got everything to work (especially if you’re not running Linux) and learned as much as I did.

References / further reading

[1] Getting started with Haskell.nix in a Flake (flake.nix file based on this) https://input-output-hk.github.io/haskell.nix/tutorials/getting-started-flakes.html

[2] Great video about Flakes https://youtu.be/UeBX7Ide5a0?t=240

[3] Practical Nix Flakes / intro to flakes https://serokell.io/blog/practical-nix-flakes

[4] Yet a great intro to Flakes https://www.tweag.io/blog/2020-05-25-flakes/

[5] The prerequisite P2 was found here: https://www.slamecka.cz/posts/2020-06-08-encoding-issues-with-nix-hakyll/

[6] Great video about Hakyll: https://www.youtube.com/watch?v=t8gim17hryw