Reinstall and recreate environments when interpreter is removed (#4935)

## Summary

We now recreate the environment in `uv sync`, `uv tool install`, and `uv
tool run` if the underlying interpreter has been removed.

Closes https://github.com/astral-sh/uv/issues/4933.
This commit is contained in:
Charlie Marsh 2024-07-09 12:25:23 -07:00 committed by GitHub
parent 53db63f6dd
commit bb703b8343
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 34 additions and 14 deletions

View file

@ -11,7 +11,7 @@ use std::str::FromStr;
use fs_err::File; use fs_err::File;
use thiserror::Error; use thiserror::Error;
use tracing::debug; use tracing::{debug, warn};
use install_wheel_rs::read_record_file; use install_wheel_rs::read_record_file;
@ -178,6 +178,9 @@ impl InstalledTools {
} }
/// Return the [`PythonEnvironment`] for a given tool, if it exists. /// Return the [`PythonEnvironment`] for a given tool, if it exists.
///
/// Returns `Ok(None)` if the environment does not exist or is linked to a non-existent
/// interpreter.
pub fn get_environment( pub fn get_environment(
&self, &self,
name: &PackageName, name: &PackageName,
@ -186,14 +189,25 @@ impl InstalledTools {
let _lock = self.acquire_lock(); let _lock = self.acquire_lock();
let environment_path = self.root.join(name.to_string()); let environment_path = self.root.join(name.to_string());
if environment_path.is_dir() { match PythonEnvironment::from_root(&environment_path, cache) {
debug!( Ok(venv) => {
"Using existing environment for tool `{name}` at `{}`.", debug!(
environment_path.user_display() "Using existing environment for tool `{name}`: {}",
); environment_path.user_display()
Ok(Some(PythonEnvironment::from_root(environment_path, cache)?)) );
} else { Ok(Some(venv))
Ok(None) }
Err(uv_python::Error::MissingEnvironment(_)) => Ok(None),
Err(uv_python::Error::Query(uv_python::InterpreterError::NotFound(
interpreter_path,
))) => {
warn!(
"Ignoring existing virtual environment linked to non-existent Python interpreter: {}",
interpreter_path.user_display()
);
Ok(None)
}
Err(err) => Err(err.into()),
} }
} }
@ -210,7 +224,7 @@ impl InstalledTools {
match fs_err::remove_dir_all(&environment_path) { match fs_err::remove_dir_all(&environment_path) {
Ok(()) => { Ok(()) => {
debug!( debug!(
"Removed existing environment for tool `{name}` at `{}`.", "Removed existing environment for tool `{name}`: {}",
environment_path.user_display() environment_path.user_display()
); );
} }
@ -219,7 +233,7 @@ impl InstalledTools {
} }
debug!( debug!(
"Creating environment for tool `{name}` at `{}`.", "Creating environment for tool `{name}`: {}",
environment_path.user_display() environment_path.user_display()
); );

View file

@ -23,6 +23,7 @@ use uv_python::{
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification}; use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
use uv_resolver::{FlatIndex, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph}; use uv_resolver::{FlatIndex, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::warn_user;
use crate::commands::pip::operations::Modifications; use crate::commands::pip::operations::Modifications;
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter}; use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
@ -174,6 +175,12 @@ impl FoundInterpreter {
} }
} }
Err(uv_python::Error::MissingEnvironment(_)) => {} Err(uv_python::Error::MissingEnvironment(_)) => {}
Err(uv_python::Error::Query(uv_python::InterpreterError::NotFound(path))) => {
warn_user!(
"Ignoring existing virtual environment linked to non-existent Python interpreter: {}",
path.user_display().cyan()
);
}
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };

View file

@ -166,9 +166,8 @@ pub(crate) async fn install(
} else { } else {
let _ = writeln!( let _ = writeln!(
printer.stderr(), printer.stderr(),
"Existing environment for `{}` does not satisfy the requested Python interpreter (`{}`)", "Existing environment for `{}` does not satisfy the requested Python interpreter",
from.name, from.name,
python_request
); );
false false
} }

View file

@ -1393,7 +1393,7 @@ fn tool_install_python_request() {
----- stderr ----- ----- stderr -----
warning: `uv tool install` is experimental and may change without warning. warning: `uv tool install` is experimental and may change without warning.
Existing environment for `black` does not satisfy the requested Python interpreter (`Python 3.11`) Existing environment for `black` does not satisfy the requested Python interpreter
Resolved [N] packages in [TIME] Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME] Prepared [N] packages in [TIME]
Installed [N] packages in [TIME] Installed [N] packages in [TIME]