MercuryTechnologies / nix-your-shell

A `nix` and `nix-shell` wrapper for shells other than `bash`
MIT License
85 stars 12 forks source link

nix-profile shadows binaries from nix-shell #25

Closed P1n3appl3 closed 1 year ago

P1n3appl3 commented 1 year ago

tl;dr: maybe nix-your-shell should export the __ETC_PROFILE_NIX_SOURCED variable?

I recently noticed that if you've got something installed in your profile, it will take precedence over packages added to a nix-shell when using nix-your-shell. For example I've got python 3.10 in my nix profile:

~ 🍍 python --version       
Python 3.10.10
~ 🍍 which python   
/home/joseph/.nix-profile/bin/python
~ 🍍 echo $PATH | sd : '\n'
/home/joseph/.cargo/bin
/home/joseph/.local/bin
/home/joseph/.nix-profile/bin
/nix/var/nix/profiles/default/bin
/usr/local/bin
/usr/bin
/bin

But when I enter a nix-shell with python 3.9, the 3.10 one is still earlier in my path

~ 🍍 nix-shell -p python39
~ β„οΈπŸ python --version
Python 3.10.10
~ β„οΈπŸ which python   
/home/joseph/.nix-profile/bin/python
~ β„οΈπŸ echo $PATH | sd : '\n'
/home/joseph/.nix-profile/bin
/nix/var/nix/profiles/default/bin
/nix/store/aadll2010h6fks413fjhrdcyga5fig2k-python3-3.9.16/bin
/nix/store/8qm6sjqa09a03glzmafprpp69k74l4lm-binutils-2.40/bin
... (more nix-store paths)
/home/joseph/.cargo/bin
/home/joseph/.local/bin
/home/joseph/.nix-profile/bin
/nix/var/nix/profiles/default/bin
/usr/local/bin
/usr/bin
/bin

This happens because ~/.nix-profile/bin and /nix/var/nix/profiles/default/bin get added to $PATH in /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh (which is sourced in /etc/zsh/zshenv in my nix installation) . Looking in there it checks a var (__ETC_PROFILE_NIX_SOURCED) before executing, so it should only be run once per shell. But since they just assign the variable instead of exporting it, starting a subshell ends up re-sourcing that script. Manually exporting that var is enough to prevent it from running the script and results in the expected behavior:

~ β„οΈπŸ exit                              
~ 21s 🍍 export __ETC_PROFILE_NIX_SOURCED=1
~ 🍍 nix-shell -p python39
~ β„οΈπŸ python --version
Python 3.9.16
~ β„οΈπŸ which python   
/nix/store/aadll2010h6fks413fjhrdcyga5fig2k-python3-3.9.16/bin/python
~ β„οΈπŸ echo $PATH | sd : '\n'
/nix/store/aadll2010h6fks413fjhrdcyga5fig2k-python3-3.9.16/bin
... (more nix-store paths)
/home/joseph/.cargo/bin
/home/joseph/.local/bin
/home/joseph/.nix-profile/bin
/nix/var/nix/profiles/default/bin
/usr/local/bin
/usr/bin
/bin

I've exported the var in my .zshrc for now, but it seems like the sort of thing that nix-your-shell should do for you. I removed nix-your-shell and checked with the vanilla nix-shell and it doesn't have this issue, though I'm not sure how it avoids sourcing nix-daemon.sh.

Also thanks for writing this! I'm new to nix and not being able to use my zsh configuration was making trying it out way more annoying.

rrbutani commented 1 year ago

I removed nix-your-shell and checked with the vanilla nix-shell and it doesn't have this issue, though I'm not sure how it avoids sourcing nix-daemon.sh.

I think the answer is that it doesn't πŸ˜›

❯ tr : "\n" <<<$PATH
/Users/me/.nix-profile/bin
/nix/var/nix/profiles/default/bin
/usr/local/bin
/System/Cryptexes/App/usr/bin
<snipped>

❯ \nix-shell -p hello --command 'tr : "\n" <<<$PATH'
/nix/store/6886788jq61kq2blrhs70w4y63pq35ys-bash-interactive-5.2-p15/bin
/nix/store/vlh4h7796gzks5kwlcvmcgr2ldp1xz7q-clang-wrapper-11.1.0/bin
<snipped: more stdenv paths>
/nix/store/44x762l9fgrbb5j8cy1wp6azc1ana7i3-hello-2.12.1/bin
/nix/store/7lqsqgisdrkq9ymfigjf9m0jnxwj1m9w-coreutils-9.1/bin
<snipped: even more stdenv paths>
/Users/me/.nix-profile/bin         # dup!
/nix/var/nix/profiles/default/bin  # dup!
/Users/me/.nix-profile/bin
/nix/var/nix/profiles/default/bin
/usr/local/bin
/System/Cryptexes/App/usr/bin
<snipped>

Because nix-shell/nix develop prepend to $PATH and run their setup within the shell they spawn it's not an issue; the env setup script gets the last say.

For any-nix-shell/nix-your-shell – which use --command to spawn the desired shell after the env setup script has run (1, 2) – this is observable.

Beyond $PATH ordering discrepancies/non-exported vars not persisting, the --command approach also means that non-exportable bash things (aliases, arrays, functions usually, etc.) also don't persist. For nix shell type use cases (i.e. "I just want to put things on my $PATH") and probably most nix-shell/nix develop use cases (i.e. wanting exportable env vars like XDG_DATA_DIRS, PYTHONPATH, etc. to be modified) this is totally fine. But users with slightly more exotic use cases like using nix-shell to build derivations or using mkShell's shellHook to provide bash functions, aliases, etc in devShells will notice this discrepancy.


If you'll excuse the tangent, I want to call out a couple of alternatives to --command that address these discrepancies; this isn't exactly what @P1n3appl3's issue is about but I think it's still related:

1: Using nix print-dev-env directly

My understanding is that nix print-dev-env dumps the setup script that nix develop/nix-shell/other facsimiles use.

Mapping nix develop/nix-shell commands to nix print-dev-env (nix-your-shell already does the work of parsing args in a rigorous way πŸ™‚) and then sourcing the script emitted in the shell that's spawned (i.e. --rcfile for bash) would address all of the discrepancies above; afaik for bash this should be entirely equivalent to running nix develop directly.

The big downside to this approach is that nix print-dev-env itself is somewhat tailored to bash and the stdenv machinery (which will very likely land in the script print-dev-env emits) is very bash specific.

We definitely have the ability to post-process the env script we get out of print-dev-env but there's a lot of surface area to try to make zsh/fish/etc compatible (arbitrary shellHooks in projects also probably tend to be bash specific) and the failure modes generally aren't graceful.

Note FWIW, zsh -c "source <(nix print-dev-env nixpkgs#hello)" does work for me with one modification (LINENO cannot be reassigned -- this is probably the kind of tweak we could get upstreamed) but this probably isn't indicative; I'm not familiar with fish but I'd expect that it would require many more tweaks.

Also, the value here over just having the exportable env vars is dubious; the stdenv functions and machinery are present but actually trying to use any of it breaks immediately.

2: Using nix print-dev-env selectively, via bash

This is more or less the nix-direnv approach; i.e.:

Like the --command approach this will not preserve aliases/arrays/functions/etc from the "actual" underlying bash shell.

The upside here is that because we have explicit control over the exportable env vars we're adding to the final bash/zsh/fish/etc shell we can choose to, for example, modify env vars after regular shell init has happened. This would fix issues like the OPs as well as this general class of issue (i.e. I source ~/.cargo/env in my .bashrc but don't want that to shadow nix provided binaries in a nix-shell).

nix-your-shell could go down this route by leveraging direnv (there'd still be value to using nix-your-shell; doing echo "use nix -p python3" > .envrc && direnv allow instead of nix-shell -p python3 isn't exactly great UX).

Alternatively, there's also some prior art here re: using a bash subprocess to extract env vars without using all of the direnv machinery.


Ultimately, what option makes the most sense depends on the goals of the project.

Given the typical use cases for nix develop and friends I think it probably doesn't make sense to pursue the first option (though it might be nice to provide some escape hatch or to have nix-your-shell behave differently for bash so nix-shell can still be used to debug drvs? not sure).

I think the second option aligns pretty well with the common use case – but I think it's totally reasonable to decide that the added complexity isn't worth it and that ad-hoc fixes for the common pitfalls (i.e. adding export __ETC_PROFILE_NIX_SOURCED to env.fish and env.sh) are sufficient to provide a good user experience.


Also, I want to echo @P1n3appl3: thanks for making nix-your-shell! I really appreciate the clear docs and source code.

Hopefully this comment wasn't too much of a diversion and is of some value!

9999years commented 1 year ago

Hey, sorry for the delay on this and thanks for the detailed reporting here! I had uh, forgotten to set this repository to watched so I wasn't getting notifications. Let me read the rest of this thread...

9999years commented 1 year ago

This issue seems pretty hairy! I've actually had a workaround myself β€” some code in my config.fish conditionally sources stuff depending on if I'm in a nix-shell or not. (I carried this over from any-nix-shell.)

The big downside to this approach is that nix print-dev-env itself is somewhat tailored to bash

Yeah, agreed. I do not really want to figure out how to adapt that stuff to fish and other shells. (I think it would also make adding new shells much more difficult.)

The second approach, sourcing nix print-dev-env in a bash shell and transferring those environment variables over to the new shell, seems a little better.

Like the --command approach this will not preserve aliases/arrays/functions/etc from the "actual" underlying bash shell.

I think this is more or less fine. It would be really nice to preserve aliases/functions/etc. but probably more trouble than it's worth. When I need those I usually just run command nix-shell ... and deal with bash for the duration.

Both of these options require a fair amount of work, though. Exporting __ETC_PROFILE_NIX_SOURCED would be easy, so maybe I'll do that just to get something out the door? I'm not sure if there would be any unintended consequences to that.

9999years commented 1 year ago

Should I close this now that we have $__ETC_PROFILE_NIX_SOURCED exported, or wait until we get a more thorough solution implemented?

For transparency, I have no scheduled plans to implement a more thorough solution, and I'm a little hesitant to increase the complexity of this project so substantially, but I don't want to write the possibility off entirely β€” it might be a really good+useful idea!

P1n3appl3 commented 1 year ago

The workaround is good enough for me, falling back to command nix-shell when i need the functions is fine for my use case.