Use base executable to set virtualenv Python path (#8481)

See extensive discussion in
https://github.com/astral-sh/uv/pull/8433#issuecomment-2430472849.

This PR brings us into alignment with the standard library by using
`sys._base_executable` rather than canonicalizing the executable path.

The benefits are primarily for Homebrew, where we'll now resolve to
paths like `/opt/homebrew/opt/python@3.12/bin` instead of the
undesirable
`/opt/homebrew/Cellar/python@3.9/3.9.19_1/Frameworks/Python.framework/Versions/3.9/bin`.

Most other users should see no change, though in some cases, nested
virtual environments now have slightly different behavior -- namely,
they _sometimes_ resolve to the virtual environment Python (at least for
Homebrew; not for rtx or uv Pythons though). See
[here](https://docs.google.com/spreadsheets/d/1Vw5ClYEjgrBJJhQiwa3cCenIA1GbcRyudYN9NwQaEcM/edit?gid=0#gid=0)
for a breakdown.

Closes https://github.com/astral-sh/uv/issues/1640.
Closes https://github.com/astral-sh/uv/issues/1795.
This commit is contained in:
Charlie Marsh 2024-10-28 12:06:25 -04:00 committed by Zanie Blue
parent b6c531f4dd
commit 633467576b
3 changed files with 34 additions and 23 deletions

View file

@ -57,31 +57,20 @@ pub(crate) fn create(
// considered the "base" for the virtual environment. This is typically the Python executable
// from the [`Interpreter`]; however, if the interpreter is a virtual environment itself, then
// the base Python executable is the Python executable of the interpreter's base interpreter.
let base_python = if cfg!(unix) {
// On Unix, follow symlinks to resolve the base interpreter, since the Python executable in
// a virtual environment is a symlink to the base interpreter.
uv_fs::canonicalize_executable(interpreter.sys_executable())?
} else if cfg!(windows) {
// On Windows, follow `virtualenv`. If we're in a virtual environment, use
// `sys._base_executable` if it exists; if not, use `sys.base_prefix`. For example, with
// Python installed from the Windows Store, `sys.base_prefix` is slightly "incorrect".
let base_python = if cfg!(unix) && interpreter.is_standalone() {
// In `python-build-standalone`, a symlinked interpreter will return its own executable path
// as `sys._base_executable`. Using the symlinked path as the base Python executable is
// incorrect, since it will cause `home` to point to something that is _not_ a Python
// installation.
//
// If we're _not_ in a virtual environment, use the interpreter's executable, since it's
// already a "system Python". We canonicalize the path to ensure that it's real and
// consistent, though we don't expect any symlinks on Windows.
if interpreter.is_virtualenv() {
if let Some(base_executable) = interpreter.sys_base_executable() {
base_executable.to_path_buf()
} else {
// Assume `python.exe`, though the exact executable name is never used (below) on
// Windows, only its parent directory.
interpreter.sys_base_prefix().join("python.exe")
}
} else {
interpreter.sys_executable().to_path_buf()
}
// Instead, we want to fully resolve the symlink to the actual Python executable.
uv_fs::canonicalize_executable(interpreter.sys_executable())?
} else {
unimplemented!("Only Windows and Unix are supported")
std::path::absolute(
interpreter
.sys_base_executable()
.unwrap_or(interpreter.sys_executable()),
)?
};
// Validate the existing location.