mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35: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"))
|
.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.
|
/// A file lock that is automatically released when dropped.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LockedFile(fs_err::File);
|
pub struct LockedFile(fs_err::File);
|
||||||
|
|
|
@ -888,13 +888,8 @@ impl Error {
|
||||||
| InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => {
|
| InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => {
|
||||||
// If the interpreter is from an active, valid virtual environment, we should
|
// If the interpreter is from an active, valid virtual environment, we should
|
||||||
// fail because it's broken
|
// fail because it's broken
|
||||||
if let Some(Ok(true)) = matches!(source, PythonSource::ActiveEnvironment)
|
if matches!(source, PythonSource::ActiveEnvironment)
|
||||||
.then(|| {
|
&& uv_fs::is_virtualenv_executable(path)
|
||||||
path.parent()
|
|
||||||
.and_then(Path::parent)
|
|
||||||
.map(|path| path.join("pyvenv.cfg").try_exists())
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
{
|
{
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -993,14 +993,9 @@ impl InterpreterInfo {
|
||||||
.symlink_metadata()
|
.symlink_metadata()
|
||||||
.is_ok_and(|metadata| metadata.is_symlink())
|
.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 {
|
Error::BrokenSymlink(BrokenSymlink {
|
||||||
path: executable.to_path_buf(),
|
path: executable.to_path_buf(),
|
||||||
venv,
|
venv: uv_fs::is_virtualenv_executable(executable),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Error::NotFound(executable.to_path_buf())
|
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() {
|
for dir in current_dir.ancestors() {
|
||||||
// If we're _within_ a virtualenv, return it.
|
// 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()));
|
return Ok(Some(dir.to_path_buf()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, search for a `.venv` directory.
|
// Otherwise, search for a `.venv` directory.
|
||||||
let dot_venv = dir.join(".venv");
|
let dot_venv = dir.join(".venv");
|
||||||
if dot_venv.is_dir() {
|
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 Err(Error::MissingPyVenvCfg(dot_venv));
|
||||||
}
|
}
|
||||||
return Ok(Some(dot_venv));
|
return Ok(Some(dot_venv));
|
||||||
|
|
|
@ -85,7 +85,7 @@ pub(crate) fn create(
|
||||||
} else if metadata.is_dir() {
|
} else if metadata.is_dir() {
|
||||||
if allow_existing {
|
if allow_existing {
|
||||||
debug!("Allowing existing directory");
|
debug!("Allowing existing directory");
|
||||||
} else if location.join("pyvenv.cfg").is_file() {
|
} else if uv_fs::is_virtualenv_base(location) {
|
||||||
debug!("Removing existing directory");
|
debug!("Removing existing directory");
|
||||||
|
|
||||||
// On Windows, if the current executable is in the directory, guard against
|
// On Windows, if the current executable is in the directory, guard against
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue