Require --clear to remove virtual environments (#17757)

This has been in warnings since 0.8
This commit is contained in:
Zanie Blue
2026-02-01 07:40:44 -06:00
parent bd08ee3c4e
commit 3632202197
9 changed files with 99 additions and 61 deletions
Generated
-1
View File
@@ -7194,7 +7194,6 @@ dependencies = [
"uv-python",
"uv-shell",
"uv-version",
"uv-warnings",
]
[[package]]
-1
View File
@@ -26,7 +26,6 @@ uv-pypi-types = { workspace = true }
uv-python = { workspace = true }
uv-shell = { workspace = true }
uv-version = { workspace = true }
uv-warnings = { workspace = true }
console = { workspace = true }
fs-err = { workspace = true }
+8 -1
View File
@@ -1,5 +1,5 @@
use std::io;
use std::path::Path;
use std::path::{Path, PathBuf};
use thiserror::Error;
@@ -20,6 +20,13 @@ pub enum Error {
NotFound(String),
#[error(transparent)]
Python(#[from] uv_python::managed::Error),
#[error("A {name} already exists at `{}`. Use `--clear` to replace it", path.display())]
Exists {
/// The type of environment (e.g., "virtual environment").
name: &'static str,
/// The path to the existing environment.
path: PathBuf,
},
}
/// The value to use for the shell prompt when inside a virtual environment.
+6 -9
View File
@@ -11,6 +11,7 @@ use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::{debug, trace};
use crate::{Error, Prompt};
use uv_fs::{CWD, Simplified, cachedir};
use uv_platform_tags::Os;
use uv_preview::Preview;
@@ -19,9 +20,6 @@ use uv_python::managed::{PythonMinorVersionLink, create_link_to_executable};
use uv_python::{Interpreter, VirtualEnvironment};
use uv_shell::escape_posix_for_single_quotes;
use uv_version::version;
use uv_warnings::warn_user_once;
use crate::{Error, Prompt};
/// Activation scripts for the environment, with dependent paths templated out.
const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[
@@ -164,13 +162,12 @@ pub(crate) fn create(
fs_err::create_dir_all(&location)?;
}
Some(false) => return err,
// When we don't have a TTY, warn that the behavior will change in the future
// When we don't have a TTY, require `--clear` explicitly.
None => {
warn_user_once!(
"A {name} already exists at `{}`. In the future, uv will require `{}` to replace it",
location.user_display(),
"--clear".green(),
);
return Err(Error::Exists {
name,
path: location.to_path_buf(),
});
}
}
}
+1 -1
View File
@@ -2161,7 +2161,7 @@ fn venv_included_in_sdist() -> Result<()> {
.child("pyproject.toml")
.write_str(pyproject_toml)?;
context.venv().assert().success();
context.venv().arg("--clear").assert().success();
// context.filters()
uv_snapshot!(context.filters(), context.build(), @"
+44 -9
View File
@@ -13839,7 +13839,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> {
// (compatible host, incompatible host) x (compatible target, incompatible target)
// A Python 3.13 host with a 3.13 implicit target works.
context.venv().arg("-p").arg("3.13").assert().success();
context
.venv()
.arg("--clear")
.arg("-p")
.arg("3.13")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install().arg("./child"), @"
success: true
exit_code: 0
@@ -13853,7 +13859,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> {
");
// A Python 3.13 host with a 3.12 explicit target fails.
context.venv().arg("-p").arg("3.13").assert().success();
context
.venv()
.arg("--clear")
.arg("-p")
.arg("3.13")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("./child"), @"
success: false
exit_code: 1
@@ -13866,8 +13878,14 @@ fn build_backend_wrong_wheel_platform() -> Result<()> {
");
// A python 3.12 host with a 3.13 explicit target works.
context.venv().arg("-p").arg("3.13").assert().success();
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.13").arg("./child"), @"
context
.venv()
.arg("--clear")
.arg("-p")
.arg("3.13")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.13").arg("./child"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -13875,13 +13893,18 @@ fn build_backend_wrong_wheel_platform() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ py313==0.1.0 (from file://[TEMP_DIR]/child)
+ py313==0.1.0 (from file://[TEMP_DIR]/child)
");
// A Python 3.13 host with a 3.12 explicit target fails.
context.venv().arg("-p").arg("3.13").assert().success();
context
.venv()
.arg("--clear")
.arg("-p")
.arg("3.13")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("./child"), @"
success: false
exit_code: 1
@@ -13916,7 +13939,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> {
.touch()?;
// A build host of 3.13 works.
context.venv().arg("-p").arg("3.13").assert().success();
context
.venv()
.arg("--clear")
.arg("-p")
.arg("3.13")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("."), @"
success: true
exit_code: 0
@@ -13930,7 +13959,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> {
");
// A build host of 3.12 fails.
context.venv().arg("-p").arg("3.12").assert().success();
context
.venv()
.arg("--clear")
.arg("-p")
.arg("3.12")
.assert()
.success();
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("."), @"
success: false
exit_code: 1
+1 -1
View File
@@ -197,7 +197,7 @@ fn find_uv_bin_base_prefix() {
"
);
context.venv().assert().success();
context.venv().arg("--clear").assert().success();
// Mutate `base_prefix` to simulate lookup in a system Python installation
uv_snapshot!(context.filters(), context.python_command()
+6 -1
View File
@@ -1478,7 +1478,12 @@ fn run_with_overlay_interpreter() -> Result<()> {
");
// Switch to a relocatable virtual environment.
context.venv().arg("--relocatable").assert().success();
context
.venv()
.arg("--allow-existing")
.arg("--relocatable")
.assert()
.success();
// Cleanup previous shutil
fs_err::remove_file(context.temp_dir.child("main"))?;
+33 -37
View File
@@ -36,16 +36,16 @@ fn create_venv() {
uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str())
.arg("--python")
.arg("3.12"), @"
success: true
exit_code: 0
.arg("3.12"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
error: Failed to create virtual environment
Caused by: A virtual environment already exists at `[VENV]/`. Use `--clear` to replace it
"
);
@@ -221,7 +221,7 @@ fn virtual_empty() -> Result<()> {
wow = "someconfig"
"#})?;
uv_snapshot!(context.filters(), context.venv(), @"
uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -229,7 +229,6 @@ fn virtual_empty() -> Result<()> {
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
");
@@ -250,7 +249,7 @@ fn virtual_dependency_group() -> Result<()> {
dev = ["sniffio"]
"#})?;
uv_snapshot!(context.filters(), context.venv(), @"
uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -258,7 +257,6 @@ fn virtual_dependency_group() -> Result<()> {
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
");
@@ -1511,7 +1509,19 @@ fn venv_python_preference() {
Activate with: source .venv/[BIN]/activate
");
uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @"
uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
Creating virtual environment at: .venv
error: Failed to create virtual environment
Caused by: A virtual environment already exists at `.venv`. Use `--clear` to replace it
");
uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--no-managed-python"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1519,23 +1529,22 @@ fn venv_python_preference() {
----- stderr -----
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
");
uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @"
success: true
exit_code: 0
uv_snapshot!(context.filters(), context.venv(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Using CPython 3.11.[X] interpreter at: [PYTHON-3.11]
Using CPython 3.12.[X]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
error: Failed to create virtual environment
Caused by: A virtual environment already exists at `.venv`. Use `--clear` to replace it
");
uv_snapshot!(context.filters(), context.venv(), @"
uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--managed-python"), @"
success: true
exit_code: 0
----- stdout -----
@@ -1543,19 +1552,6 @@ fn venv_python_preference() {
----- stderr -----
Using CPython 3.12.[X]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
");
uv_snapshot!(context.filters(), context.venv().arg("--managed-python"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
");
}
@@ -1653,11 +1649,12 @@ fn create_venv_symlink_recreate_preservation() -> Result<()> {
// Verify symlink is preserved after first creation
assert!(symlink_path.path().is_symlink());
// Run uv venv again WITHOUT --clear to test recreation behavior
// Run uv venv again with --clear to test symlink preservation during recreation
uv_snapshot!(context.filters(), context.venv()
.arg(symlink_path.as_os_str())
.arg("--clear")
.arg("--python")
.arg("3.12"), @"
.arg("3.12"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -1665,7 +1662,6 @@ fn create_venv_symlink_recreate_preservation() -> Result<()> {
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
"
);
@@ -1717,11 +1713,12 @@ fn create_venv_nested_symlink_preservation() -> Result<()> {
assert!(symlink_path.path().is_symlink());
assert!(intermediate_link.path().is_symlink());
// Run uv venv again to test nested symlink preservation during recreation
// Run uv venv again with --clear to test nested symlink preservation during recreation
uv_snapshot!(context.filters(), context.venv()
.arg(symlink_path.as_os_str())
.arg("--clear")
.arg("--python")
.arg("3.12"), @"
.arg("3.12"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -1729,7 +1726,6 @@ fn create_venv_nested_symlink_preservation() -> Result<()> {
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it
Activate with: source .venv/[BIN]/activate
"
);