-
-
Notifications
You must be signed in to change notification settings - Fork 14.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MacOS: Python shebang issue #123067
Comments
Duplicate of #65351 |
Some more investegation: [nix-shell]$ which pip
Looking at our pip executable (which is also a wrapped shell script):
#! /nix/store/ra8yvijdfjcs5f66b99gdjn86gparrbz-bash-4.4-p23/bin/bash -e
export NIX_PYTHONPREFIX='/nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env'
export NIX_PYTHONEXECUTABLE='/nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9'
export NIX_PYTHONPATH='/nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/lib/python3.9/site-packages'
export PYTHONNOUSERSITE='true'
exec "/nix/store/ccm6jcg1il8wdshiavrbc65p3s6i8rbl-python3.9-pip-21.0.1/bin/pip" "$@" What happens if we try to modify # ...
export NIX_PYTHONEXECUTABLE='this-is-a-test'
# ... ... and then reinstall ipython:
#!this-is-a-test
# -*- coding: utf-8 -*-
import re
import sys
from IPython import start_ipython
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(start_ipython()) So it seems like |
I'm guessing a solution (on MacOS) here would be to set # ...
export NIX_PYTHONEXECUTABLE='/nix/store/ra8yvijdfjcs5f66b99gdjn86gparrbz-bash-4.4-p23/bin/bash /nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9'
# ... But it seems like [nix-shell]$ rm -rf _build && pip install -q ipython && cat _build/pip/bin/ipython #!/bin/sh
'''exec' "/nix/store/ra8yvijdfjcs5f66b99gdjn86gparrbz-bash-4.4-p23/bin/bash /nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9" "$0" "$@"
' '''
# -*- coding: utf-8 -*-
import re
import sys
from IPython import start_ipython
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(start_ipython()) Which doesn't work
Manually modifying the ipython shebang ( #!/nix/store/ra8yvijdfjcs5f66b99gdjn86gparrbz-bash-4.4-p23/bin/bash /nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9
# -*- coding: utf-8 -*-
import re
import sys
from IPython import start_ipython
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(start_ipython()) [nix-shell]$ ipython
Python 3.9.2 (default, Apr 8 2021, 19:41:15)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.23.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: |
Okay, through some experimentation, I discovered a hack for working around the issue with spaces in # ...
export NIX_PYTHONEXECUTABLE='/nix/store/ra8yvijdfjcs5f66b99gdjn86gparrbz-bash-4.4-p23/bin/bash" "/nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9'
# ... Notice the double quotes in the middle. This causes the following shebang expression to be generated instead: [nix-shell]$ rm -rf _build && pip install -q ipython && cat _build/pip/bin/ipython #!/bin/sh
'''exec' "/nix/store/ra8yvijdfjcs5f66b99gdjn86gparrbz-bash-4.4-p23/bin/bash" "/nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9" "$0" "$@"
' '''
# -*- coding: utf-8 -*-
import re
import sys
from IPython import start_ipython
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(start_ipython()) Which actually seems to work: [nix-shell]$ ipython
Python 3.9.2 (default, Apr 8 2021, 19:41:15)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.23.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: |
Since this also works around all the shebang-limitations (like maximum length, number of arguments, pointing to a script etc), I imagine this should be fairly polymorphic with respect to different unix-platforms. One would need to check whether the python interpreter is a binary file or a text file before determining what to use for The reference to |
|
executable = os.environ.pop('NIX_PYTHONEXECUTABLE', None) | |
prefix = os.environ.pop('NIX_PYTHONPREFIX', None) | |
if 'PYTHONEXECUTABLE' not in os.environ and executable is not None: | |
sys.executable = executable |
pip uses sys.executable
for deciding on what shebang to use when creating _build/pip/bin/ipython
.
ScriptMaker._get_shebang()
https://github.com/pypa/pip/blob/e6414d6db6db37951988f6f2b11ec530ed0b191d/src/pip/_vendor/distlib/scripts.py#L164
get_executable()
https://github.com/pypa/pip/blob/e6414d6db6db37951988f6f2b11ec530ed0b191d/src/pip/_vendor/distlib/util.py#L312
This code puts double quotes around our executable if it contains spaces, and doesn't start with a double quote:
enquote_executable(executable) https://github.com/pypa/pip/blob/e6414d6db6db37951988f6f2b11ec530ed0b191d/src/pip/_vendor/distlib/scripts.py#L63
This code creates the exec-multiline shebang if it detects any spaces in the shebang (or we exceed the maximum shebang-length):
ScriptMaker._build_shebang():
https://github.com/pypa/pip/blob/e6414d6db6db37951988f6f2b11ec530ed0b191d/src/pip/_vendor/distlib/scripts.py#L147-L155
So turns out (from looking at pip source code) that we can put double quotes at the start/end of NIX_PYTHONEXECUTABLE, to make it slightly more explicit: [nix-shell]$ cat $(which pip)
#! /nix/store/jdi2v7ir1sr6vp7pc5x0nhb6lpcmg6xg-bash-4.4-p23/bin/bash -e
export NIX_PYTHONPREFIX='/nix/store/qpqw3macz4iv66a0rxvlajpnifhxxw4c-python3-3.9.2-env'
export NIX_PYTHONEXECUTABLE='"/nix/store/jdi2v7ir1sr6vp7pc5x0nhb6lpcmg6xg-bash-4.4-p23/bin/bash" "/nix/store/qpqw3macz4iv66a0rxvlajpnifhxxw4c-python3-3.9.2-env/bin/python3.9"'
export NIX_PYTHONPATH='/nix/store/qpqw3macz4iv66a0rxvlajpnifhxxw4c-python3-3.9.2-env/lib/python3.9/site-packages'
export PYTHONNOUSERSITE='true'
exec "/nix/store/vr4yqqmx0s999xspgprnw7r3d1609hac-python3.9-pip-21.0.1/bin/pip" "$@" |
Counterexample to where the [nix-shell]$ pip install live-server
Collecting live-server
Using cached live_server-0.9.9-py3-none-any.whl (5.9 kB)
Collecting beautifulsoup4==4.6.3
Using cached beautifulsoup4-4.6.3-py3-none-any.whl (90 kB)
Collecting Click==7.0
Using cached Click-7.0-py2.py3-none-any.whl (81 kB)
Collecting tornado==5.1.1
Using cached tornado-5.1.1.tar.gz (516 kB)
ERROR: Error [Errno 2] No such file or directory: '"/nix/store/jdi2v7ir1sr6vp7pc5x0nhb6lpcmg6xg-bash-4.4-p23/bin/bash" "/nix/store/qpqw3macz4iv66a0rxvlajpnifhxxw4c-python3-3.9.2-env/bin/python3.9"' while executing command python setup.py egg_info
ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: '"/nix/store/jdi2v7ir1sr6vp7pc5x0nhb6lpcmg6xg-bash-4.4-p23/bin/bash" "/nix/store/qpqw3macz4iv66a0rxvlajpnifhxxw4c-python3-3.9.2-env/bin/python3.9"' It seems like sys.executable is expected to be a valid path, meaning we can't use the bash-hack. Although we get a working shebang - something else ends up failing as a result. |
I marked this as stale due to inactivity. → More info |
I'm having the same issue with |
@NilsIrl I'm assuming you are using # shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
ruby = pkgs.ruby.override { makeWrapper = pkgs.makeBinaryWrapper; };
rubyPkgs = ruby.withPackages(ps: []);
in
pkgs.mkShell {
buildInputs = [ rubyPkgs ];
} If it does, I can make a PR to make this the default wrapper for ruby |
That doesn't seem to be the case: https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/security/cewl/default.nix |
@NilsIrl What nix expression are you using? |
Oh, looking at pkgs.cewl I see the following: nix-shell -p cewl
[nix-shell]$ head -n4 $(which cewl) #!/nix/store/44mm7f7rsp5vc8dflkkn3pfbp8ghrjlf-wrapped-ruby-cewl-ruby-env/bin/ruby
#encoding: UTF-8
# == CeWL: Custom Word List Generator [nix-shell]$ cat /nix/store/44mm7f7rsp5vc8dflkkn3pfbp8ghrjlf-wrapped-ruby-cewl-ruby-env/bin/ruby #! /nix/store/a54wrar1jym1d8yvlijq0l2gghmy8szz-bash-5.1-p12/bin/bash -e
export BUNDLE_GEMFILE='/nix/store/s56719qzccbym8mbyfax6rsi2jx26zv3-gemfile-and-lockfile/Gemfile'
unset BUNDLE_PATH
export BUNDLE_FROZEN='1'
export GEM_HOME='/nix/store/yxapcqnbrfhgi7sl69hbqp18j0vs61sm-cewl-ruby-env/lib/ruby/gems/2.7.0'
export GEM_PATH='/nix/store/yxapcqnbrfhgi7sl69hbqp18j0vs61sm-cewl-ruby-env/lib/ruby/gems/2.7.0'
exec "/nix/store/ia70ss13m22znbl8khrf2hq72qmh5drr-ruby-2.7.5/bin/ruby" "$@" I'm on Linux right now though, but I can imagine that this wrapper looks the same on macOS as well - so we need to use makeBinaryWrapper for |
@NilsIrl Kind of a "shotgun"-solution, but can you verify whether this works for you? # shell.nix
let
pkgs = import <nixpkgs> {
overlays = [ (final: prev: { makeWrapper = prev.makeBinaryWrapper; }) ];
};
in
pkgs.mkShell {
buildInputs = [ pkgs.cewl ];
} nix-shell shell.nix
[nix-shell]$ head -n4 $(which cewl) #!/nix/store/d9shnlmjh51av5hsych4r4fpl0m9ydbf-wrapped-ruby-cewl-ruby-env/bin/ruby
#encoding: UTF-8
# == CeWL: Custom Word List Generator Now, the shebang will be a binary wrapper instead of a shell script, which should make this work on macOS: [nix-shell]$ cat /nix/store/d9shnlmjh51av5hsych4r4fpl0m9ydbf-wrapped-ruby-cewl-ruby-env/bin/ruby ...binary data...
# ------------------------------------------------------------------------------------
# The C-code for this binary wrapper has been generated using the following command:
makeCWrapper /nix/store/ia70ss13m22znbl8khrf2hq72qmh5drr-ruby-2.7.5/bin/ruby \
--set 'BUNDLE_GEMFILE' '/nix/store/s56719qzccbym8mbyfax6rsi2jx26zv3-gemfile-and-lockfile/Gemfile' \
--unset 'BUNDLE_PATH' \
--set 'BUNDLE_FROZEN' '1' \
--set 'GEM_HOME' '/nix/store/fal997gsyzyjrzcpx6cg2qirpyw1n0cn-cewl-ruby-env/lib/ruby/gems/2.7.0' \
--set 'GEM_PATH' '/nix/store/fal997gsyzyjrzcpx6cg2qirpyw1n0cn-cewl-ruby-env/lib/ruby/gems/2.7.0'
# (Use `nix-shell -p makeBinaryWrapper` to get access to makeCWrapper in your shell)
# ------------------------------------------------------------------------------------
...binary-data... |
I can happily say that after waiting a few hours for stuff to build I have finally landed in a shell in which I can run |
Describe the bug
On MacOS, nested shebangs/shebangs pointing at scripts are not allowed. When using
pkgs.python39.withPackages(...)
and installing packages with pip into a sandbox environment, binary executables get a shebang that makes them unable to execute.To Reproduce
Expected behavior
It should be possible to do
$ pip install ipython
, followed by$ ipython
- and have ipython actually execute successfully.Observations
The shebang in _build/pip/bin/ipython points to a script, which is not allowed on MacOS (only Linux):
[nix-shell]$ cat $PIP_PREFIX/bin/ipython
If
/nix/store/i46k148mi830riq4wxh49ki8qmq0731k-python3-3.9.2-env/bin/python3.9
was an executable, everything would be fine. But since this is a script, with its own shebang, this causes issues:Metadata
"x86_64-darwin"
Darwin 20.3.0, macOS 10.16
no
no
nix-env (Nix) 2.3.10
"darwin, nixpkgs-21.05pre284563.ab6943a7450"
/Users/tobias/.nix-defexpr/channels/nixpkgs
The text was updated successfully, but these errors were encountered: