Documentation - Working with Nix
I spent a couple of days trying to get Hint working on Nix and I've finally managed it with the help of @mchakravarty. Below is what was required, could you document this somewhere obvious, maybe in the README.md?
Using hint with nix
Hint relies on certain GHC paths being set up correctly to function, these are the path to the library of packages and the path to the package database. If you are running hint with stack exec then this works without any modification. This is because stack sets the env var GHC_PACKAGE_PATH as well as GHC libraries being in the expected places. If you run the binary produced by stack build by itself you will need to set GHC_PACKAGE_PATH to the value you can find with stack exec -- env | grep GHC_PACKAGE_PATH.
Unfortunately when you build your project with nix you will find that although your program runs, the hint interpreter can't find the package library files or the package db. There are two things you must do to solve these problems.
Set GHC_PACKAGE_PATH
You will need to build a ghc binary that has all the packages that hint will need when interpreting code:
myGhc = pkgs.haskell.packages.ghc822.ghcWithPackages (ps: [
myPackageOne
myPackageTwo
]);
You will then need to wrap up your binary in a shell script that will set the env var:
my-program-invoker = pkgs.stdenv.mkDerivation {
name = "my-program-invoker";
unpackPhase = "true";
buildInputs = [ myGhc my-program pkgs.makeWrapper ];
buildPhase = ''
# We need to provide the ghc interpreter (hint) with the location of the ghc lib dir and the package db
mkdir $out
ln -s ${my-program}/bin/my-program $out/my-program
# Unfortunately ghc-8.2.2 is hardcoded here so when you want to upgrade GHC you will need to fix this
wrapProgram $out/my-program --set GHC_PACKAGE_PATH "${myGhc}/lib/ghc-8.2.2/package.conf.d"
'';
installPhase = "echo nothing to install";
};
Now hint will be able to find the package database, however it still can't find the library files and you will probably get an error can't find Prelude.
Set library directory
Hint provides unsafeRunInterpreterWithArgsLibdir to do this. You will need to set an env var (or pass in on the cli prompt) with the location of myGhc library directory. In order to allow stack to work as well as nix, I put in a bit of boiler plate:
runInterpreter :: InterpreterT IO a -> IO (Either InterpreterError a)
runInterpreter action = do
mLibDir <- liftIO $ lookupEnv "GHC_LIB_DIR"
case mLibDir of
Just libDir ->
unsafeRunInterpreterWithArgsLibdir [] libDir action
Nothing -> Interpreter.runInterpreter action
I then need to set GHC_LIB_DIR in the nix wrapper script by adding an extra --set flag, the final invoker wrapper will look like this:
my-program-invoker = pkgs.stdenv.mkDerivation {
name = "my-program-invoker";
unpackPhase = "true";
buildInputs = [ myGhc my-program pkgs.makeWrapper ];
buildPhase = ''
# We need to provide the ghc interpreter (hint) with the location of the ghc lib dir and the package db
mkdir $out
ln -s ${my-program}/bin/my-program $out/my-program
# Unfortunately ghc-8.2.2 is hardcoded here so when you want to upgrade GHC you will need to fix this
wrapProgram $out/my-program --set GHC_LIB_DIR "${myGhc}/lib/ghc-8.2.2" --set GHC_PACKAGE_PATH "${myGhc}/lib/ghc-8.2.2/package.conf.d"
'';
installPhase = "echo nothing to install";
};
After doing this, nix will build a GHC with all the correct libraries and a wrapper script which you can then use to run your program and hint should work correctly.
You will need to build a ghc binary that has all the packages
That sounds crazy heavyweight, is this commonly done in the nix world? GHC_PACKAGE_PATH is a colon-separated list of paths, wouldn't it be simpler to compile your dependencies to their own package database and to add the path of that package database to GHC_PACKAGE_PATH, instead of adding them to ghc's package database?
@gelisam when I said a "ghc binary" what I really meant was a ghc environment, basically ghc and a package database with everything in the correct place etc, most things are symlinks to the nix store anyway.
@shmish111 We can fix the hard-coded ghc version by extracting it from the myGhc derivation:
my-program-invoker = pkgs.stdenv.mkDerivation {
name = "my-program-invoker";
unpackPhase = "true";
buildInputs = [ myGhc my-program pkgs.makeWrapper ];
buildPhase = ''
# We need to provide the ghc interpreter (hint) with the location of the ghc lib dir and the package db
mkdir -p $out/bin
ln -s ${my-program}/bin/my-program $out/bin/my-program
# Fortunately ${myGhc.meta.name} is no longer hardcoded here so you don't need to fix anything to upgrade GHC
wrapProgram $out/bin/my-program --set GHC_PACKAGE_PATH "${myGhc}/lib/${myGhc.meta.name}/package.conf.d"
'';
installPhase = "echo nothing to install";
};
I also added bin/ to the path of the wrapper, so it will be included in PATH properly.
For haskell.nix users: https://github.com/input-output-hk/plutus/blob/fb7ccb60fe58d14fe4f1bfadcdc87a5aaeca3d1d/marlowe-playground-client/default.nix#L13-L22