MatrixAI / Haskell-Demo

Haskell Demo Project using Nix

Home Page:https://matrix.ai

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Automatic stack nix integration without the `--nix` flag

CMCDragonkai opened this issue · comments

The usage of the --nix flag is a bad idea because it only works at the shell and is not inherited by anything in the shell that utilises stack.

At the same time we don't want to have a config parameter that enables nix because that means the stack.yaml requires to be changed when working on a non-nix environment.

I'm tracking an issue about automatic nix integration: commercialhaskell/stack#3980

But it's not working even at 1.7.1 and the PR implies that it only works when on NixOS, not that when inside a nix environment.

An alternative way is use makeWrapper and buildEnv in nix to wrap the stack command such that it always uses stack --nix.

{
  pkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/8b1cf100cd8badad6e1b6d4650b904b88aa870db.tar.gz) {}
}:
with pkgs;
let
  stack-nix = buildEnv {
    name = "stack-nix";
    paths = [ stack ];
    pathsToLink = [ "/" "/bin" ];
    buildInputs = [ makeWrapper ];
    postBuild = ''
      wrapProgram $out/bin/stack --add-flags '--nix'
    '';
    version = stack.version;
  };
in
  haskell.lib.buildStackProject {
    name = "stack-test";
    buildInputs = [ stack-nix ];
  }

It ends up working, but only because the buildInputs includes stack-nix ahead of its own stack. So it's stack command takes precedence in the shell's PATH environment variable. Ideally we would override the stack argument used in buildStackProject. However it cannot be done with buildEnv, it would have to be a the original derivation with its build script overridden or added an additional stage that wraps the stack command.

(haskell.lib.buildStackProject.override { stack = stack-nix; }) {
  name = "stack-test";
}

Attempting this variation just to see if it can work:

{
  pkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/be1461fc0ab29c45c69e7b2c2097c887750e4fe0.tar.gz) {}
}:
  with pkgs;
  let
    stack-nix = stack.overrideAttrs (oldAttrs: {
      buildInputs = oldAttrs.buildInputs ++ [ makeWrapper ];
      postPhases = (pkgs.lib.attrByPath [ "postPhases" ] [] oldAttrs) ++ [ "stackNixWrapPhase" ];
      stackNixWrapPhase = ''
        wrapProgram $out/bin/stack --add-flags '--nix'
      '';
    });
    # stack-nix = stack;
  in
    (haskell.lib.buildStackProject.override { stack = stack-nix; }) {
      name = "stack-test";
      buildInputs = [];
    }

If it does, there's still a downside, it requires recompilation of stack. Which is quite expensive compared to just using buildEnv and just ensuring that stack-nix comes ahead of the original stack.

The above build completes, and simple stack --version and stack init works, but then stack ghci and stack build freezes. So I'm guessing the buildEnv is just the superior solution.

Actually waiting for a while results in this report:

/nix/store/8zkg9ac4s4alzyf4a8kfrig1j73z66dw-bash-4.4-p23/bin/bash: warning: shell level (1000) too high, resetting to 1

Which I suspected there was an infinite loop being caused by the makeWrapper that is taking over the very path of stack.

It turns out that both the buildEnv solution and the overrideAttrs solutions both don't work when using stack ghci or stack build, this is due to some sort of infinite loop. Maybe because the redirection method of wrapProgram doesn't work with the way stack invokes itself.

The only safest solution right now to either wait for stack to implement automatic nix integration or use nix integration at the stack global configuration: https://github.com/commercialhaskell/stack/blob/master/doc/yaml_configuration.md#non-project-specific-config

That's it.

Just add:

nix:
  enable: true

Into your ~/.stack/config.yaml, then you'll get automatic nix integration. No longer need to use stack --nix. Note that switches it on for the entire user profile which is unfortunate, but avoids us from forcing nix integration using the project specific stack.yaml and we can use stack inside a nix-shell.

Note that stack tutorials think that the user wants to use nix inside stack, but we are actually using stack inside nix, not the same thing. But the --nix integration is still needed for stack to utilise nix installed GHC.

However note that means ALL stack projects will have nix enabled. So that may not be a good idea when working on stack projects (like third party projects) that don't have Nix enabled. I suspect that stack will retry gracefully without nix though.

No longer relevant when not using stack.