Starting a REPL for a haskellPackages.developHaskell derivation?

It’s very convenient to Nixify a Haskell package using pkgs.haskellPackages.developPackage { root = ./.; } (or callCabal2Nix ...), but I couldn’t figure out a good way to start a GHCI (REPL) session for derivations defined like this.

I understand that I can define a Nix shell environment around this derivation and add cabal-install etc. as tools and then I can run cabal repl, but that’s not ideal for what I want to achieve. Unless I’m missing something important, running cabal repl in the resulting shell isn’t very faithful to the derivation. It doesn’t seem to use the same Haskell package versions and it compiles them from scratch.

What I really want is to start a repl precisely at the moment where ./Setup build is called on the package, smoothly inheriting the whole Nix setup. I did manage to get exactly what I want on Linux, by using a flake app like this:

apps.develop-my-package = {
  type = "app";
  program = (pkgs.writeShellScript "develop-my-package" ''
      # Set up an environment where I can access the DB etc.
      DRV=$(nix-store -q --deriver ${my-nixified-package})
      nix-shell $DRV --command ${pkgs.writeShellScript "enter-my-package-repl" ''
        cd my-package-folder
        runPhase setupCompilerEnvironmentPhase
        runPhase compileBuildDriverPhase
        runPhase configurePhase
        ./Setup repl
      ''}
    ''
  ).outPath;
};

I’m really happy with how this works. Anyone can now clone the repo and run nix run .#develop-my-package and without installing anything on their system, they drop down to a REPL session where they have access to various environmental utilities (like DB connections) and they can run :r to reload the shell from their source files etc.

My problem is that

  • I had to spill out the guts of the developPackage output and call the appropriate phases explicitly until I have an environment where I can run ./Setup repl. So I’m worried that this is not a future-proof way of doing it and also that it might not work for other projects where something is slightly different.
  • I just tried it on MacOS and I’m getting some annoying errors (which I will look into next). Another indication of how fragile this approach is.
  • A very minor inconvenience is that running this flake app leaves me with a Setup binary in my project folder, that I need to gitignore etc. Not too important, just a minor friction.

Anyway, while scratching my head over this, I started wondering if others might have figured out a better way to achieve what I want here: A hassle-free way to drop down to a GHCI session for a haskell4nix derivation.

1 Like

Reporting back here that I have streamlined my approach and arrived at the following Nix function that will transform a Haskell derivation to a new derivation that produces the Setup executable for the package so that Setup repl can be run directly in the source folder:

mkHsSetup = drv: drv.overrideAttrs (oldAttrs: {
  buildPhase = "";
  setupCompilerEnvironmentPhase = builtins.replaceStrings
    [ "builddir=\"$(mktemp -d)\"" ]
    [ "mkdir -p $out; builddir=$out" ]
    oldAttrs.setupCompilerEnvironmentPhase;
  doCheck = false;
  nativeBuildInputs = oldAttrs.nativeBuildInputs ++ [ pkgs.makeWrapper ];
  outputs = [ "out" ];
  pname = oldAttrs.pname + "-setup";
  installPhase = ''
    mkdir -p $out/bin
    cp Setup $out/bin/.wrapped-Setup
    cp -r dist $out

    cat > $out/bin/Setup <<EOF
    #!${pkgs.runtimeShell}
    builddir="\$(mktemp -d)"
    cleanup() {
      rm -rf "\$builddir"
    }
    trap cleanup EXIT
    cp -rf $out/dist/* "\$builddir"
    $out/bin/.wrapped-Setup --builddir="\$builddir" "\$@"
    EOF

    chmod +x $out/bin/Setup
  '';
  meta.mainProgram = "Setup";
});

So, we’re disabling the buildPhase, performing the pre-build setup in $out instead of a mktemp folder and exposing the Setup binary in the installPhase while wrapping it up with a script that creates a temporary directory and passes it as the builddir flag. So, now my develop-my-package app looks like this:

apps.develop-my-package = {
  type = "app";
  program = (pkgs.writeShellScript "develop-my-package" ''
      # Set up an environment where I can access the DB etc.
      (cd my-package-folder; ${mkHsSetup my-package}/bin/Setup repl)      
  ).outPath;
};

Or you can also just nix run the result of the mkHsSetup derivation in the project folder.