mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Require --force
to replace existing virtual environments in uv venv
This commit is contained in:
parent
df35919d5a
commit
9e69276a6f
11 changed files with 101 additions and 19 deletions
|
@ -323,6 +323,7 @@ impl SourceBuild {
|
|||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?
|
||||
|
|
|
@ -2470,16 +2470,26 @@ pub struct VenvArgs {
|
|||
|
||||
/// Preserve any existing files or directories at the target path.
|
||||
///
|
||||
/// By default, `uv venv` will remove an existing virtual environment at the given path, and
|
||||
/// exit with an error if the path is non-empty but _not_ a virtual environment. The
|
||||
/// `--allow-existing` option will instead write to the given path, regardless of its contents,
|
||||
/// and without clearing it beforehand.
|
||||
/// By default, `uv venv` will exit with an error if the directory is non-empty but _not_ a
|
||||
/// virtual environment. The `--allow-existing` option will instead write to the given path,
|
||||
/// regardless of its contents, and without clearing it beforehand.
|
||||
///
|
||||
/// WARNING: This option can lead to unexpected behavior if the existing virtual environment and
|
||||
/// the newly-created virtual environment are linked to different Python interpreters.
|
||||
#[clap(long)]
|
||||
pub allow_existing: bool,
|
||||
|
||||
/// Replace an existing directory at the target path.
|
||||
///
|
||||
/// By default, `uv venv` will not replace an existing directory. Use `-f` to replace an
|
||||
/// existing virtual environment, or `-ff` to replace a directory that is not a virtual
|
||||
/// environment.
|
||||
///
|
||||
/// See `--allow-existing` to create a virtual environment in a directory, regardless of its
|
||||
/// contents, and without clearing it beforehand.
|
||||
#[clap(long, action = clap::ArgAction::Count)]
|
||||
pub force: u8,
|
||||
|
||||
/// The path to the virtual environment to create.
|
||||
///
|
||||
/// Default to `.venv` in the working directory.
|
||||
|
|
|
@ -281,6 +281,7 @@ impl InstalledTools {
|
|||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
|
|
@ -38,6 +38,29 @@ impl Prompt {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(zanieb): Consider folding `allow_existing` into this?
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum VenvForceMode {
|
||||
/// Do not replace an existing directory.
|
||||
Disabled,
|
||||
/// Replace an existing directory, if it is a virtual environment.
|
||||
ReplaceEnvironment,
|
||||
/// Replace an existing directory, regardless of contents.
|
||||
ReplaceAny,
|
||||
}
|
||||
|
||||
impl VenvForceMode {
|
||||
pub fn from_args(force: u8) -> Self {
|
||||
if force == 0 {
|
||||
VenvForceMode::Disabled
|
||||
} else if force == 1 {
|
||||
VenvForceMode::ReplaceEnvironment
|
||||
} else {
|
||||
VenvForceMode::ReplaceAny
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a virtualenv.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
pub fn create_venv(
|
||||
|
@ -46,6 +69,7 @@ pub fn create_venv(
|
|||
prompt: Prompt,
|
||||
system_site_packages: bool,
|
||||
allow_existing: bool,
|
||||
force: VenvForceMode,
|
||||
relocatable: bool,
|
||||
seed: bool,
|
||||
) -> Result<PythonEnvironment, Error> {
|
||||
|
@ -56,6 +80,7 @@ pub fn create_venv(
|
|||
prompt,
|
||||
system_site_packages,
|
||||
allow_existing,
|
||||
force,
|
||||
relocatable,
|
||||
seed,
|
||||
)?;
|
||||
|
|
|
@ -16,7 +16,7 @@ use uv_python::{Interpreter, VirtualEnvironment};
|
|||
use uv_shell::escape_posix_for_single_quotes;
|
||||
use uv_version::version;
|
||||
|
||||
use crate::{Error, Prompt};
|
||||
use crate::{Error, Prompt, VenvForceMode};
|
||||
|
||||
/// Activation scripts for the environment, with dependent paths templated out.
|
||||
const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[
|
||||
|
@ -51,6 +51,7 @@ pub(crate) fn create(
|
|||
prompt: Prompt,
|
||||
system_site_packages: bool,
|
||||
allow_existing: bool,
|
||||
force: VenvForceMode,
|
||||
relocatable: bool,
|
||||
seed: bool,
|
||||
) -> Result<VirtualEnvironment, Error> {
|
||||
|
@ -79,11 +80,35 @@ pub(crate) fn create(
|
|||
format!("File exists at `{}`", location.user_display()),
|
||||
)));
|
||||
} else if metadata.is_dir() {
|
||||
if allow_existing {
|
||||
debug!("Allowing existing directory");
|
||||
} else if location.join("pyvenv.cfg").is_file() {
|
||||
debug!("Removing existing directory");
|
||||
let is_virtualenv = location.join("pyvenv.cfg").is_file();
|
||||
let is_empty = !is_virtualenv
|
||||
&& location
|
||||
.read_dir()
|
||||
.is_ok_and(|mut dir| dir.next().is_none());
|
||||
let should_remove = if !is_empty {
|
||||
if allow_existing {
|
||||
debug!("Allowing existing directory due to `--allow-existing`");
|
||||
false
|
||||
} else {
|
||||
match force {
|
||||
VenvForceMode::Disabled => false,
|
||||
VenvForceMode::ReplaceEnvironment => {
|
||||
if is_virtualenv {
|
||||
debug!("Replacing existing virtual environment due to `-f`");
|
||||
}
|
||||
is_virtualenv
|
||||
}
|
||||
VenvForceMode::ReplaceAny => {
|
||||
debug!("Replacing existing directory due to `-ff`");
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if should_remove {
|
||||
// On Windows, if the current executable is in the directory, guard against
|
||||
// self-deletion.
|
||||
#[cfg(windows)]
|
||||
|
@ -97,18 +122,20 @@ pub(crate) fn create(
|
|||
|
||||
fs::remove_dir_all(location)?;
|
||||
fs::create_dir_all(location)?;
|
||||
} else if location
|
||||
.read_dir()
|
||||
.is_ok_and(|mut dir| dir.next().is_none())
|
||||
{
|
||||
debug!("Ignoring empty directory");
|
||||
} else {
|
||||
} else if !is_empty && !allow_existing {
|
||||
return Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::AlreadyExists,
|
||||
format!(
|
||||
"The directory `{}` exists, but it's not a virtual environment",
|
||||
location.user_display()
|
||||
),
|
||||
if is_virtualenv {
|
||||
format!(
|
||||
"The virtual environment `{}` already exists, use `-f` to replace it",
|
||||
location.user_display()
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"The directory `{}` already exists, use `-ff` to replace it",
|
||||
location.user_display()
|
||||
)
|
||||
},
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ impl CachedEnvironment {
|
|||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
|
|
|
@ -1239,6 +1239,7 @@ impl ProjectEnvironment {
|
|||
prompt,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
@ -1276,6 +1277,7 @@ impl ProjectEnvironment {
|
|||
prompt,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
@ -1405,6 +1407,7 @@ impl ScriptEnvironment {
|
|||
prompt,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
@ -1439,6 +1442,7 @@ impl ScriptEnvironment {
|
|||
prompt,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
|
|
@ -432,6 +432,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
@ -629,6 +630,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?
|
||||
|
@ -854,6 +856,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
uv_virtualenv::Prompt::None,
|
||||
false,
|
||||
false,
|
||||
uv_virtualenv::VenvForceMode::ReplaceAny,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
|
|
|
@ -29,6 +29,7 @@ use uv_resolver::{ExcludeNewer, FlatIndex};
|
|||
use uv_settings::PythonInstallMirrors;
|
||||
use uv_shell::{shlex_posix, shlex_windows, Shell};
|
||||
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy};
|
||||
use uv_virtualenv::VenvForceMode;
|
||||
use uv_warnings::warn_user;
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceError};
|
||||
|
||||
|
@ -58,6 +59,7 @@ pub(crate) async fn venv(
|
|||
prompt: uv_virtualenv::Prompt,
|
||||
system_site_packages: bool,
|
||||
seed: bool,
|
||||
force: VenvForceMode,
|
||||
allow_existing: bool,
|
||||
exclude_newer: Option<ExcludeNewer>,
|
||||
concurrency: Concurrency,
|
||||
|
@ -84,6 +86,7 @@ pub(crate) async fn venv(
|
|||
seed,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
force,
|
||||
allow_existing,
|
||||
exclude_newer,
|
||||
concurrency,
|
||||
|
@ -141,6 +144,7 @@ async fn venv_impl(
|
|||
seed: bool,
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
force: VenvForceMode,
|
||||
allow_existing: bool,
|
||||
exclude_newer: Option<ExcludeNewer>,
|
||||
concurrency: Concurrency,
|
||||
|
@ -271,6 +275,7 @@ async fn venv_impl(
|
|||
prompt,
|
||||
system_site_packages,
|
||||
allow_existing,
|
||||
force,
|
||||
relocatable,
|
||||
seed,
|
||||
)
|
||||
|
|
|
@ -982,6 +982,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
uv_virtualenv::Prompt::from_args(prompt),
|
||||
args.system_site_packages,
|
||||
args.seed,
|
||||
args.force,
|
||||
args.allow_existing,
|
||||
args.settings.exclude_newer,
|
||||
globals.concurrency,
|
||||
|
|
|
@ -43,6 +43,7 @@ use uv_settings::{
|
|||
};
|
||||
use uv_static::EnvVars;
|
||||
use uv_torch::TorchMode;
|
||||
use uv_virtualenv::VenvForceMode;
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::pyproject::DependencyType;
|
||||
|
||||
|
@ -2390,6 +2391,7 @@ impl BuildSettings {
|
|||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct VenvSettings {
|
||||
pub(crate) seed: bool,
|
||||
pub(crate) force: VenvForceMode,
|
||||
pub(crate) allow_existing: bool,
|
||||
pub(crate) path: Option<PathBuf>,
|
||||
pub(crate) prompt: Option<String>,
|
||||
|
@ -2408,6 +2410,7 @@ impl VenvSettings {
|
|||
system,
|
||||
no_system,
|
||||
seed,
|
||||
force,
|
||||
allow_existing,
|
||||
path,
|
||||
prompt,
|
||||
|
@ -2425,6 +2428,7 @@ impl VenvSettings {
|
|||
|
||||
Self {
|
||||
seed,
|
||||
force: VenvForceMode::from_args(force),
|
||||
allow_existing,
|
||||
path,
|
||||
prompt,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue