Nix Haskell Development (2020)

niv is really handy for automating such things. It manages a JSON file with dependencies and provides a nice Nix wrapper. It has a command-line tool to quickly add/drop/update dependencies.

2 Likes

Nice reply! I’ve learned more from your post than hours of reading nix projects and googling. Each number is a reply to the corresponding number from your comment.

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

  2. I did not know about shellFor, much cleaner. Not sure if I want to show both approaches or not. (I have not added this in yet). Note, changing buildInputs to nativeBulidInput doesn’t put ghcid on my path when using overrideAttrs. I will try with shellFor later.

  3. I was thinking 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. I bet that use case is better served with separate nix files for testing. (I have not make this change yet)

  4. Much better idea

  5. Learned about this from you, very helpful.

  6. I would still prefer to disable tests, haddocks, profiling on a per dependency basis simply so most things get pulled from the cache. Still, that is a nice tip when working on several distinct packages where there is no cached version.

  7. haskell.nix is on my todo list to try. 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.

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