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 thiserror::Error;
use tracing::debug;
use tracing::{debug, warn};
use install_wheel_rs::read_record_file;
@ -178,6 +178,9 @@ impl InstalledTools {
}
/// 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(
&self,
name: &PackageName,
@ -186,14 +189,25 @@ impl InstalledTools {
let _lock = self.acquire_lock();
let environment_path = self.root.join(name.to_string());
if environment_path.is_dir() {
debug!(
"Using existing environment for tool `{name}` at `{}`.",
environment_path.user_display()
);
Ok(Some(PythonEnvironment::from_root(environment_path, cache)?))
} else {
Ok(None)
match PythonEnvironment::from_root(&environment_path, cache) {
Ok(venv) => {
debug!(
"Using existing environment for tool `{name}`: {}",
environment_path.user_display()
);
Ok(Some(venv))
}
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) {
Ok(()) => {
debug!(
"Removed existing environment for tool `{name}` at `{}`.",
"Removed existing environment for tool `{name}`: {}",
environment_path.user_display()
);
}
@ -219,7 +233,7 @@ impl InstalledTools {
}
debug!(
"Creating environment for tool `{name}` at `{}`.",
"Creating environment for tool `{name}`: {}",
environment_path.user_display()
);

View file

@ -23,6 +23,7 @@ use uv_python::{
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
use uv_resolver::{FlatIndex, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::warn_user;
use crate::commands::pip::operations::Modifications;
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
@ -174,6 +175,12 @@ impl FoundInterpreter {
}
}
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()),
};

View file

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

View file

@ -1393,7 +1393,7 @@ fn tool_install_python_request() {
----- stderr -----
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]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]