Using Haskell with devenv.sh

Hi :wave:

Here’s getting started with Haskell using devenv.sh:

  • Pick from a wide range of GHC versions.
  • Get stack/cabal as part of the dev environment.
  • Get Haskell Language Server for the chosen GHC.
  • Enable any linters/formatters as part of the hooks.
  • mac/arm, mac/intel, linux/arm, linux/intel.

$ devenv shell

I’m wondering how we can make the experience better?

I wonder if anyone in the Haskell community would benefit if we created a repository that would allow picking any minor version of GHC ever released in the style of nixpkgs-python?

Most of our code is written in Haskell, so I’d love to support it as a first-class citizen.

Domen

18 Likes

When I consider using this and only read over the start page devenv.sh, my immediate questions & concerns are:

  • I don’t know how/whether this integrates with my existing NixOS configuration, which already includes a Haskell setup
  • I don’t know how/whether I can install an editor (let’s say VsCodium) with the Haskell Language Server extension such that it will work with exactly that GHC version that I declare in my devenv. Should I declare the editor as part of the devenv? Or do I install it separately? If the former, I don’t know whether I’ll be able to start my editor globally (some kind of start menu or launcher) or I need to start it from that shell.

So at that part I’m slightly discouraged. But knowing that it’s done by you, and you usually create fantastic things, I went on skimming through the documentation to try and figure out these questions. But after having looked through the documentation, I could not answer these questions myself.

3 Likes

How is this different from using a flake shell, or arion? Does it do caching of haskell libraries as well?

2 Likes
  1. The idea behind developer environments belonging to the project is that the tooling (except for the editor) all comes from project specific settings.

The main objective is that noone needs to install any dependencies according to their local setup,
but it’s done uniformly for anyone hacking on the project.

For example in the case of Haskell, setting up HLS can be painful with the right GHC version since it needs to match.

  1. For your editor to pick up the environment and thus HLS, it’s best to install direnv extension. See direnv - Visual Studio Marketplace
1 Like

It integrates into flakes. Think of it as NixOS for your project.

Arion allows you to use Nix to declare containers, while devenv allows you to develop in your shell, but also generate containers via devenv container shell or devenv container processes to launch all defined processes.

1 Like

The question about how or whether to integrate devenv.sh into my existing flake.nix was the blocker I had, as well. Perhaps that needs to be highlighted in the docs somehow.

In a Flake + Haskell context, it sounds like the pitch is that devenv.lib.mkShell is a supercharged replacement for haskellPackages.shellFor.

2 Likes

Good point, we have quite a bunch of documentation now.

It would be good if we expose that devenv has two ways of working: standalone or flakes.

Hey @domenkozar, that’s great stuff :partying_face:

I’m feeling ready to start migrating my flakes to devenv. I find myself often copy pasting my flakes into github projects I download and modifying them to fit the project environment (even contributing them back sometimes, right @turion :smiley: )

It would be great to have stack and cabal examples as well as migration steps from cabal2nix or haskell4nix.

As I’m going through my first attempt to use devenv right now on this project, maybe it’s helpful if I go through this and report my thoughts and difficulties?

The project has a very simple stack.yaml and I bumped into a difficult error (I know you’re fundraising for improving errors in nix, and I wish that effort the very best). I just ran devenv init in the folder and added languages.haskell.enable = true; and when I ran stack build I got this error:

error:
       … while calling the 'derivationStrict' builtin

         at //builtin/derivation.nix:9:12: (source not available)

       … while evaluating derivation 'myEnv'
         whose name attribute is located at /nix/store/dbfxzxrxf82zxql89ywnprg0dbbmxy69-nixos-20.09.3301.42809feaa9f/nixos/pkgs/build-support/trivial-builders.nix:7:7

       … while evaluating attribute 'LD_LIBRARY_PATH' of derivation 'myEnv'

         at «string»:1:411:

            1| with (import <nixpkgs> {}); let inputs = [haskell.compiler.ghc925 git gcc gmp]; libPath = lib.makeLibraryPath inputs; stackExtraArgs = lib.concatMap (pkg: [ ''--extra-lib-dirs=${lib.getLib pkg}/lib''   ''--extra-include-dirs=${lib.getDev pkg}/include'' ]) inputs; in runCommand ''myEnv'' { buildInputs = lib.optional stdenv.isLinux glibcLocales ++ inputs; STACK_PLATFORM_VARIANT=''nix''; STACK_IN_NIX_SHELL=1; LD_LIBRARY_PATH = libPath;STACK_IN_NIX_EXTRA_ARGS = stackExtraArgs; LANG="en_US.UTF-8";} ""
             |                                                                                                                                                                                                                                                                                                                                                                                                                           ^

       error: attribute 'ghc925' missing

       at «string»:1:43:

            1| with (import <nixpkgs> {}); let inputs = [haskell.compiler.ghc925 git gcc gmp]; libPath = lib.makeLibraryPath inputs; stackExtraArgs = lib.concatMap (pkg: [ ''--extra-lib-dirs=${lib.getLib pkg}/lib''   ''--extra-include-dirs=${lib.getDev pkg}/include'' ]) inputs; in runCommand ''myEnv'' { buildInputs = lib.optional stdenv.isLinux glibcLocales ++ inputs; STACK_PLATFORM_VARIANT=''nix''; STACK_IN_NIX_SHELL=1; LD_LIBRARY_PATH = libPath;STACK_IN_NIX_EXTRA_ARGS = stackExtraArgs; LANG="en_US.UTF-8";} ""
             |                                           ^
       Did you mean ghc865?

A thought: the fact that the devenv dev shell is --impure worries me a bit, as I don’t know if something from my system environment is interfering here. That’s a worry I don’t usually have with nix develop and that’s a pretty big one. Also I installed devenv on my nixos machine via home manager (passing the flake through to home.packages, which wasn’t completely obvious) so I’m also wondering now if I messed that up.

I tried to add languages.haskell.package = pkgs.haskell.compiler.ghc925; not really believing it would do anything, and indeed it didn’t. So I find myself having to dropdown into nix to troubleshoot.

My assumption reading the last message in the stack trace is that the haskell.compiler.ghc925 could be coming from devenv itself, or from stack trying to do something smart with its nix integration. That’s the point where I tried to go the the devenv examples but came up empty.

Ah ha, I feel lucky. I got to this issue in a search and two clicks :partying_face:

A thought: I haven’t been immersed in nix/haskell dev in a few months, so I have a lot of context to reload here. Also I tended to migrate away from stack and towards cabal, so I’m having flashbacks of how actually quite difficult haskell dev setup is.

So now I think I should try to disable nix integration in stack.

nix:
  enable: false

Yeah not quite:

> stack build
I don't know how to install GHC on your system configuration, please install manually
x> ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.2.5
> which ghc
/nix/store/fyl847yw1p7n3nll9jbcj1cg4y09wdxl-devenv-profile/bin/ghc

I feel tempted to change my nix channels given the github issue, but that would be too much of a defeat. A bit of jogging my memory and I’m thinking I should be able to pass the GHC path to stack. A bit of googling later, I’m realising there’s system-ghc: true.

Victory!

> stack exec -- which ghc
/nix/store/lnxk3fmylchldhcs6hpbwx8bp9jjcp6w-ghc-9.2.5/bin/ghc
> stack build
[1 of 2] Compiling Main             ( /home/jun/.stack/setup-exe-src/setup-Z6RU0evB.hs, /home/jun/.stack/setup-exe-src/setup-Z6RU0evB.o )
[2 of 2] Compiling StackSetupShim   ( /home/jun/.stack/setup-exe-src/setup-shim-Z6RU0evB.hs, /home/jun/.stack/setup-exe-src/setup-shim-Z6RU0evB.o )
Linking /home/jun/.stack/setup-exe-cache/x86_64-linux/tmp-Cabal-simple_Z6RU0evB_3.6.3.0_ghc-9.2.5 ...

Hope my developer experience was a little bit useful, I’m not sure if I landed on the “correct devenv way” but at least I can build the project.

TLDR; For me, adding this to the project’s stack.yaml did it:

nix:
  enable: false
system-ghc: true

A parting thought: I would have preferred if devenv init picked up the fact that it’s a haskell project, and also didn’t create a devenv.yaml file (since it’s using the default input flake anyway) given that project root folder get quite bloated these days.

Cheers,

Jun

2 Likes

Also, things are now taking a long time to build and I’m wondering. How does one setup cachix or nix binary caches in a devenv project?

  • a search for binary cache, cache or cachix comes out empty in the devenv doc search Getting Started - devenv
  • devenv cachix on google is a bit of a pointless one unfortunately :sweat:

I faintly remember there’s a public cache from iohk and I think nixpkgs might also have some haskell packages in cache but only for the latest versions? Not sure about all this anymore as in other professional contexts I’ve used a org level cachix cache.

Might be nice to have some recipes for this in the docs as well :pray:

UPDATE: A yes I’m in good company asking myself this A common public nix cache? - NixOS Discourse

1 Like

I can try to answer some of your questions, however I’m not familiar with devenv.sh, so I’ll try to just give you high level answers, and wait for Domen to answer with specifics.

If you run stack --nix build, stack will internally re-exec itself inside a nix-shell. It looks something like this:

$ stack --nix build --verbose
2.9.1 x86_64 hpack-0.35.0...
2023-07-12 12:49:52.655673: [debug] Using a nix-shell environment with nix packages: zlib, haskell.compiler.ghc8107, git, gcc, gmp
2023-07-12 12:49:52.655888: [debug] Run process: /run/current-system/sw/bin/nix-shell -E "with (import <nixpkgs> {}); let inputs = [zlib haskell.compiler.ghc8107 git gcc gmp]; libPath = lib.makeLibraryPath inputs; stackExtraArgs = lib.concatMap (pkg: [ ''--extra-lib-dirs=${lib.getLib pkg}/lib''   ''--extra-include-dirs=${lib.getDev pkg}/include'' ]) inputs; in runCommand ''myEnv'' { buildInputs = lib.optional stdenv.isLinux glibcLocales ++ inputs; STACK_PLATFORM_VARIANT=''nix''; STACK_IN_NIX_SHELL=1; LD_LIBRARY_PATH = libPath;STACK_IN_NIX_EXTRA_ARGS = stackExtraArgs; LANG=\"en_US.UTF-8\";} \"\"" --run "'/nix/store/a8qshjkhkk5gwgxlzc1fjcm0yzrkkqz7-stack-2.9.1/bin/stack' $STACK_IN_NIX_EXTRA_ARGS '--internal-re-exec-version=2.9.1' 'build' '--verbose'"
...

If you look at the Run process: line closely, you can see that stack specifically tries to pass a given version of GHC to the nix-shell environment.

In your case, you are likely using a Stackage resolver that uses GHC-9.2.5, so stack is trying to helpfully pass ghc925 to the nix-shell environment. The error you’re seeing makes me think you’re trying to use a Nixpkgs checkout that doesn’t yet contain ghc925.

I’m not sure exactly what your setup looks like, but you likely need to either update your system with a more recent version of Nixpkgs (which will hopefully contain ghc925), or update devenv.sh to use a newer version of Nixpkgs. It depends on where exactly nix-shell is trying to get ghc925 from.

This is also a reasonable approach, and it looks like you’ve gotten it working. As you found out, you can just completely disable Nix support in stack, pull in GHC with devenv.sh (or nix-shell, or nix develop), and tell stack to use the GHC it finds on PATH.

A couple things here:

  • If you’re using stack for building your Haskell project, then stack will build all your Haskell dependencies. Nix-based caches generally won’t help you out here.
  • If you decide to build your Haskell dependencies with Nix, and do development with cabal, then you can start to benefit from Nix-based caches. Depending on which Nixpkgs commit and GHC version you are using, you can likely get most(?) of your Haskell dependencies from the main NixOS cache. A lot of people try to setup their projects so they get cache hits in this way, if possible.
  • If your project needs a certain compiler version and you can’t pull Haskell dependencies from the main NixOS cache, then cachix is a popular choice in the NixOS community (as it seems you’re aware!).
1 Like

Thank you very much @cdepillabout your insights about caching in particular are very useful and a great reason to switch to cabal!

devenv uses this input flake by default github:NixOS/nixpkgs/nixpkgs-unstable which contains ghc925 https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/top-level/release-haskell.nix#L67

stack is trying to helpfully pass ghc925 to the nix-shell environment

Unfortunately that’s probably where assumptions break down, I’m not sure how, but maybe @domenkozar will know?

1 Like