Repository navigation
venv base path does not resolve symlinks using realpath() #106045
Description
It seems there is a bug in venv (and a similar one in virtualenv) where the "base" path in pyvenv.cfg is set incorrectly. If Python is installed in a non-standard folder, e.g. /usr/local/python-X.Y.Z and then symlinked into /usr/local/bin/python3, the venv package does not work correctly. I believe the source of the trouble is the setting of "home" variable. Specifically, this line:
dirname, exename = os.path.split(os.path.abspath(executable))
The dirname result is used to set "home". If the executable path is /usr/local/bin/python3 (actually a symlink to /usr/local/python-X.Y.Z/bin/python3), the "home" should not be set to /usr/local. Changing the above line (in the ensure_directories()) function to:
dirname, exename = os.path.split(os.path.realpath(executable))
This fixes the problem. I believe this is consistent with what the getpath.py module in Python does.
I noticed with problem when running the most recent Debian OS, version 12. It includes Python 3.11 and therefore /usr/lib/python3.11 exists. With the above bug, the venv tries to use /usr/lib/python3.11 as the sys.path. Importing the struct module fails with the mysterious error:
>>> import struct
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.11/struct.py", line 13, in <module>
from _struct import *
ModuleNotFoundError: No module named '_struct'
Linked PRs
- gh-106045: Fix
venvcreation from a python executable symlink #115237 - gh-106045: Fix venv path discovery for symlinked executables #156483
- [3.15] gh-106045: Fix venv path discovery for symlinked executables (GH-156483) #160002
- [3.14] gh-106045: Fix venv path discovery for symlinked executables (GH-156483) #160003
- gh-106045: Keep searching from venv home when the resolved base executable has no stdlib #160123
- [3.15] gh-106045: Keep searching from venv home when the resolved base executable has no stdlib (GH-160123) #160354
- [3.14] gh-106045: Keep searching from venv home when the resolved base executable has no stdlib (GH-160123) #160355
Activity
mayeut commentedon Sep 28, 2024
In order not to break usage involving symlinks to a python install tree, rather than resolving the realpath, only executable symlinks can be checked. xref pypa/virtualenv#2770 (comment)
pythongh-106045: Fix ``venv`` creation from a python executable symlink
pelson commentedon Jan 8, 2025
Yes, I've been tracking this down, and it boils down to
sys.base_exec_prefixbeing wrong. In turn, there are docs atcpython/Doc/c-api/init.rst
Line 653 in 6ea04da
I note also that setting home in
pyvenv.cfgtof'{sys.base_prefix}/bin(instead ofdirname(sys.executable)) also works-around this issue (IMO this is a little "less-wrong" than resolving the symlink tosys.executable, but still not correct).I've spent a lot of time digging into the logic of
getpath.pywhere the venv path work is done, and realised that the fundamental problem here is that having a symlinked executable outside of an installation prefix is not something that was considered by PEP-405. Specifically:Clearly if the executable (symlinked) is in a different location to the prefix, then the documented approach is going to result in a computed prefix from the symlink (if that prefix has the appropriate landmark "lib-dynlib" directory)
This explains why resolving the symlink to executable before using that as
homedoes work in many cases, but that solution misses the subtlety of symlinked prefixes thatgetpathhandles well for venv-less prefix resolution (which doesn't fully resolve symlinks, but instead stops descending as soon as it finds a suitable prefix).Note that today, CPython has no problem with non-venv symlinked executables living outside of the prefix. Using exactly the same setup as above, we get the desired behaviour:
Perhaps @FFY00 (given their recent changes to getpath) has some idea of whether we can use exactly the same logic for venv
base_prefixcalculation as we do for non-venvprefixcalculation?Fundamentally, my belief is that the definition of
homein PEP-405 should really be updated if it were to properly support symlinked executables outside of their prefix - the reason is that we don't record the actual base executable, and have to guess it based on heuristics (and the original symlink could have been named arbitrarily) . The OP's proposal to automatically resolve the symlink would be inconsistent with what Python does for non-venvs, and in my opinion, would be the wrong thing to do (as mentioned by others in this issue - it would break certain established / reasonable usages).Given that today it works well to run a symlinked executable which is outside of its prefix for non-venv cases, I would propose that the PEP is updated to replace
homewith abase_executablekey (which is simply the value ofsys.base_executable), upon which the existing getpath logic for determining its prefix and lib directories can be applied.I'd be happy to turn this into a discussion on python.org, if this is the best way to establish a solid rationale and consensus (please confirm if this is the best approach).
pelson commentedon Jan 8, 2025
To follow-up to my very long last message - I think following what CPython is doing for prefix resolution (for non-venvs) at startup would work well for all cases that I've seen. Namely, it uses a non-path segment recursive symlink resolve on the executable, which then is then used to search for the stdlib.
This would require no change in
venv- it should continue to reportdirname(executable)as thehomeinpyvenv.cfg, as it is the interpreter's responsibility to determine the correct location for thevenv's prefix based on that executable location.It turns out (after a lot of searching), that this is a two-line fix to
getpath.py:I'll dig into how to write a test for this, and submit.
One thing to note: I think the calculation of
sys._base_executableis over-resolving symlinks today - this is a separate bug which I guess means that homebrew users can't take a venv from a venv and have it survive beyond the next patch update.FFY00 commentedon Jan 8, 2025
Yeah, it's known that the
homekey is problematic. We could replace it with abase_executablekey, as you propose, but we'd still be relying on the IMO faulty "search up until we find something 🤷" heuristic. I'd rather just go away with all heuristics and replace thehomekey withbase-prefixandbase-exec-prefixkeys, which directly gives us the prefixes. I opened GH-127895 with this proposal.Please submit it! I think we can treat it as a bug, so we can still backport the fix to 3.12 and 3.13.
With GH-127972 and GH-127974, on POSIX builds with a shared libpython, this should now be mitigated by looking at the libpython directory before resorting to the base interpreter, which should be much more reliable.
pythongh-106045: Fix ``venv`` creation from a python executable symlink
mayeut commentedon Jan 11, 2025
It does not seem to be helping in this case (on macOS):
With #115237 resolving
homedifferently, the venv works with both shared & static builds.mayeut commentedon Jan 16, 2025
@pelson, did you mean the following instead ?
zahlman commentedon Mar 28, 2025
I frequently encounter this problem because of how I've set up my multiple local builds of Python (used to test version compatibility for packages, and also to investigate differences between Python versions). Because of the wrong home path, the standard library can't be found at all, with more dramatic consequences:
This seems to be a regression introduced in 3.11 (my
py3.12works because it sees the standard library of my system Python).FFY00 commentedon Mar 28, 2025
Can you run
ldd py3.13? If libpython is linked, please also let me know if the linker is loading it from the correct place.zahlman commentedon Mar 29, 2025
Not there. I just used all default compilation options for these builds.
But it looks the same for the ones where it works.
FFY00 commentedon Mar 30, 2025
Right, if you did not build with
--enable-shared, it will static link libpython. Are you moving or copying thepythonbinary to a different location? Or rather, how exactly are you installing Python?zahlman commentedon Mar 31, 2025
Python is installed by using
configureto set a prefix and usingmake altinstall, then manually symlinking to the altinstalled binary from/usr/local/bin. At least, that's what my notes say to do; I'll try to make time this week to redo it from scratch and confirm.jjhelmus commentedon Oct 31, 2025
I ran into this issue in python-build-standalone, astral-sh/python-build-standalone#380
Is there still interested in a change to
getpath.pyto address this? I'd be happy to create a PR for discussion.brettcannon commentedon Oct 31, 2025
@jjhelmus sure, let's see what a PR looks like.
FFY00 commentedon Dec 10, 2025
It's unclear to me what would be changed, but feel free to open a PR. Just beware that touching
getpath.pyis always very delicate, and there's a big possibility that whatever you change will slightly change its behavior in ways other use-cases may not be expecting, and break them. This is why I am always so cautious when touchinggetpath😅reneleonhardt commentedon Dec 10, 2025
Would it be possible to add all those untested use cases / branches to the unit test coverage before changing
getpath?FFY00 commentedon Dec 10, 2025
I believe a lot of them are, IIRC the
getpathtests are pretty extensive. If you change something, most likely some test will fail, and we'll have to carefully consider the behavior change.jjhelmus commentedon Aug 27, 2026
I've come back to this issue after some time and put up a proposed fix with test cases in #156483.
Currently,
getpath.pysearches for landmark files relative to the home value inpyvenv.cfg. When that value refers to an symlink, discovery can fall back to the prefix value set during the build. If the install does not match this location, either from relocating after and install or installing into a different location, then sys.base_prefix, etc are set incorrectly. This is the case that occured npython-build-standaloneas the distribution are almost never installed into the configured prefix,/install.Additionally, if an landmark file exist near the symlink, perhaps from another valid python installation, these can result in the symlinked directory being identified as the base installation.
Both of these can cases be reproduced with Docker containers:
Result:
And the misleading landmark file:
Result:
Both of these layouts work as expected with the C-based path-discovery in Python 3.10:
Relocated Python
Results:
Landmark
gh-106045: Fix venv path discovery for symlinked executables (#156483)
[3.15] gh-106045: Fix venv path discovery for symlinked executables (G…
[3.14] gh-106045: Fix venv path discovery for symlinked executables (G…
pythongh-106045: Keep searching from venv home when the resolved base…
gh-106045: Keep searching from venv home when the resolved base execu…
[3.14] gh-106045: Keep searching from venv home when the resolved bas…
[3.15] gh-106045: Keep searching from venv home when the resolved bas…