When Haskell Dependencies Fail

It’s generally best to use the default GHC version in nixpkgs, not a version of your choice. This is because the haskell package set is roughly based on Stackage, which requires specific GHC versions. So pkgs.haskellPackages is generally better than pkgs.haskell.packages.ghcXYZ unless you know you need the new version and are willing to wade through dependency bounds issues.

That said, many of the issues that crop up when using other GHC versions can be solved by extending the haskell package set and modifying packages or replacing them outright. Here’s how my typical haskell project is set up:

{ pkgs ? import <nixpkgs> {} }:

# packageSourceOverrides is a convenience function
# making it easier to update dependencies to new Hackage versions
# or custom source tree.
(haskellPackages.extend (haskell.lib.packageSourceOverrides {
  # Replace the pinned lens version with 4.17 from Hackage.
  lens = "4.17";

  # Add / replace foo with a git checkout.
  # cabal2nix will be called automatically for you
  foo = builtins.fetchGit {
    url = https://github.com/Foo/foo.git;
    rev = "...";
  };

  # builtins.fetchGit ./. is an easy way to get a clean tree
  # from your project. It will even included uncommitted changes,
  # but only if they're in tracked files so build dirs like ./dist
  # aren't sucked into the nix store unnecessarily.
  my-local-pkg = builtins.fetchGit ./.;
})).extend (self: super: {
  # We can extend again to add manual overrides
  # that can't be done with packageSourceOverrides

  # doJailbreak is a very common one. This line replaces bar with
  # a modified version that strips the package of its dependency
  # bounds, allowing us to use it with newer packages than it
  # anticipated.
  bar = haskell.lib.doJailbreak super.bar;
})

To build my package, I can do nix-build -A my-local-pkg, and to enter a nix-shell for incremental building, I can either do nix-shell -A my-local-pkg.env or add this shell.nix file:

(import ./. {}).my-local-pkg.env

If you’re working on multiple local cabal packages, you can get incrementalism with a cabal.project file and by replacing env with shellFor

# cabal.project
packages:
  ./foo
  ./bar
# shell.nix
(import ./. {}).shellFor (p: [p.my-local-pkg1 p.my-local-pkg2])

Now nix-shell --run 'cabal new-build all' can build both packages incrementally.

5 Likes