Skip to content

Commit

Permalink
Allow syncing to empty virtual environment directories
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Nov 25, 2024
1 parent 13e0ef4 commit 0eded19
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 7 deletions.
10 changes: 10 additions & 0 deletions crates/uv-python/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub struct InvalidEnvironment {
#[derive(Debug, Clone)]
pub enum InvalidEnvironmentKind {
NotDirectory,
Empty,
MissingExecutable(PathBuf),
}

Expand Down Expand Up @@ -128,6 +129,7 @@ impl std::fmt::Display for InvalidEnvironmentKind {
Self::MissingExecutable(path) => {
write!(f, "missing Python executable at `{}`", path.user_display())
}
Self::Empty => write!(f, "directory is empty"),
}
}
}
Expand Down Expand Up @@ -178,6 +180,14 @@ impl PythonEnvironment {
.into());
}

if venv.read_dir().is_ok_and(|mut dir| dir.next().is_none()) {
return Err(InvalidEnvironment {
path: venv,
kind: InvalidEnvironmentKind::Empty,
}
.into());
}

let executable = virtualenv_python_executable(&venv);

// Check if the executable exists before querying so we can provide a more specific error
Expand Down
15 changes: 11 additions & 4 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,8 @@ impl ProjectInterpreter {
));
}
}
// If the environment is an empty directory, it's fine to use
InvalidEnvironmentKind::Empty => {}
};
}
Err(uv_python::Error::Query(uv_python::InterpreterError::NotFound(path))) => {
Expand Down Expand Up @@ -807,10 +809,15 @@ pub(crate) async fn get_or_init_environment(
(Ok(false), Ok(false)) => false,
// If it's not a virtual environment, bail
(Ok(true), Ok(false)) => {
return Err(ProjectError::InvalidProjectEnvironmentDir(
venv,
"it is not a compatible environment but cannot be recreated because it is not a virtual environment".to_string(),
));
// Unless it's empty, in which case we just ignore it
if venv.read_dir().is_ok_and(|mut dir| dir.next().is_none()) {
false
} else {
return Err(ProjectError::InvalidProjectEnvironmentDir(
venv,
"it is not a compatible environment but cannot be recreated because it is not a virtual environment".to_string(),
));
}
}
// Similarly, if we can't _tell_ if it exists we should bail
(_, Err(err)) | (Err(err), _) => {
Expand Down
10 changes: 7 additions & 3 deletions crates/uv/tests/it/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2795,13 +2795,17 @@ fn sync_empty_virtual_environment() -> Result<()> {

// Running `uv sync` should work
uv_snapshot!(context.filters(), context.sync(), @r###"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
error: Project virtual environment directory `[VENV]/` cannot be used because it is not a compatible environment but cannot be recreated because it is not a virtual environment
Creating virtual environment at: .venv
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);

Ok(())
Expand Down

0 comments on commit 0eded19

Please sign in to comment.