diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index cea6d633f..6d01551e4 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -40,6 +40,7 @@ use uv_pypi_types::VerbatimParsedUrl; use uv_python::{Interpreter, PythonEnvironment}; use uv_static::EnvVars; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, SourceBuildTrait}; +use uv_virtualenv::VenvCreationPolicy; use uv_warnings::warn_user_once; use uv_workspace::WorkspaceCache; @@ -333,8 +334,7 @@ impl SourceBuild { interpreter.clone(), uv_virtualenv::Prompt::None, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, false, diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index fcb31c8b5..294f7e7d2 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -5,6 +5,7 @@ use uv_configuration::PreviewMode; use uv_dirs::user_executable_directory; use uv_pep440::Version; use uv_pep508::{InvalidNameError, PackageName}; +use uv_virtualenv::VenvCreationPolicy; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -285,8 +286,7 @@ impl InstalledTools { interpreter, uv_virtualenv::Prompt::None, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, false, diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index 088ada59f..e766383d8 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -6,6 +6,8 @@ use thiserror::Error; use uv_configuration::PreviewMode; use uv_python::{Interpreter, PythonEnvironment}; +pub use virtualenv::VenvCreationPolicy; + mod virtualenv; #[derive(Debug, Error)] @@ -50,8 +52,7 @@ pub fn create_venv( interpreter: Interpreter, prompt: Prompt, system_site_packages: bool, - allow_existing: bool, - clear: bool, + venv_creation_policy: VenvCreationPolicy, relocatable: bool, seed: bool, upgradeable: bool, @@ -63,8 +64,7 @@ pub fn create_venv( &interpreter, prompt, system_site_packages, - allow_existing, - clear, + venv_creation_policy, relocatable, seed, upgradeable, diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index a32b67d64..e80a6f77e 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -54,8 +54,7 @@ pub(crate) fn create( interpreter: &Interpreter, prompt: Prompt, system_site_packages: bool, - allow_existing: bool, - clear: bool, + venv_creation_policy: VenvCreationPolicy, relocatable: bool, seed: bool, upgradeable: bool, @@ -86,13 +85,15 @@ pub(crate) fn create( format!("File exists at `{}`", location.user_display()), ))); } else if metadata.is_dir() { - let confirmation_required = !clear && !allow_existing; - let confirmed_clear = confirmation_required && confirm_clear(location)?; + let confirmed_clear = venv_creation_policy == VenvCreationPolicy::FailIfNotEmpty + && confirm_clear(location)?; - if allow_existing { + if venv_creation_policy == VenvCreationPolicy::OverwriteFiles { debug!("Allowing existing directory due to `--allow-existing`"); - } else if clear || confirmed_clear { - if clear { + } else if venv_creation_policy == VenvCreationPolicy::RemoveDirectory + || confirmed_clear + { + if venv_creation_policy == VenvCreationPolicy::RemoveDirectory { debug!("Removing existing directory due to `--clear`"); } else { debug!("Removing existing directory"); @@ -491,6 +492,17 @@ fn confirm_clear(location: &Path) -> Result { } } +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub enum VenvCreationPolicy { + /// Do not create a virtual environment if a non-empty directory exists. + #[default] + FailIfNotEmpty, + /// Overwrite existing virtual environment files. + OverwriteFiles, + /// Remove existing directory. + RemoveDirectory, +} + #[derive(Debug, Copy, Clone)] enum WindowsExecutable { /// The `python.exe` executable (or `venvlauncher.exe` launcher shim). diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index e821c8693..b3fdf296d 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -8,6 +8,7 @@ use uv_configuration::{Concurrency, Constraints, PreviewMode}; use uv_distribution_types::{Name, Resolution}; use uv_fs::PythonExt; use uv_python::{Interpreter, PythonEnvironment, canonicalize_executable}; +use uv_virtualenv::VenvCreationPolicy; use crate::commands::pip::loggers::{InstallLogger, ResolveLogger}; use crate::commands::pip::operations::Modifications; @@ -96,8 +97,7 @@ impl CachedEnvironment { interpreter, uv_virtualenv::Prompt::None, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, true, false, false, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index b26dc1e56..b0b30de40 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -43,6 +43,7 @@ use uv_scripts::Pep723ItemRef; use uv_settings::PythonInstallMirrors; use uv_static::EnvVars; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; +use uv_virtualenv::VenvCreationPolicy; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::dependency_groups::DependencyGroupError; use uv_workspace::pyproject::PyProjectToml; @@ -1308,8 +1309,7 @@ impl ProjectEnvironment { interpreter, prompt, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, upgradeable, @@ -1348,8 +1348,7 @@ impl ProjectEnvironment { interpreter, prompt, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, upgradeable, @@ -1487,8 +1486,7 @@ impl ScriptEnvironment { interpreter, prompt, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, upgradeable, @@ -1524,8 +1522,7 @@ impl ScriptEnvironment { interpreter, prompt, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, upgradeable, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 080eb2792..881e40cd0 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -37,6 +37,7 @@ use uv_scripts::Pep723Item; use uv_settings::PythonInstallMirrors; use uv_shell::runnable::WindowsRunnable; use uv_static::EnvVars; +use uv_virtualenv::VenvCreationPolicy; use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceCache, WorkspaceError}; @@ -452,8 +453,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl interpreter, uv_virtualenv::Prompt::None, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, false, @@ -657,8 +657,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl interpreter, uv_virtualenv::Prompt::None, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, false, @@ -887,8 +886,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl interpreter, uv_virtualenv::Prompt::None, false, - false, - true, + VenvCreationPolicy::RemoveDirectory, false, false, false, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 26e168625..65afae63b 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -29,6 +29,7 @@ use uv_resolver::{ExcludeNewer, FlatIndex}; use uv_settings::PythonInstallMirrors; use uv_shell::{Shell, shlex_posix, shlex_windows}; use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy}; +use uv_virtualenv::VenvCreationPolicy; use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceError}; @@ -60,8 +61,7 @@ pub(crate) async fn venv( prompt: uv_virtualenv::Prompt, system_site_packages: bool, seed: bool, - allow_existing: bool, - clear: bool, + venv_creation_policy: VenvCreationPolicy, exclude_newer: Option, concurrency: Concurrency, no_config: bool, @@ -87,8 +87,7 @@ pub(crate) async fn venv( seed, python_preference, python_downloads, - allow_existing, - clear, + venv_creation_policy, exclude_newer, concurrency, no_config, @@ -145,8 +144,7 @@ async fn venv_impl( seed: bool, python_preference: PythonPreference, python_downloads: PythonDownloads, - allow_existing: bool, - clear: bool, + venv_creation_policy: VenvCreationPolicy, exclude_newer: Option, concurrency: Concurrency, no_config: bool, @@ -291,8 +289,7 @@ async fn venv_impl( interpreter, prompt, system_site_packages, - allow_existing, - clear, + venv_creation_policy, relocatable, seed, upgradeable, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 60a103f92..fed2fe4d5 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -41,6 +41,7 @@ use uv_requirements_txt::RequirementsTxtRequirement; use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script}; use uv_settings::{Combine, FilesystemOptions, Options}; use uv_static::EnvVars; +use uv_virtualenv::VenvCreationPolicy; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache}; @@ -1026,6 +1027,14 @@ async fn run(mut cli: Cli) -> Result { let python_request: Option = args.settings.python.as_deref().map(PythonRequest::parse); + let venv_creation_policy = if args.allow_existing { + VenvCreationPolicy::OverwriteFiles + } else if args.clear { + VenvCreationPolicy::RemoveDirectory + } else { + VenvCreationPolicy::default() + }; + commands::venv( &project_dir, args.path, @@ -1042,8 +1051,7 @@ async fn run(mut cli: Cli) -> Result { uv_virtualenv::Prompt::from_args(prompt), args.system_site_packages, args.seed, - args.allow_existing, - args.clear, + venv_creation_policy, args.settings.exclude_newer, globals.concurrency, cli.top_level.no_config,