Run hpack before entering nix-shell

I need to use cabal v2-repl to load my project into ghci, but a cabal file is required for this. My project formerly used stack and only has a package.yaml, please assume that replacing package.yaml with a cabal file is not an option.

That means I need to run hpack before entering a nix-shell. Here is a derivation that attempts but fails to run hpack before entering nix shell but otherwise works. I’m not sure why preConfigure seemingly has no effect:

Note: You can also view this default.nix in the context of the project here.

{ compiler ? "ghc883" ,
  pkgs ? import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/3567e1f6cc204f3b999431ce9e182a86e115976f.tar.gz") {}}:
let
  pinnedHaskell = pkgs.haskell.packages.${compiler};
  haskell-nix-minimal = pinnedHaskell.developPackage
    { root = ./.;
      overrides = with pkgs.haskell.lib; self: super: {
        bson = dontCheck (addBuildDepend (markUnbroken super.bson) self.network-bsd);
        mongoDB = dontCheck (markUnbroken (self.callHackage "mongoDB" "2.7.0.0" {}));
      };
      source-overrides =
        {
          orville = pkgs.fetchFromGitHub {
            owner = "EdutainmentLive";
            repo = "orville";
            rev = "8a2432891e96a547777b67a67135e235a1dce80c";
            sha256 = "0i8z3zr4z55bzidlh3pz3r3h0hachk5ndfhznw3kqk4c5j6y27ry";
          };
        };
      # This to run when I call nix-shell
      # My project only has a package.yaml and no cabal file
      # I need to use cabal v2-repl though so I need to generate it each
      # time I do:
      # nix-shell --pure --run "cabal v2-repl"
      # I think preConfigure hook is correct but this doesn't work
      modifier = drv:
        with pkgs.haskellPackages;
        pkgs.haskell.lib.overrideCabal drv (attrs: {
          buildTools = [ hlint cabal-install hpack ];
          # NOTE This doesn't work, I'd like it to generate the cabal file required by cabal-v2 repl
          # preConfigure = ''
          # ${pinnedHaskell.hpack}/bin/hpack
          # '';
        });
    };
in haskell-nix-minimal

  # NOTE: this example generates an hpack file on demand like I might need
  # https://github.com/luc-tielen/typesystem/blob/3b61395b40630c300ffd499efa71166f450579a5/default.nix#L27
  # snippets from above link:
  # hpack2cabal = name: src: pkgs.runCommand "hpack2cabal-${name}" {} ''
  #   ${hpack}/bin/hpack '${src}' - > "$out"
  # '';
  # source = nix-gitignore.gitignoreSource [] ./.;
  # processedSource = hpack2cabal "typesystem" source;

I usually have a cabal wrapper script for my project that runs cabal in a nix-shell for me and creates the necessary GC root to keep the shell inputs in the store. You could do this and add a step that runs hpack first.

#!/usr/bin/env bash

DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )

# NOTE: This only works as a GC root if you enable the `keep-outputs` Nix option.
# Otherwise you'll need something more complicated.
drv=$(nix-instantiate --add-root $DIR/dist-newstyle/gc-root --indirect $DIR/default.nix -A shell)
nix-shell $drv --run "(cd $DIR && hpack) && cabal $(printf "%q " "$@")"

As for why your preConfigure thing didn’t work: That code would be run in the build sandbox if you were to nix-build your project. When you nix-shell, it runs none of the build phases; it just drops you in a shell with the right environment variables set.

1 Like

Somewhat related, recent advice around hpack recommends storing the .cabal in your repo, even though it is technically a generated file:

1 Like

Thanks, that makes me more dubious of the worth of keeping hpack and interoperating with it.

I guess you could patch out the non-deterministic allowing pieces but I’ll only do that if moving to cabal file only isn’t an option

Luckily pinning nixpkgs handles ensuring a constant hpack version though.

1 Like

Alright, it turns out I actually do need this and my preConfigure hook doesn’t seem to generate the cabal file (link to default.nix in context of the repo) and here is the code directly:

{ compilerVersion ? "ghc883" ,
  pkgs ?
  import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs-channels/archive/nixos-20.03.tar.gz";
    sha256 = "1gqv2m7plkladd3va664xyqb962pqs4pizzibvkm1nh0f4rfpxvy";
  }) {}}:
let
  compiler = pkgs.haskell.packages."${compilerVersion}";
  pkg = compiler.developPackage
    { root = ./.;
      # overrides = self: super: {};
      # source-overrides = {};
      modifier = drv:
        pkgs.haskell.lib.overrideCabal drv (old: {
          buildDepends = [ pkgs.cabal-install pkgs.haskellPackages.hpack ];
          preConfigure = builtins.concatStringsSep "\n" [
            (old.preConfigure or "")
            "hpack"
          ];
        });
    };
in pkg

I feel silly… I just wanted shellHook :slight_smile:

{ compilerVersion ? "ghc883" ,
  pkgs ?
  import (builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs-channels/archive/nixos-20.03.tar.gz";
    sha256 = "1gqv2m7plkladd3va664xyqb962pqs4pizzibvkm1nh0f4rfpxvy";
  }) {}}:
let
  compiler = pkgs.haskell.packages."${compilerVersion}";
  pkg = compiler.developPackage
    { root = ./.;
      # overrides = self: super: {};
      # source-overrides = {};
      modifier = drv:
        pkgs.haskell.lib.overrideCabal drv (old: {
          buildDepends = [ pkgs.cabal-install pkgs.haskellPackages.hpack ];
          shellHook = "hpack";
        });
    };
in pkg