diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 5795031d5cb..b2c48a1900f 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -27,6 +27,7 @@ use uv_pep508::{MarkerEnvironment, StringVersion}; use uv_platform::{Arch, Libc, Os}; use uv_platform_tags::{Platform, Tags, TagsError, TagsOptions}; use uv_pypi_types::{ResolverMarkerEnvironment, Scheme}; +use uv_static::EnvVars; use crate::implementation::LenientImplementationName; use crate::managed::ManagedPythonInstallations; @@ -1149,6 +1150,8 @@ impl InterpreterInfo { }; let canonical = canonicalize_executable(&absolute).map_err(handle_io_error)?; + let python_executable = env::var_os(EnvVars::PYTHONEXECUTABLE).map(PathBuf::from); + let pyvenv_launcher = env::var_os(EnvVars::PYVENV_LAUNCHER).map(PathBuf::from); let cache_entry = cache.entry( CacheBucket::Interpreter, @@ -1170,7 +1173,13 @@ impl InterpreterInfo { // absolute path refers to different interpreters with matching ctimes, e.g., if you // have a `.venv/bin/python` pointing to both Python 3.12 and Python 3.13 that were // modified at the same time. - format!("{}.msgpack", cache_digest(&(&absolute, &canonical))), + // + // Launcher overrides can also change the reported executable and virtual environment + // without changing either executable path. + format!( + "{}.msgpack", + cache_digest(&(&absolute, &canonical, &python_executable, &pyvenv_launcher)) + ), ); // We check the timestamp of the canonicalized executable to check if an underlying diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 6bddc4ca443..6be14772915 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -1077,6 +1077,11 @@ impl EnvVars { #[attr_added_in("0.7.13")] pub const PYTHONHOME: &'static str = "PYTHONHOME"; + /// Overrides the executable Python uses to determine its environment. + #[attr_hidden] + #[attr_added_in("next release")] + pub const PYTHONEXECUTABLE: &'static str = "PYTHONEXECUTABLE"; + /// Used to correctly detect virtual environments when using trampolines. #[attr_hidden] #[attr_added_in("0.7.13")] diff --git a/crates/uv/tests/python/python_find.rs b/crates/uv/tests/python/python_find.rs index adbc33f45af..d4436da2184 100644 --- a/crates/uv/tests/python/python_find.rs +++ b/crates/uv/tests/python/python_find.rs @@ -146,7 +146,7 @@ fn python_find_cached_launcher_override() { // `PYTHONEXECUTABLE` makes the requested `other` interpreter report `.venv`. uv_snapshot!(context.filters(), context.python_find() .arg(&requested_python) - .env("PYTHONEXECUTABLE", &override_python), @" + .env(EnvVars::PYTHONEXECUTABLE, &override_python), @" exit_code: 0 (success) ----- stdout ----- [VENV]/bin/python3 @@ -155,10 +155,10 @@ fn python_find_cached_launcher_override() { // Without `PYTHONEXECUTABLE`, this should return `other`, not the cached `.venv`. uv_snapshot!(context.filters(), context.python_find() .arg(&requested_python) - .env_remove("PYTHONEXECUTABLE"), @" + .env_remove(EnvVars::PYTHONEXECUTABLE), @" exit_code: 0 (success) ----- stdout ----- - [VENV]/bin/python3 + [TEMP_DIR]/other/bin/python3 "); } diff --git a/hawk.toml b/hawk.toml index f8627f16a10..434c1cbe198 100644 --- a/hawk.toml +++ b/hawk.toml @@ -207,15 +207,6 @@ kind = "inherent_associated_constant" level = "expect" reason = "read by generated activation scripts" -[[override]] -lint = "hawk::dead_public" -crate = "uv_static" -item = "env_vars::EnvVars::PYVENV_LAUNCHER" -kind = "inherent_associated_constant" -level = "expect" -target = "cfg(not(windows))" -reason = "used by Windows trampoline launchers" - [[override]] lint = "hawk::dead_public" crate = "uv_static"