Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/uv-virtualenv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
9 changes: 8 additions & 1 deletion crates/uv-virtualenv/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::io;
use std::path::Path;
use std::path::{Path, PathBuf};

use thiserror::Error;

Expand All @@ -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.
Expand Down
15 changes: 6 additions & 9 deletions crates/uv-virtualenv/src/virtualenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)] = &[
Expand Down Expand Up @@ -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(),
});
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/tests/it/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(), @"
Expand Down
53 changes: 44 additions & 9 deletions crates/uv/tests/it/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13859,7 +13859,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
Expand All @@ -13873,7 +13879,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
Expand All @@ -13886,22 +13898,33 @@ 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 -----

----- 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
Expand Down Expand Up @@ -13936,7 +13959,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
Expand All @@ -13950,7 +13979,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
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/tests/it/python_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 6 additions & 1 deletion crates/uv/tests/it/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))?;
Expand Down
54 changes: 25 additions & 29 deletions crates/uv/tests/it/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
"
);

Expand Down Expand Up @@ -221,15 +221,14 @@ 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 -----

----- 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
");

Expand All @@ -250,15 +249,14 @@ 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 -----

----- 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
");

Expand Down Expand Up @@ -1457,51 +1455,49 @@ fn venv_python_preference() {
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().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
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().arg("--no-managed-python"), @"
uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--no-managed-python"), @"
success: true
exit_code: 0
----- stdout -----

----- 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(), @"
success: true
exit_code: 0
uv_snapshot!(context.filters(), context.venv(), @r"
success: false
exit_code: 2
----- 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
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("--managed-python"), @"
uv_snapshot!(context.filters(), context.venv().arg("--clear").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
");
}
Expand Down Expand Up @@ -1599,19 +1595,19 @@ 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 -----

----- 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
"
);
Expand Down Expand Up @@ -1663,19 +1659,19 @@ 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 -----

----- 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
"
);
Expand Down
Loading