Skip to content

Commit

Permalink
Allow --force to overwrite existing virtualenv
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 19, 2024
1 parent acbee16 commit 208ee71
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 21 deletions.
1 change: 1 addition & 0 deletions crates/uv-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ impl SourceBuild {
uv_virtualenv::Prompt::None,
false,
Vec::new(),
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 @@ -85,19 +85,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 @@ -48,6 +48,7 @@ pub fn create_bare_venv(
prompt: Prompt,
system_site_packages: bool,
extra_cfg: Vec<(String, String)>,
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 @@ -89,7 +90,9 @@ pub fn create_bare_venv(
format!("File exists at `{}`", location.simplified_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 @@ -140,19 +143,17 @@ pub fn create_bare_venv(
fs::write(location.join(".gitignore"), "*")?;

// 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
2 changes: 2 additions & 0 deletions crates/uv-virtualenv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub fn create_venv(
prompt: Prompt,
system_site_packages: bool,
extra_cfg: Vec<(String, String)>,
force: bool,
) -> Result<PythonEnvironment, Error> {
// Create the virtualenv at the given location.
let virtualenv = create_bare_venv(
Expand All @@ -60,6 +61,7 @@ pub fn create_venv(
prompt,
system_site_packages,
extra_cfg,
force,
)?;

// Create the corresponding `PythonEnvironment`.
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 @@ -47,6 +47,7 @@ fn run() -> Result<(), uv_virtualenv::Error> {
Prompt::from_args(cli.prompt),
cli.system_site_packages,
Vec::new(),
false,
)?;
Ok(())
}
Expand Down
23 changes: 18 additions & 5 deletions crates/uv/src/commands/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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 @@ -38,6 +42,7 @@ pub(crate) async fn venv(
system_site_packages: bool,
connectivity: Connectivity,
seed: bool,
force: bool,
exclude_newer: Option<DateTime<Utc>>,
native_tls: bool,
cache: &Cache,
Expand All @@ -52,6 +57,7 @@ pub(crate) async fn venv(
system_site_packages,
connectivity,
seed,
force,
exclude_newer,
native_tls,
cache,
Expand Down Expand Up @@ -87,7 +93,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 @@ -97,6 +103,7 @@ async fn venv_impl(
system_site_packages: bool,
connectivity: Connectivity,
seed: bool,
force: bool,
exclude_newer: Option<DateTime<Utc>>,
native_tls: bool,
cache: &Cache,
Expand Down Expand Up @@ -131,9 +138,15 @@ async fn venv_impl(
let extra_cfg = vec![("uv".to_string(), env!("CARGO_PKG_VERSION").to_string())];

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

// Install seed packages.
if seed {
Expand Down
10 changes: 10 additions & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,15 @@ struct VenvArgs {
#[clap(long)]
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)]
force: bool,

/// The path to the virtual environment to create.
#[clap(default_value = DEFAULT_VENV_NAME)]
name: PathBuf,
Expand Down Expand Up @@ -1781,6 +1790,7 @@ async fn run() -> Result<ExitStatus> {
Connectivity::Online
},
args.seed,
args.force,
args.exclude_newer,
cli.native_tls,
&cache,
Expand Down

0 comments on commit 208ee71

Please sign in to comment.