Jerry-rigging GHCup on NixOS?

I’m sort of attached to the GHCup workflow, especially if I go to NixOS, simply because Haskell is niche, Nix / NixOS is niche, and crossing two niche technologies together gets me easily into the space where I have no community support at all (incidentally, IHP doesn’t run trivially on NixOS, despite being married to Nix).

From what I’ve heard, maerwald isn’t planning on supporting NixOS with GHCup at the moment, especially given NixOS’s hostility to outside toolchains (pip doesn’t seem to work either), but does anyone have experience in forcing NixOS to support GHCup? I’m now running an entertaining triboot system of NixOS - Arch - Win10 (with Debian WSL2 underneath to boot), so it’s not such a big deal, but it’d be fun to have access to the GHCup-based workflow on NixOS.

3 Likes

If you took all the nixpills (including flakes), I would argue it’s actually much easier to setup Haskell tooling consistently and reproducibly using Nixpkgs. NixOS is not necessary relevant in this context. I use NixOS mostly on laptops, and I have an old server using Fedora+nixpkgs.

I still recommend people using ghcup unless I sense they might be interested in the pills too.

1 Like

But I do see that it might be difficult to quickly get started because there is a lack of single way of doing it, and hence hard to get started if one’s Nix Fu is not there yet.

Is anyone aware of a good writeup on this subject already? I might be able to provide one otherwise.

Yeah, perhaps I’m just too attached to maerwald’s work, because Nix is relatively rare and NixOps is actually a specific skill that can lead to lucrative positions.

It’s probably why I’m attached to Windows Haskell, even though Haskell on Windows is mediocre at best, since Windows is ubiquitous and being able to get Windows Haskell to work and be productive helps in pushing for acceptance of Haskell.


In my case, I love NixOS (it’s literally the most newbie friendly Linux provided you aren’t afraid of screwing around with .nix config files), I love Haskell, I love GHCup. I’m typing this on an Arch distro just to keep my GHCup, but I’ll probably figure out how to jerry-rig GHCup to sort of work.

2 Likes

Maybe you can outline what workflow you want to use? The fact of reliably installing binary distributions on nixOS might be a bit tricky.

Maybe the workflow you are looking for actually exists already?

Note that dealing with native dependencies on nixOS is quite different to how other more stateful/mutable distributions work.

1 Like

I’ve also been thinking about this. The workflow I would want is just the exact same as GHCup on other platforms: always downloading binaries (unless explicitly running ghcup compile ...) and having GHC (and HLS, cabal, stack) versions decoupled from nixpkgs.

I’ve briefly discussed this with @bgamari what it would take to fully support GHCup on NixOS and we came to the conclusion that it would not be an easy thing to do. Ben suggested that perhaps GitHub - mpickering/ghc-artefact-nix: Create environments with GHC HEAD artefacts could be a way to tackle it.

Currently, I mostly use a command like this to get my GHC and HLS:

nix-shell -p haskell.compiler.ghc945 -p haskell.packages.ghc945.haskell-language-server

It is decent. Sometimes to get the very latest GHC versions I have to use the unstable channel like this:

nix-shell -I nixpkgs=channel:nixos-unstable -p haskell.compiler.ghc962 -p haskell.packages.ghc962.haskell-language-server

Unfortunately, for some GHC versions (even on the stable channel) it compiles HLS from source which takes a very long time.

3 Likes

This indeed can be a pain point.

I have thought having a pinned nixpkgs branch maintained by someone, where its caching can be ensured, so that no one would need to recompile them for recent versions of two latest major release GHCs. But perhaps the cost can be an issue for caching things for the community.

We have built something somewhat related with our devx shell. It used haskell.nix to get the compilers, cabal, hls (and some other tools); and is cached on cache.zw3rk.com.

You can use them with

nix develop github:input-output-hk/devx#ghc8107 --no-write-lock-file --refresh

which provide a shell with ghc, cabal, hls, hlint, …

There is also a guide on how to use it with direnv, and make it work with VSCode, emacs, and vim.

There is also a note on getting DevContainer/CodeSpaces to work.

We’ve also got a corresponding speed-optimised GitHub Action, which we use in ci to test the various combinations we have to build the hello package.

As this builds on the logic in haskell.nix, we can do (limited) cross compilation (e.g. to windows), and static targets. However I will note that there may still be residual bugs.

Maybe this is helpful to someone.

8 Likes

That devx shell works great. A few observations:

  • I was able to install GHC 9.8.1 with HLS support using only binaries in less than 2 minutes or so. I haven’t been able to do that yet using any other approach.

  • I had to add some lines to my configuration.nix:

      nix.settings.trusted-substituters = [ 
        "https://cache.iog.io"
        "https://cache.zw3rk.com"
      ];
      nix.settings.trusted-public-keys = [
        "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ="
        "loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk="
      ];
      nix.settings.experimental-features = [ "nix-command" "flakes" ];
    

    (The devx documentation says you can alternatively add yourself to the list of trusted users and enable some setting which trusts flakes, but that involves a bit too much trust for my taste.)

  • It seems to download a whole new OS including packages like git, gcc, and even systemd. That seems a bit overkill.

  • It also installs an isolated cabal and I needed to do cabal update first.

  • I don’t know how to add additional system dependencies. For example what if I want to use sbv and need Z3 installed? Or perhaps more interesting, what if there really is a C dependency like gi-gtk?

  • The cabal version seems to be 3.10.1.0 currently, while I had hoped to get 3.10.2.0 (or maybe it doesn’t include cabal and that is just the version I already had installed?).

  • What I really want is to install a few common versions of GHC and corresponding HLS globally such that I can use with-compiler: ... in cabal.project files to have cabal automatically pick up the right compiler version without me having to bother with direnv for most packages.

2 Likes

I’m guessing it’s actually the cli interface?

I believe you could somehow build an entirely different installation logic behind the same cli interface.

It will lose some properties like simplicity of where files are placed, maybe also different space requirements. But you’ll be able to get a different configuration experience.

2 Likes

I’d prefer the word “incompatibility” to “hostility” — toolchains are used in Nixpkgs wherever they don’t destroy the value Nix brings.

That said, Nix and NixOs are definitely hard to figure out and aren’t compatible with every workflow.

What I’ve recently learned is embrace flakes — if you’re gonna have a bad time, you might as well have the best bad time available. I’ve gotten as far as creating a template with all the best practices I know for packaging Cabal packages on Nix. Now I run

nix flake init -t github:chreekat/nix-templates#haskell-generic

whenever I want to instantly get going. Using a different compiler is as easy as changing which branch of NixOS I pull from, assuming I’m within a reasonable range for production use. Once the flake.nix is initialized, it’s a one line change.

If the compiler I want isn’t on a NixOS release, then this doesn’t work. Working on the bleeding edge is one of the use cases I don’t use NixOS for, nor indeed any distro. But I do still use nix-shell for the dev environment when skating on the bleeding edge.

3 Likes

For me it is much more about HLS and whether or not it is cached. Building it from source cost me more than half an hour if I remember it right, so I’d really like to avoid that. And that is much more common, for example the HLS for GHC 9.6.3 is not in the (nixpkgs/hydra) cache let alone the one for GHC 9.8.1.

1 Like

@jaror we are trying to address this. I hope to have some good answers to the points you raised. The biggest issue with haskell.nix (and thus devx) we run into is that adding new compilers and support all the targets we do, is often a lot of work. Just adding 9.4.8 has been quite some work :-/

I think for most items we can probably try to make them easier. For globally installed GHCs though I don’t see this working easily. That’s something that GHCup is much better at.

Adding additional system dependencies I think should work by entering another sub shell. After all it’s all cabal + ghc really so as soon as they are available in the environment it should work. We haven’t found a good way to augment flake shells yet.

1 Like

I should clarify that the devx/haskell.nix cache does work great. I was talking about the normal nixpkgs/hydra cache.

2 Likes

I have now tried this for the gi-gtk package, but there seemed to be issues with mismatched glibc versions. I got errors like this:

Preprocessing library for haskell-gi-base-0.26.4..
running dist/build/Data/GI/Base/BasicConversions_hsc_make failed (exit code 1)
rsp file was: "dist/build/Data/GI/Base/hsc2hscall37022-3.rsp"
output file:"dist/build/Data/GI/Base/BasicConversions.hs"
command was: dist/build/Data/GI/Base/BasicConversions_hsc_make  >dist/build/Data/GI/Base/BasicConversions.hs
error: dist/build/Data/GI/Base/BasicConversions_hsc_make: /nix/store/aw2fw9ag10wr9pf0qk4nk5sxi0q0bn56-glibc-2.37-8/lib/libc.so.6: version `GLIBC_2.38' not found (required by /nix/store/2if9iy5cy0bicwafllpa2aiq30v26app-glib-2.78.1/lib/libglib-2.0.so.0)

And after adding glibc manually to the list of dependencies I still got this error:

Preprocessing library for haskell-gi-base-0.26.4..
running dist/build/Data/GI/Base/BasicConversions_hsc_make failed (exit code 127)
rsp file was: "dist/build/Data/GI/Base/hsc2hscall46493-3.rsp"
output file:"dist/build/Data/GI/Base/BasicConversions.hs"
command was: dist/build/Data/GI/Base/BasicConversions_hsc_make  >dist/build/Data/GI/Base/BasicConversions.hs
error: dist/build/Data/GI/Base/BasicConversions_hsc_make: error while loading shared libraries: __vdso_gettimeofday: invalid mode for dlopen(): Invalid argument

I do not encounter this issue when using haskell.compiler.ghc963 from a normal nix shell. This is the complete shell that seems to work:

nix-shell -p haskell.compiler.ghc963 pango pkg-config gobject-introspection gtk4 atk pcre2 xorg.libXdmcp util-linux libselinux.dev libsepol.dev pcre fribidi.dev libthai.dev libdatrie.dev

Shot in the dark, but whenever I have C / system libs problems, my first thought is LD_LIBRARY_PATH:

pkgs.mkShell {
  buildInputs = deps;

  -- glibc
  LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib/:$LD_LIBRARY_PATH";

  -- or for adding other deps
  -- LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath deps}:$LD_LIBRARY_PATH";
};

Is this still related to the topic?

I think the main question of this topic is “How can we get a GHCup(-like) workflow on NixOS?”

For me that includes:

  • Quick access to new releases, e.g. 9.8 now.
  • Use normal cabal workflow including the ability to use C dependencies

One of the suggestions was to use the devx shell, but I’m encountered some ways in which it doesn’t quite behave as well as GHCup would on other platforms.

I guess @Liamzy never answered this question and I did kind of take over the discussion from there.

@Liamzy if you want to keep this thread strictly on the topic of making GHCup work on NixOS then I can split out my part of the discussion to another thread.