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