Skip to content

Commit 58fdecd

Browse files
committed
Require uv venv --clear before clearing an existing directory
1 parent efc3612 commit 58fdecd

File tree

23 files changed

+143
-68
lines changed

23 files changed

+143
-68
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-build-frontend/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ impl SourceBuild {
334334
uv_virtualenv::Prompt::None,
335335
false,
336336
false,
337+
true,
337338
false,
338339
false,
339340
false,

crates/uv-cli/src/compat.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,6 @@ enum Resolver {
266266
/// These represent a subset of the `virtualenv` interface that uv supports by default.
267267
#[derive(Args)]
268268
pub struct VenvCompatArgs {
269-
#[clap(long, hide = true)]
270-
clear: bool,
271-
272269
#[clap(long, hide = true)]
273270
no_seed: bool,
274271

@@ -289,12 +286,6 @@ impl CompatArgs for VenvCompatArgs {
289286
/// behavior. If an argument is passed that does _not_ match uv's behavior, this method will
290287
/// return an error.
291288
fn validate(&self) -> Result<()> {
292-
if self.clear {
293-
warn_user!(
294-
"virtualenv's `--clear` has no effect (uv always clears the virtual environment)"
295-
);
296-
}
297-
298289
if self.no_seed {
299290
warn_user!(
300291
"virtualenv's `--no-seed` has no effect (uv omits seed packages by default)"

crates/uv-cli/src/lib.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2562,16 +2562,23 @@ pub struct VenvArgs {
25622562
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_SEED)]
25632563
pub seed: bool,
25642564

2565+
/// Remove any existing files or directories at the target path.
2566+
///
2567+
/// By default, `uv venv` will exit with an error if the given path is non-empty. The
2568+
/// `--clear` option will instead clear a non-empty path before creating a new virtual
2569+
/// environment.
2570+
#[clap(long, short, conflicts_with = "allow_existing", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_CLEAR)]
2571+
pub clear: bool,
2572+
25652573
/// Preserve any existing files or directories at the target path.
25662574
///
2567-
/// By default, `uv venv` will remove an existing virtual environment at the given path, and
2568-
/// exit with an error if the path is non-empty but _not_ a virtual environment. The
2575+
/// By default, `uv venv` will exit with an error if the given path is non-empty. The
25692576
/// `--allow-existing` option will instead write to the given path, regardless of its contents,
25702577
/// and without clearing it beforehand.
25712578
///
25722579
/// WARNING: This option can lead to unexpected behavior if the existing virtual environment and
25732580
/// the newly-created virtual environment are linked to different Python interpreters.
2574-
#[clap(long)]
2581+
#[clap(long, conflicts_with = "clear")]
25752582
pub allow_existing: bool,
25762583

25772584
/// The path to the virtual environment to create.

crates/uv-static/src/env_vars.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ impl EnvVars {
292292
/// Distributions can be read from a local directory by using the `file://` URL scheme.
293293
pub const UV_PYPY_INSTALL_MIRROR: &'static str = "UV_PYPY_INSTALL_MIRROR";
294294

295+
/// Remove any existing files or directories at the target path.
296+
///
297+
/// By default, `uv venv` will exit with an error if the given path is non-empty. The
298+
/// `--clear` option will instead clear a non-empty path before creating a new virtual
299+
/// environment.
300+
pub const UV_VENV_CLEAR: &'static str = "UV_VENV_CLEAR";
301+
295302
/// Install seed packages (one or more of: `pip`, `setuptools`, and `wheel`) into the virtual environment
296303
/// created by `uv venv`.
297304
///

crates/uv-tool/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ impl InstalledTools {
286286
uv_virtualenv::Prompt::None,
287287
false,
288288
false,
289+
true,
289290
false,
290291
false,
291292
false,

crates/uv-virtualenv/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ workspace = true
2121

2222
[dependencies]
2323
uv-configuration = { workspace = true }
24+
uv-console = { workspace = true }
2425
uv-fs = { workspace = true }
2526
uv-pypi-types = { workspace = true }
2627
uv-python = { workspace = true }
2728
uv-shell = { workspace = true }
2829
uv-version = { workspace = true }
2930

31+
console = { workspace = true }
3032
fs-err = { workspace = true }
3133
itertools = { workspace = true }
34+
owo-colors = { workspace = true }
3235
pathdiff = { workspace = true }
3336
thiserror = { workspace = true }
3437
tracing = { workspace = true }

crates/uv-virtualenv/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub fn create_venv(
5151
prompt: Prompt,
5252
system_site_packages: bool,
5353
allow_existing: bool,
54+
clear: bool,
5455
relocatable: bool,
5556
seed: bool,
5657
upgradeable: bool,
@@ -63,6 +64,7 @@ pub fn create_venv(
6364
prompt,
6465
system_site_packages,
6566
allow_existing,
67+
clear,
6668
relocatable,
6769
seed,
6870
upgradeable,

crates/uv-virtualenv/src/virtualenv.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use std::io;
55
use std::io::{BufWriter, Write};
66
use std::path::Path;
77

8+
use console::Term;
89
use fs_err as fs;
910
use fs_err::File;
1011
use itertools::Itertools;
12+
use owo_colors::OwoColorize;
1113
use tracing::debug;
1214

1315
use uv_configuration::PreviewMode;
@@ -53,6 +55,7 @@ pub(crate) fn create(
5355
prompt: Prompt,
5456
system_site_packages: bool,
5557
allow_existing: bool,
58+
clear: bool,
5659
relocatable: bool,
5760
seed: bool,
5861
upgradeable: bool,
@@ -83,10 +86,17 @@ pub(crate) fn create(
8386
format!("File exists at `{}`", location.user_display()),
8487
)));
8588
} else if metadata.is_dir() {
89+
let confirmation_required = !clear && !allow_existing;
90+
let confirmed_clear = confirmation_required && confirm_clear(location)?;
91+
8692
if allow_existing {
87-
debug!("Allowing existing directory");
88-
} else if uv_fs::is_virtualenv_base(location) {
89-
debug!("Removing existing directory");
93+
debug!("Allowing existing directory due to `--allow-existing`");
94+
} else if clear || confirmed_clear {
95+
if clear {
96+
debug!("Removing existing directory due to `--clear`");
97+
} else {
98+
debug!("Removing existing directory");
99+
}
90100

91101
// On Windows, if the current executable is in the directory, guard against
92102
// self-deletion.
@@ -110,8 +120,12 @@ pub(crate) fn create(
110120
return Err(Error::Io(io::Error::new(
111121
io::ErrorKind::AlreadyExists,
112122
format!(
113-
"The directory `{}` exists, but it's not a virtual environment",
114-
location.user_display()
123+
"The directory `{}` exists. \n\n{}{} Use `{}` to remove the directory first or `{}` to write to the directory without clearing",
124+
location.user_display(),
125+
"hint".bold().cyan(),
126+
":".bold(),
127+
"--clear".green(),
128+
"--allow-existing".green(),
115129
),
116130
)));
117131
}
@@ -464,6 +478,19 @@ pub(crate) fn create(
464478
})
465479
}
466480

481+
fn confirm_clear(location: &Path) -> Result<bool, io::Error> {
482+
let term = Term::stderr();
483+
if term.is_term() {
484+
let prompt = format!(
485+
"The directory `{}` exists. Did you mean to clear its contents (`--clear`)?",
486+
location.user_display(),
487+
);
488+
uv_console::confirm(&prompt, &term, true)
489+
} else {
490+
Ok(false)
491+
}
492+
}
493+
467494
#[derive(Debug, Copy, Clone)]
468495
enum WindowsExecutable {
469496
/// The `python.exe` executable (or `venvlauncher.exe` launcher shim).

crates/uv/src/commands/project/environment.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ impl CachedEnvironment {
9797
false,
9898
false,
9999
true,
100+
true,
100101
false,
101102
false,
102103
preview,

0 commit comments

Comments
 (0)