Nix Haskell Development (2020)

This post was better than any guide or manual I’ve read so far. Wish I could click the heart button more than once! I really hope you make this a wiki article.

2 Likes

The way I pin nix packages I picked up from niv. Last I checked (quite some time ago) there was some issue I had combining it with my Haskell development setup (The problem might have just been my setup). I’ll check it out again. Thanks for mentioning it!

1 Like

I’m so glad it was useful to you!

I’m done for today, but tomorrow I’ll come back and update it with shellFor, niv and do some grammar checking.

Let me know if the updates get too cluttered.

Is the date your talking about the same as what nix-prefetch-git sets or do you mean the date you pinned nixpkgs?

Ah, so the date I’m referring to here is the date you used for the channel.

For instance, if I were to use the tip of the 19.09 channel as of today, I’d put in a comment in my nix code that says:

# This is the release-19.09 branch as of 2020-03-09.

At work, we use a release branch of nixpkgs, and sometimes things like CUDA-support or MacOSX support will stop working in the release branch. It is helpful to have an idea of the date of the release branch, so you can keep track of the various bugs you tend to run into when your project gets more complicated.

Although this is a very small nitpick. It might be easier just to recommend niv and not worry about it!

changing buildInputs to nativeBulidInput doesn’t put ghcid on my path when using overrideAttrs

Ah, sorry, this should be nativeBuildInputs (plural). I’ll correct it my original post.

(edit: I tried to google you an explanation of nativeBuildInputs vs. buildInputs, but I couldn’t find an easy-to-understand explanation. If anyone can help me out here, I’d appreciate it!)

the compiler argument could be used to test which versions of ghc your project works with, so you can annotate your cabal file with them

In this case, you may also have to change the nixpkgs checkout, so you can get a different LTS package set.

For instance, if you wanted to test GHC-8.6.5, you should use 19.09 (since it contains LTS-14, which uses GHC-8.6.5), but if you want to test GHC-8.8.2, you should use the current master (since it contains LTS-15, which is GHC-8.8.2). Now, there are some instances where you don’t have to do this (and you can instead just pass a different compiler like you suggest), but in practice, for many non-trivial projects, you’ll need to use a nixpkgs Haskell package set that is geared to the compiler you want to test.

Although, I’d say that if you end up wanting to test against different GHC’s, I’d recommend just going to something like haskell.nix.

I would still prefer to disable tests, haddocks, profiling on a per dependency basis simply so most things get pulled from the cache.

Yeah, I agree that is the best thing to do!

I’m not sure if I should start linking people to this write-up before or after I take the time to learn how haskell.nix does things differently/better.

haskell.nix has another big learning curve (although they do have some nice documentation), so my suggestion would be to release this write-up as-is, and maybe just point people to haskell.nix at the very end for more complicated projects.

Thank you all again for your help, especially @cdepillabout feedback. I think I’ve gotten this Howto to a point it can be published on the Nix Wiki, though I won’t do so until later tomorrow. Let me know if you spot any typos or other errors before I submit it.

(I’m also delaying to see if I get the motivation to write out and explain a shellFor example)

2 Likes

Great tutorial, thanks; I remember it taking me me a lot of trial and error to figure those out, so I wish we had it sooner!

I found that one disadvantage for this pattern is that it makes starting the new project a lot more time consuming. So I wanted to mention the cookiecutter template I was using which results on a project pretty similar to your example: GitHub - utdemir/hs-nix-template: A Haskell project template that uses Nix and comes with cabal-install, ghcid, ormolu, haskell-language-server and more. , it saves quite a bit of time setting all these up.

4 Likes

This is very nice! I’d like to put it as a tl;dr at the top of the guide. I would also like to make a PR adding some examples on how to use niv (importing and adding new packages to the right place). That way I can have your repository as a complete example of how to start using Nix to develop Haskell projects.

Its a much nicer experience to just call nix-shell -p cookiecutter git --run 'cookiecutter gh:utdemir/hs-nix-template' and call it a day. This tutorial has turned into a description of some aspects of how to use Nix to develop Haskell, not necessarily the best way to set it up.

One thing I would mention, don’t override haskellPackages with the custom package set (I made this mistake for awhile as well). Your better off creating a myHaskellPackages so you can still use the original haskellPackages for build tools. This helps with build times since haskellPackages on a recent nixpkgs checkout is often cached, but a modified version may require you to build them yourself.

2 Likes

I would also like to make a PR adding some examples on how to use niv (importing and adding new packages to the right place).

Of course! I’ll just give you commit access, feel free to decline the invitation it if you don’t prefer it :).

Your better off creating a myHaskellPackages so you can still use the original haskellPackages for build tools.

Oh, this is a good point, thanks! I’ll update the template.

1 Like

Thanks for giving me access! I’ll continue this tread with issues on the repo so we don’t clutter up this discussion.

People reading this thread might be interested by two haskell-nix project structures :

2 Likes

Thanks for this write-up! I am trying to adapt some of what you have done here to my Ubuntu system with the Nix package manager installed. I have used NixOS for other efforts but the paths are all different when you are just using the package manager and installing everything with nix-env -i. For example, it’s unclear to me that I have anything like import ./nix/nixos-19-09.nix. The closest I think I have is a near-empty ~/.config/nixpkgs/config.nix and a manifest.nix that is part of my per-user profile. Actually, what I think I want is for the nix-shell to take on the form of just what is returned by callCabal2nix, with maybe some divergences later. Put another way, I’m trying to get the simplest version of your example working with Haskell, on a system with just the Nix package manager. Oh, I also fish shell, which seems to muck everything up for Nix shell. :slight_smile:

I’m glad you liked it!

I actually use fish as well and I use direnv to bring the development environment into fish instead of invoking nix-shell to start development. If you setup direnv with lorri it will load up your last cached version of the development environment (or just an empty environment the first time) so you don’t have to wait for Nix to start hacking on your project.

However, none of this is necessary to get started. Here is a cut down version, two files, you can copy into a simple Haskell project that should just work. Just name it default.nix and put it in the same directory as your cabal file.

{ compiler ? "ghc865", nixpkgs ? import <nixpkgs> {}}:

let

  # This is from the nixos-20.03 branch 
  pinnedPkgs = nixpkgs.fetchFromGitHub {
    owner = "NixOS";
    repo  = "nixpkgs";
    rev = "182f229ba7cc197768488972347e47044cd793fb";
    sha256 = "0x20m7czzyla2rd9vyn837wyc2hjnpad45idq823ixmdhyifx31s";
  };

  pkgs = import pinnedPkgs {};

  gitignore = pkgs.nix-gitignore.gitignoreSourcePure [ ./.gitignore ];

  myHaskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hself: hsuper: {
      "test" =
        hself.callCabal2nix
          "test"
          (gitignore ./.)
          {};
    };
  };

  shell = myHaskellPackages.shellFor {
    packages = p: [
      p."test"
    ];
    buildInputs = with pkgs.haskellPackages; [
      myHaskellPackages.cabal-install
      hlint
      brittany
      ghcid
    ];
    withHoogle = true;
  };

  exe = pkgs.haskell.lib.justStaticExecutables (myHaskellPackages."test");

in
  {
    inherit shell;
    inherit exe;
  }

Then rename all instances of test with the name of your project from your cabal file (The place where it says name: project-name-here). And create a simple shell.nix:

(import ./default.nix {}).shell

Now you can run nix-shell to get a shell and nix-build --attr exe to build your executable (It ends up in ./result/bin/project-name). Keep in mind either of these commands might take a while since Nix will pull down ghc (ghc 8.6.5 in this case) and all the libraries you are using.

Note: I’ve included ghcid (the by far the best simple tool to develop Haskell with), hlint for code suggestions and brittany for code formatting.

6 Likes

This post took me on a bit of an adventure. I now have Lorri and direnv and home manager on my Ubuntu system. Never used home manager before, but I’m super happy it exists because I never could get used to nix-env -i for everything, after having used NixOS before. Everything is happy now. Thank you. :slight_smile:

My favorite part is my new “impure” tag on my powerline in fish. lol

1 Like

Hi! I’m looking for a way to integrate a new build step into the standard build phase of my cabal-based cabal derivation (calbal2nix). Unfortunately the current Haskell section in the docs doesn’t provide any clues on how to do that. Any ideas how can I approach this?

random guess: have you tried preBuild/postBuild hooks etc ?

would I just concatenate my postBuild with the original postBuild in overrides? I see there’s one particular postBuild step defined for cabal packages in https://github.com/NixOS/nixpkgs/blob/d2b8b928655f1b5e80985e49555aef70818a9bdf/pkgs/development/haskell-modules/lib.nix#L285-L299 but there’s no shortcuts that would allow it as a convenient method.

Did this ever get published (e.g., on the Nix Wiki), or is this the most up-to-date version of this excellent tutorial?

1 Like

This has not been published to the Nix Wiki and I think I should update it for 2021. Its not that anything above wouldn’t work, but that there are easier and more concise ways of doing Haskell development in Nix in practice. I want to both provide a shorted version for those wanting to get started quickly, but also leave the more verbose version.

In short, one of my new years resolutions is to revisit this and add what I have learned over the past year. If this is not done by the end of the month, feel free to ping me.

11 Likes

The current tooling works well if all of your dependencies can be compiled with the versions of packages specified on stackage. But if some of them don’t (for example if the versions are too new), it can go horribly wrong. You’ll need to manually pick specific versions of packages, and override them in the package set, similar to what you’d usually do in stack.yaml. This process is tedious, error-prone, and needs to be done every time nixpkgs updates. This is also what a dependency solver is for. I think we definitely need something that can take advantage of cabal’s solver to help us generate a build plan. Currently only haskell.nix can do this and I think nixpkgs definitely need this as well.

This issue has been addressed in greater detail in my preview post: A summary of the problems I met while using the current nixpkgs Haskell infrastructure (And my thoughts on how to solve them).

1 Like

Thanks, the trick with overriding mkDerivation helped to pass a custom environment variable into project build with callCabal2nix inside overlay, but this solution causes nix to rebuild all dependencies (hours…).

So an alternative is appreciated.

As for me I think callCabal2nix is very limited in customization.
I don’t understand why passing environment variable is not possible.

.overrideAttrs is not working inside overlay.

I need a way to pass git commit hash. Project is fetched from git as an archive and git meta information is erased, but gitlab CI has CI_COMMIT_SHA env variable, but nix cleans env before launching GHC.