hint icon indicating copy to clipboard operation
hint copied to clipboard

Documentation - Working with Nix

Open shmish111 opened this issue 7 years ago • 5 comments

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.

shmish111 avatar Nov 09 '18 12:11 shmish111

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 avatar Dec 26 '18 22:12 gelisam

@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 avatar Jan 07 '19 09:01 shmish111

@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.

anka-213 avatar Jul 10 '20 13:07 anka-213

For haskell.nix users: https://github.com/input-output-hk/plutus/blob/fb7ccb60fe58d14fe4f1bfadcdc87a5aaeca3d1d/marlowe-playground-client/default.nix#L13-L22

domenkozar avatar Dec 10 '20 14:12 domenkozar