-
-
Notifications
You must be signed in to change notification settings - Fork 14.4k
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
Fix venv creation in Python environments #297628
Conversation
@domenkozar @3541 New PR |
I suggest we merge this to staging and give it a go. |
for path in ${lib.concatStringsSep " " paths}; do | ||
if [ -d "$path/bin" ]; then | ||
cd "$path/bin" | ||
for prg in *; do | ||
if [ -f "$prg" ]; then | ||
rm -f "$out/bin/$prg" | ||
if [ -x "$prg" ]; then | ||
makeWrapper "$path/bin/$prg" "$out/bin/$prg" --set NIX_PYTHONPREFIX "$out" --set NIX_PYTHONEXECUTABLE ${pythonExecutable} --set NIX_PYTHONPATH ${pythonPath} ${lib.optionalString (!permitUserSite) ''--set PYTHONNOUSERSITE "true"''} ${lib.concatStringsSep " " makeWrapperArgs} | ||
if [ -f ".$prg-wrapped" ]; then | ||
echo "#!${pythonExecutable}" > "$out/bin/$prg" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I am wrong, but does this assume that all executables in linked Python packages are Python scripts? This is an assumption that cannot be made; it's not uncommon to have shell scripts in bin/
and binaries can occur as well. Replacing the shebang can be done, but it needs to be checked that it is a Python interpreter, and preferably also exactly the same interpreter. The latter should actually always be the case, if not, we have another problem (e.g. with overriding somewhere).
In time we should aim to not wrap packages at build time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it only assumes that if files named bin/foo
and bin/.foo-wrapped
exist, then bin/foo
is a wrapper and bin/.foo-wrapped
is a python script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In time we should aim to not wrap packages at build time.
Yes! I have an idea for how to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for extending the tests, this gives a lot more trust in the functioning of this approach.
Please do separate the make-wrapper changes in a separate commit as they are a separate unit.
The way we build python environments is subtly broken. A python environment should be semantically identical to a vanilla Python installation in, say, /usr/local. The current implementation, however, differs in two important ways. The first is that it's impossible to use python packages from the environment in python virtual environments. The second is that the nix-generated environment appears to be a venv, but it's not. This commit changes the way python environments are built: * When generating wrappers for python executables, we inherit argv[0] from the wrapper. This causes python to initialize its configuration in the environment with all the correct paths. * We remove the sitecustomize.py file from the base python package. This file was used tweak the python configuration after it was incorrectly initialized. That's no longer necessary. The end result is that python environments no longer appear to be venvs, and behave more like a vanilla python installation. In addition it's possible to create a venv using an environment and use packages from both the environment and the venv.
When building a python environment's bin directory, we now detect wrapped python scripts from installed packages, and generate unwrapped copies with the environment's python executable as the interpreter.
@FRidh Done! |
Did one last manual check, and everything still works as expected. |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/nixpkgs-news-a-weekly-recap-for-the-nix-community/42137/5 |
makeWrapper "$path/bin/$prg" "$out/bin/$prg" --set NIX_PYTHONPREFIX "$out" --set NIX_PYTHONEXECUTABLE ${pythonExecutable} --set NIX_PYTHONPATH ${pythonPath} ${lib.optionalString (!permitUserSite) ''--set PYTHONNOUSERSITE "true"''} ${lib.concatStringsSep " " makeWrapperArgs} | ||
if [ -f ".$prg-wrapped" ]; then | ||
echo "#!${pythonExecutable}" > "$out/bin/$prg" | ||
sed -e '1d' -e '3d' ".$prg-wrapped" >> "$out/bin/$prg" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming line 3 here is wrong. In normal wrappers this is line 2 and when injecting the wrapper this could be any line, since it is skipping comments IIRC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert in #302385
@cwp this PR got reverted because of the broken |
cc @domenkozar |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
The problem
The way we build python environments is subtly broken. A python environment should be semantically
identical to a vanilla Python installation in, say,
/usr/local
. The current implementation,however, differs in two important ways.
The first is that it's impossible to use python packages from the environment in python virtual
environments. Here's a demonstration:
Another problem is that the nix installation of python appears to python code to be a virtual
environment. The canonical way to detect a Python venv is to compare
sys.prefix
tosys.base_prefix
. In the base python installation, they will have the same value, but a venv willupdate
sys.prefix
to point to the venv.The nix-generated environment appears to be a venv, but it's not. Most notably, it lacks a
pyvenv.cfg
file in the root directory, but the structure of the file tree and the symlinks itcontains are also a bit different. Sophisticated python code that manipulates virtualenvs fails when
run in a nix environment.
The cause
The build machinery for Python packages does some clever manipulation of the Python runtime to make
the python interpreter and python packages that are individually defined as nix derivations
available to python code without copying them all into a single directory the way a vanilla Python
installation does.
The python interpreter packages use the sitecustomize
hook to read environment variables and manipulate the python runtime:
sys.executable
based onNIX_PYTHONEXECUTABLE
sys.prefix
based onNIX_PYTHONPREFIX
sys.path
based onNIX_PYTHONPATH
When a python environment with additional packages is built, a wrapper is created
that invokes the python interpreter with the correct environment supply the sitecustomize module
with the information it needs. This wrapper is what causes the issues mentioned above. The python
interpeter relies very heavily on the path with which it is invoked to initialize its runtime. The
wrapper is quite transparent; it invokes the python interpreter with the "real" path of the
interpreter rather than that of the wrapper. This leads the python runtime to get built in the
context of the bare interpreter, rather than the full environment. The sitecustomize module then
makes some tweaks, but it can't completely compensate for the "incorrect" initialization of the
runtime.
The solution
The fix is pretty simple. Rather than invoking python with the "correct" path to the interpreter,
we invoke it with the path to the wrapper. This causes python to initialize its runtime correctly,
and makes all the sitecustomize machinery unnecessary. Rather than manipulating the
python runtime, we rely on symlinks to make the various nix packages available in the enviornment.
This approach has several benefits:
sitecustomize
for their own purposesTesting
This change includes tweaks to the existing python environment tests, and several new tests.