diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 50b52d9d5c2c..0dfbe76f5c3e 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -140,6 +140,25 @@ impl Interpreter { } } + /// Return the [`Interpreter`] for the base executable, if it's available. + /// + /// If no such base executable is available, or if the base executable is the same as the + /// current executable, this method returns `None`. + pub fn to_base_interpreter(&self, cache: &Cache) -> Result, Error> { + if let Some(base_executable) = self + .sys_base_executable() + .filter(|base_executable| *base_executable != self.sys_executable()) + { + match Self::query(base_executable, cache) { + Ok(base_interpreter) => Ok(Some(base_interpreter)), + Err(Error::NotFound(_)) => Ok(None), + Err(err) => Err(err), + } + } else { + Ok(None) + } + } + /// Returns the path to the Python virtual environment. #[inline] pub fn platform(&self) -> &Platform { diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index 097392efee0c..3487680a99e9 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -42,6 +42,22 @@ impl CachedEnvironment { ) -> anyhow::Result { let spec = RequirementsSpecification::from_requirements(requirements); + // When caching, always use the base interpreter, rather than that of the virtual + // environment. + let interpreter = if let Some(interpreter) = interpreter.to_base_interpreter(cache)? { + debug!( + "Caching via base interpreter: `{}`", + interpreter.sys_executable().display() + ); + interpreter + } else { + debug!( + "Caching via interpreter: `{}`", + interpreter.sys_executable().display() + ); + interpreter + }; + // Resolve the requirements with the interpreter. let resolution = resolve_environment( &interpreter, @@ -60,11 +76,13 @@ impl CachedEnvironment { // Hash the resolution by hashing the generated lockfile. // TODO(charlie): If the resolution contains any mutable metadata (like a path or URL // dependency), skip this step. - let lock = Lock::from_resolution_graph(&resolution)?; - let toml = lock.to_toml()?; - let resolution_hash = digest(&toml); + let resolution_hash = digest( + &Lock::from_resolution_graph(&resolution)? + .to_toml()? + .as_bytes(), + ); - // Hash the interpreter by hashing the sysconfig data. + // Hash the interpreter based on its path. // TODO(charlie): Come up with a robust hash for the interpreter. let interpreter_hash = digest(&interpreter.sys_executable());