xzfc / cached-nix-shell

Instant startup time for nix-shell
https://xzfc.github.io/cached-nix-shell/cached-nix-shell.1
The Unlicense
195 stars 16 forks source link

shellHook behavior #18

Open mzabani opened 3 years ago

mzabani commented 3 years ago

Given the following cached-shell.nix:

{ pkgs ? import <nixpkgs> {} }:
    pkgs.mkShell {
        shellHook = ''
            echo "Initialize something here (maybe a Postgres Database, for example)"
            export SOMEVAR="$OTHERVAR"
        '';
    }

This is how nix-shell behaves:

$ OTHERVAR=201 nix-shell cached-shell.nix 
Initialize something here (maybe a Postgres Database, for example)

$ echo $SOMEVAR
201

And this is cached-nix-shell (the echo command outputs nothing both with a cached shell and when caching it the first time):

$ OTHERVAR=201 cached-nix-shell cached-shell.nix 

$ echo $SOMEVAR

So I think there's a difference in behavior. Would it be correct to say that the correct behavior for cached-nix-shell should be:

  1. Cache a derivation which is the shell derivation without its shellHook attribute. However, take care to cache dependencies that might be brought in by ${some-nix-var} in shellHook's body.
  2. Run shellHook everytime cached-nix-shell runs.

Thanks in advance!

xzfc commented 3 years ago

Yep, there are some differences in behavior caused by shell hooks.

However, the 100% correct behavior would be more complicated than the way that you described, because:

  1. shellHook is not the only hook that gets executed when you run nix-shell. Another hook is setup-hook. In the following example, $PYTHONPATH variable is set by the setup-hook of python3.

    $ cat ./shell-with-python3.nix
    { pkgs ? import <nixpkgs> { } }:
    pkgs.mkShell { buildInputs = [ pkgs.python3 ]; }
    
    $ nix-shell ./shell-with-python3.nix --run 'echo $PYTHONPATH'
    /nix/store/fjgnz0xfl04hsblsi4ym5y5akfh6mlmy-python3-3.8.5/lib/python3.8/site-packages:/nix/store/fjgnz0xfl04hsblsi4ym5y5akfh6mlmy-python3-3.8.5/lib/python3.8/site-packages
    
    $ PYTHONPATH=/somedir nix-shell ./shell-with-python3.nix --run 'echo $PYTHONPATH' 
    /somedir:/nix/store/fjgnz0xfl04hsblsi4ym5y5akfh6mlmy-python3-3.8.5/lib/python3.8/site-packages:/nix/store/fjgnz0xfl04hsblsi4ym5y5akfh6mlmy-python3-3.8.5/lib/python3.8/site-packages
    
    $ PYTHONPATH=/somedir dontAddPythonPath=1 nix-shell ./shell-with-python3.nix --run 'echo $PYTHONPATH'
    /somedir
  2. Shell hook may access not only the exported environment variables (which are currently cached by c-n-s) but also bash functions defined by setup hooks or setup.sh (which are not). In the following example stripHash is a bash function defined in setup.sh.

    $ cat ./shell-print-stripHash.nix
    { pkgs ? import <nixpkgs> { } }:
    pkgs.mkShell { shellHook = "stripHash $NIX_CC"; }
    
    $ nix-shell ./shell-print-stripHash.nix --run :
    gcc-wrapper-9.3.0
xzfc commented 3 years ago

If you need a workaround for this particular use case, you might add these lines to your bashrc:

if [ "$IN_CACHED_NIX_SHELL" ]; then
    eval "$shellHook"
    unset shellHook
fi

Or, if you'll need just the variable, you'll have to pass it with --keep:

$ OTHERVAR=201 cached-nix-shell --keep OTHERVAR cached-shell.nix
mzabani commented 3 years ago

Oh, I see, things are indeed more complicated than I thought. Thanks for explaining. The workaround you suggested works really well too. Thanks!

I wonder though if it wouldn't still be better/more expected to not cache shellHook and run it upon invocation. It certainly is what I expected, and from what I understand cached-nix-shell is already insensitive to environment variables which might alter what setuphook does (so your examples are with PYTHONPATH would already be different with c-n-s, and like that they'd remain), so this change would be an improvement? Although of course, a drastic/possibly breaking change in behaviour.