Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow --force to overwrite existing virtualenv #2548

Merged
merged 1 commit into from
May 1, 2024
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: 1 addition & 0 deletions crates/uv-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ impl SourceBuild {
interpreter.clone(),
uv_virtualenv::Prompt::None,
false,
false,
)?,
BuildIsolation::Shared(venv) => venv.clone(),
};
Expand Down
25 changes: 16 additions & 9 deletions crates/uv-fs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,26 @@ pub fn replace_symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io:
)
}

/// Create a symlink from `src` to `dst`, replacing any existing symlink.
/// Create a symlink from `src` to `dst`, replacing any existing symlink if necessary.
#[cfg(unix)]
pub fn replace_symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
// Create a symlink to the directory store.
let temp_dir =
tempfile::tempdir_in(dst.as_ref().parent().expect("Cache entry to have parent"))?;
let temp_file = temp_dir.path().join("link");
std::os::unix::fs::symlink(src, &temp_file)?;
// Attempt to create the symlink directly.
match std::os::unix::fs::symlink(src.as_ref(), dst.as_ref()) {
Ok(()) => Ok(()),
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
// Create a symlink to the directory store, using a temporary file to ensure atomicity.
let temp_dir =
tempfile::tempdir_in(dst.as_ref().parent().expect("Cache entry to have parent"))?;
let temp_file = temp_dir.path().join("link");
std::os::unix::fs::symlink(src, &temp_file)?;

// Move the symlink into the wheel cache.
fs_err::rename(&temp_file, dst.as_ref())?;
// Move the symlink into the wheel cache.
fs_err::rename(&temp_file, dst.as_ref())?;

Ok(())
Ok(())
}
Err(err) => Err(err),
}
}

/// Write `data` to `path` atomically using a temporary file and atomic rename.
Expand Down
15 changes: 8 additions & 7 deletions crates/uv-virtualenv/src/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub fn create_bare_venv(
interpreter: &Interpreter,
prompt: Prompt,
system_site_packages: bool,
force: bool,
) -> Result<Virtualenv, Error> {
// Determine the base Python executable; that is, the Python executable that should be
// considered the "base" for the virtual environment. This is typically the Python executable
Expand Down Expand Up @@ -88,7 +89,9 @@ pub fn create_bare_venv(
format!("File exists at `{}`", location.user_display()),
)));
} else if metadata.is_dir() {
if location.join("pyvenv.cfg").is_file() {
if force {
info!("Overwriting existing directory");
} else if location.join("pyvenv.cfg").is_file() {
info!("Removing existing directory");
fs::remove_dir_all(location)?;
fs::create_dir_all(location)?;
Expand Down Expand Up @@ -147,19 +150,17 @@ pub fn create_bare_venv(
})?;

// Different names for the python interpreter
fs::create_dir(&scripts)?;
fs::create_dir_all(&scripts)?;
let executable = scripts.join(format!("python{EXE_SUFFIX}"));

#[cfg(unix)]
{
use fs_err::os::unix::fs::symlink;

symlink(&base_python, &executable)?;
symlink(
uv_fs::replace_symlink(&base_python, &executable)?;
uv_fs::replace_symlink(
"python",
scripts.join(format!("python{}", interpreter.python_major())),
)?;
symlink(
uv_fs::replace_symlink(
"python",
scripts.join(format!(
"python{}.{}",
Expand Down
3 changes: 2 additions & 1 deletion crates/uv-virtualenv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ pub fn create_venv(
interpreter: Interpreter,
prompt: Prompt,
system_site_packages: bool,
force: bool,
) -> Result<PythonEnvironment, Error> {
// Create the virtualenv at the given location.
let virtualenv = create_bare_venv(location, &interpreter, prompt, system_site_packages)?;
let virtualenv = create_bare_venv(location, &interpreter, prompt, system_site_packages, force)?;

// Create the corresponding `PythonEnvironment`.
let interpreter = interpreter.with_virtualenv(virtualenv);
Expand Down
1 change: 1 addition & 0 deletions crates/uv-virtualenv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn run() -> Result<(), uv_virtualenv::Error> {
&interpreter,
Prompt::from_args(cli.prompt),
cli.system_site_packages,
false,
)?;
Ok(())
}
Expand Down
9 changes: 9 additions & 0 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,15 @@ pub(crate) struct VenvArgs {
#[arg(long)]
pub(crate) seed: bool,

/// Overwrite the directory at the specified path when creating the virtual environment.
///
/// 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 `--force`
/// option will instead write to the given path, regardless of its contents, and without
/// clearing it beforehand.
#[clap(long)]
pub(crate) force: bool,

/// The path to the virtual environment to create.
#[arg(default_value = ".venv")]
pub(crate) name: PathBuf,
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ async fn environment_for_run(
python_env.into_interpreter(),
uv_virtualenv::Prompt::None,
false,
false,
)?;

// Determine the tags, markers, and interpreter to use for resolution.
Expand Down
13 changes: 10 additions & 3 deletions crates/uv/src/commands/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ use crate::printer::Printer;
use crate::shell::Shell;

/// Create a virtual environment.
#[allow(clippy::unnecessary_wraps, clippy::too_many_arguments)]
#[allow(
clippy::unnecessary_wraps,
clippy::too_many_arguments,
clippy::fn_params_excessive_bools
)]
pub(crate) async fn venv(
path: &Path,
python_request: Option<&str>,
Expand All @@ -41,6 +45,7 @@ pub(crate) async fn venv(
system_site_packages: bool,
connectivity: Connectivity,
seed: bool,
force: bool,
exclude_newer: Option<ExcludeNewer>,
native_tls: bool,
cache: &Cache,
Expand All @@ -57,6 +62,7 @@ pub(crate) async fn venv(
system_site_packages,
connectivity,
seed,
force,
exclude_newer,
native_tls,
cache,
Expand Down Expand Up @@ -92,7 +98,7 @@ enum VenvError {
}

/// Create a virtual environment.
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
async fn venv_impl(
path: &Path,
python_request: Option<&str>,
Expand All @@ -104,6 +110,7 @@ async fn venv_impl(
system_site_packages: bool,
connectivity: Connectivity,
seed: bool,
force: bool,
exclude_newer: Option<ExcludeNewer>,
native_tls: bool,
cache: &Cache,
Expand Down Expand Up @@ -140,7 +147,7 @@ async fn venv_impl(
.into_diagnostic()?;

// Create the virtual environment.
let venv = uv_virtualenv::create_venv(path, interpreter, prompt, system_site_packages)
let venv = uv_virtualenv::create_venv(path, interpreter, prompt, system_site_packages, force)
.map_err(VenvError::Creation)?;

// Install seed packages.
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ async fn run() -> Result<ExitStatus> {
args.system_site_packages,
args.shared.connectivity,
args.seed,
args.force,
args.shared.exclude_newer,
globals.native_tls,
&cache,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ impl PipCheckSettings {
pub(crate) struct VenvSettings {
// CLI-only settings.
pub(crate) seed: bool,
pub(crate) force: bool,
pub(crate) name: PathBuf,
pub(crate) prompt: Option<String>,
pub(crate) system_site_packages: bool,
Expand All @@ -752,6 +753,7 @@ impl VenvSettings {
system,
no_system,
seed,
force,
name,
prompt,
system_site_packages,
Expand All @@ -770,6 +772,7 @@ impl VenvSettings {
Self {
// CLI-only settings.
seed,
force,
name,
prompt,
system_site_packages,
Expand Down
Loading
Loading