Nix Notes: Overriding Haskell Packages


Setup

In one of the projects, I use a pinned version of nixpkgs based on miso. There are two advantages to that, one is that I know that miso builds with that version, and the second one is that I can take advantage of miso’s cachix cache and not compile everything from scratch.

In the most basic form, it looks like this.

File nixpkgs.nix:

let

  bootstrap = import <nixpkgs> {};

  misoTarball = bootstrap.fetchFromGitHub {
    owner = "dmjio";
    repo = "miso";
    rev = "2c193a3253216d70f0ac182fbe9c801de00363ae";
    sha256 = "1ywksdzcfd339x1hxp5pvkgbv9mdy1y0971k8v161hg33na2p8wz";
  };

  miso = import "${misoTarball}" { } ;

  inherit (miso) pkgs;

in miso.pkgs

I can then use it in another file to compile my local Haskell package named automation-server.

File default.nix in the same directory as the cabal file:

let
  pkgs = import ./nixpkgs.nix;
  ghc865 = pkgs.haskell.packages.ghc865;
  myPackage = ghc865.callCabal2nix "automation-server" ./. { };
in myPackage

nix-build compiles that package and returns the path to compiled binaries. Easy.

Problem

I wanted to use the haxl library, version at least 2.1.0.0 because of GenHaxl having an incompatible number of arguments with the previous version, so I modified the cabal file with a haxl >= 2.1.0.0 version bound.

I was a bit surprised when it failed with:

Setup: Encountered missing dependencies:
haxl >=2.1.0.0

If I removed the version bound it was able to depend on haxl, but some older version and my code didn’t compile.

Partial solution

I eventually found out that you can override the version of the package in pkgs.

You do it like this:

let
  pkgs = import ./nixpkgs.nix;
  ghc865 = pkgs.haskell.packages.ghc865.extend (self: super: {
    haxl = self.callHackage "haxl" "2.1.2.0" {};
  });
  myPackage = ghc865.callCabal2nix "automation-server" ./. { };
in myPackage

That unfortunately didn’t work right away, I was met with this error:

building '/nix/store/qbbd8m86s90vvfjpw91wki0ihnp9hcdf-all-cabal-hashes-component-haxl-2.1.2.0.drv'...
tar: */haxl/2.1.2.0/haxl.json: Not found in archive
tar: */haxl/2.1.2.0/haxl.cabal: Not found in archive
tar: Exiting with failure status due to previous errors

Full solution

It turns out that for reproducibility not only Haskell packages are pinned in nixpkgs, but also there’s a repository with their hashes called all-cabal-hashes that is also pinned.

So I grabbed the latest git commit hash from all-cabal-hashes, 8c7bdd9ad4bc3671b4214e32766873d443af2622 in my case, and modified the nix file as follows:

let
  pkgs = import ./nixpkgs.nix;

  ghc865 = (pkgs.haskell.packages.ghc865.override {
    all-cabal-hashes = pkgs.fetchurl {
      url = "https://github.com/commercialhaskell/all-cabal-hashes/archive/8c7bdd9ad4bc3671b4214e32766873d443af2622.tar.gz";
      sha256 = "0q9qdpvn3c64rwnafcqkzzyi4z72mvvwmvn06d89fnzfpqjxvwx2";
    };
  }).extend (self: super: {
      haxl = self.callHackage "haxl" "2.1.2.0" {};
    }
  );

  myPackage = ghc865.callCabal2nix "automation-server" ./. { };
in myPackage

That solved my problem.

Wrapping up

You might wonder where I got the sha256 hash for all-cabal-hashes or miso. I don’t know the proper way to do it, but you can get by putting in a wrong hash and letting Nix tell you what the hash should have been. Just don’t use a hash that was used before, because then Nix blindly uses an existing resource with that hash and doesn’t even try to download yours and reverify.

While researching this I didn’t find anything resembling documentation for the Naskell infrastructure in Nix, only random forum posts and github issues.

In the future, I plan to look into the sources a bit more. For a while, I didn’t even know where to look for them, but now that I know where they are and I can parse the Nix language that will be my source of truth. Code never lies.

Additional resources