mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Consolidate logic for checking for a virtual environment (#14214)
We were checking whether a path was an executable in a virtual environment or the base directory of a virtual environment in multiple places in the codebase. This PR consolidates this logic into one place. Closes #13947.
This commit is contained in:
parent
a9a9e71481
commit
6481aa3e64
5 changed files with 30 additions and 16 deletions
|
@ -575,6 +575,30 @@ pub fn is_temporary(path: impl AsRef<Path>) -> bool {
|
|||
.is_some_and(|name| name.starts_with(".tmp"))
|
||||
}
|
||||
|
||||
/// Checks if the grandparent directory of the given executable is the base
|
||||
/// of a virtual environment.
|
||||
///
|
||||
/// The procedure described in PEP 405 includes checking both the parent and
|
||||
/// grandparent directory of an executable, but in practice we've found this to
|
||||
/// be unnecessary.
|
||||
pub fn is_virtualenv_executable(executable: impl AsRef<Path>) -> bool {
|
||||
executable
|
||||
.as_ref()
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.is_some_and(is_virtualenv_base)
|
||||
}
|
||||
|
||||
/// Returns `true` if a path is the base path of a virtual environment,
|
||||
/// indicated by the presence of a `pyvenv.cfg` file.
|
||||
///
|
||||
/// The procedure described in PEP 405 includes scanning `pyvenv.cfg`
|
||||
/// for a `home` key, but in practice we've found this to be
|
||||
/// unnecessary.
|
||||
pub fn is_virtualenv_base(path: impl AsRef<Path>) -> bool {
|
||||
path.as_ref().join("pyvenv.cfg").is_file()
|
||||
}
|
||||
|
||||
/// A file lock that is automatically released when dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct LockedFile(fs_err::File);
|
||||
|
|
|
@ -888,13 +888,8 @@ impl Error {
|
|||
| InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => {
|
||||
// If the interpreter is from an active, valid virtual environment, we should
|
||||
// fail because it's broken
|
||||
if let Some(Ok(true)) = matches!(source, PythonSource::ActiveEnvironment)
|
||||
.then(|| {
|
||||
path.parent()
|
||||
.and_then(Path::parent)
|
||||
.map(|path| path.join("pyvenv.cfg").try_exists())
|
||||
})
|
||||
.flatten()
|
||||
if matches!(source, PythonSource::ActiveEnvironment)
|
||||
&& uv_fs::is_virtualenv_executable(path)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
|
|
|
@ -993,14 +993,9 @@ impl InterpreterInfo {
|
|||
.symlink_metadata()
|
||||
.is_ok_and(|metadata| metadata.is_symlink())
|
||||
{
|
||||
let venv = executable
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.map(|path| path.join("pyvenv.cfg").is_file())
|
||||
.unwrap_or(false);
|
||||
Error::BrokenSymlink(BrokenSymlink {
|
||||
path: executable.to_path_buf(),
|
||||
venv,
|
||||
venv: uv_fs::is_virtualenv_executable(executable),
|
||||
})
|
||||
} else {
|
||||
Error::NotFound(executable.to_path_buf())
|
||||
|
|
|
@ -130,14 +130,14 @@ pub(crate) fn virtualenv_from_working_dir() -> Result<Option<PathBuf>, Error> {
|
|||
|
||||
for dir in current_dir.ancestors() {
|
||||
// If we're _within_ a virtualenv, return it.
|
||||
if dir.join("pyvenv.cfg").is_file() {
|
||||
if uv_fs::is_virtualenv_base(dir) {
|
||||
return Ok(Some(dir.to_path_buf()));
|
||||
}
|
||||
|
||||
// Otherwise, search for a `.venv` directory.
|
||||
let dot_venv = dir.join(".venv");
|
||||
if dot_venv.is_dir() {
|
||||
if !dot_venv.join("pyvenv.cfg").is_file() {
|
||||
if !uv_fs::is_virtualenv_base(&dot_venv) {
|
||||
return Err(Error::MissingPyVenvCfg(dot_venv));
|
||||
}
|
||||
return Ok(Some(dot_venv));
|
||||
|
|
|
@ -85,7 +85,7 @@ pub(crate) fn create(
|
|||
} else if metadata.is_dir() {
|
||||
if allow_existing {
|
||||
debug!("Allowing existing directory");
|
||||
} else if location.join("pyvenv.cfg").is_file() {
|
||||
} else if uv_fs::is_virtualenv_base(location) {
|
||||
debug!("Removing existing directory");
|
||||
|
||||
// On Windows, if the current executable is in the directory, guard against
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue