Skip to content

Commit

Permalink
Split virtual environment detection into a dedicated module (#3331)
Browse files Browse the repository at this point in the history
Split out of #3266
  • Loading branch information
zanieb authored May 2, 2024
1 parent c28a280 commit 4967555
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use std::env;
use std::path::{Path, PathBuf};

use same_file::is_same_file;
use tracing::{debug, info};

use uv_cache::Cache;
use uv_fs::{LockedFile, Simplified};

use crate::environment::cfg::PyVenvConfiguration;
use crate::virtualenv::{detect_virtualenv, virtualenv_python_executable, PyVenvConfiguration};
use crate::{find_default_python, find_requested_python, Error, Interpreter, Target};

/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
Expand All @@ -21,11 +20,11 @@ pub struct PythonEnvironment {
impl PythonEnvironment {
/// Create a [`PythonEnvironment`] for an existing virtual environment.
pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> {
let Some(venv) = detect_virtual_env()? else {
let Some(venv) = detect_virtualenv()? else {
return Err(Error::VenvNotFound);
};
let venv = fs_err::canonicalize(venv)?;
let executable = detect_python_executable(&venv);
let executable = virtualenv_python_executable(&venv);
let interpreter = Interpreter::query(&executable, cache)?;

debug_assert!(
Expand Down Expand Up @@ -152,61 +151,3 @@ impl PythonEnvironment {
self.interpreter
}
}

/// Locate the current virtual environment.
pub(crate) fn detect_virtual_env() -> Result<Option<PathBuf>, Error> {
if let Some(dir) = env::var_os("VIRTUAL_ENV").filter(|value| !value.is_empty()) {
info!(
"Found a virtualenv through VIRTUAL_ENV at: {}",
Path::new(&dir).display()
);
return Ok(Some(PathBuf::from(dir)));
}
if let Some(dir) = env::var_os("CONDA_PREFIX").filter(|value| !value.is_empty()) {
info!(
"Found a virtualenv through CONDA_PREFIX at: {}",
Path::new(&dir).display()
);
return Ok(Some(PathBuf::from(dir)));
}

// Search for a `.venv` directory in the current or any parent directory.
let current_dir = env::current_dir().expect("Failed to detect current directory");
for dir in current_dir.ancestors() {
let dot_venv = dir.join(".venv");
if dot_venv.is_dir() {
if !dot_venv.join("pyvenv.cfg").is_file() {
return Err(Error::MissingPyVenvCfg(dot_venv));
}
debug!("Found a virtualenv named .venv at: {}", dot_venv.display());
return Ok(Some(dot_venv));
}
}

Ok(None)
}

/// Returns the path to the `python` executable inside a virtual environment.
pub(crate) fn detect_python_executable(venv: impl AsRef<Path>) -> PathBuf {
let venv = venv.as_ref();
if cfg!(windows) {
// Search for `python.exe` in the `Scripts` directory.
let executable = venv.join("Scripts").join("python.exe");
if executable.exists() {
return executable;
}

// Apparently, Python installed via msys2 on Windows _might_ produce a POSIX-like layout.
// See: https://github.com/PyO3/maturin/issues/1108
let executable = venv.join("bin").join("python.exe");
if executable.exists() {
return executable;
}

// Fallback for Conda environments.
venv.join("python.exe")
} else {
// Search for `python` in the `bin` directory.
venv.join("bin").join("python")
}
}
58 changes: 0 additions & 58 deletions crates/uv-interpreter/src/environment/cfg.rs

This file was deleted.

3 changes: 0 additions & 3 deletions crates/uv-interpreter/src/environment/mod.rs

This file was deleted.

17 changes: 0 additions & 17 deletions crates/uv-interpreter/src/environment/virtualenv.rs

This file was deleted.

6 changes: 3 additions & 3 deletions crates/uv-interpreter/src/find_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use tracing::{debug, instrument};
use uv_cache::Cache;
use uv_warnings::warn_user_once;

use crate::environment::python_environment::{detect_python_executable, detect_virtual_env};
use crate::interpreter::InterpreterInfoError;
use crate::py_launcher::{py_list_paths, Error as PyLauncherError, PyListPath};
use crate::virtualenv::{detect_virtualenv, virtualenv_python_executable};
use crate::PythonVersion;
use crate::{Error, Interpreter};

Expand Down Expand Up @@ -506,8 +506,8 @@ fn find_version(

// Check if the venv Python matches.
if !system {
if let Some(venv) = detect_virtual_env()? {
let executable = detect_python_executable(venv);
if let Some(venv) = detect_virtualenv()? {
let executable = virtualenv_python_executable(venv);
let interpreter = Interpreter::query(executable, cache)?;

if version_matches(&interpreter) {
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use pypi_types::Scheme;
use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp};
use uv_fs::{write_atomic_sync, PythonExt, Simplified};

use crate::{Error, PythonVersion, Target, Virtualenv};
use crate::{Error, PythonVersion, Target, VirtualEnvironment};

/// A Python executable and its associated platform markers.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -98,7 +98,7 @@ impl Interpreter {

/// Return a new [`Interpreter`] with the given virtual environment root.
#[must_use]
pub fn with_virtualenv(self, virtualenv: Virtualenv) -> Self {
pub fn with_virtualenv(self, virtualenv: VirtualEnvironment) -> Self {
Self {
scheme: virtualenv.scheme,
sys_executable: virtualenv.executable,
Expand Down
8 changes: 4 additions & 4 deletions crates/uv-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ use std::process::ExitStatus;

use thiserror::Error;

pub use crate::environment::cfg::PyVenvConfiguration;
pub use crate::environment::python_environment::PythonEnvironment;
pub use crate::environment::virtualenv::Virtualenv;
pub use crate::environment::PythonEnvironment;
pub use crate::find_python::{find_best_python, find_default_python, find_requested_python};
pub use crate::interpreter::Interpreter;
use crate::interpreter::InterpreterInfoError;
pub use crate::python_version::PythonVersion;
pub use crate::target::Target;
pub use crate::virtualenv::{PyVenvConfiguration, VirtualEnvironment};

mod environment;
mod find_python;
Expand All @@ -32,6 +31,7 @@ pub mod platform;
mod py_launcher;
mod python_version;
mod target;
mod virtualenv;

#[derive(Debug, Error)]
pub enum Error {
Expand Down Expand Up @@ -77,7 +77,7 @@ pub enum Error {
#[error("Failed to write to cache")]
Encode(#[from] rmp_serde::encode::Error),
#[error("Broken virtualenv: Failed to parse pyvenv.cfg")]
Cfg(#[from] environment::cfg::Error),
Cfg(#[from] virtualenv::Error),
#[error("Error finding `{}` in PATH", _0.to_string_lossy())]
WhichError(OsString, #[source] which::Error),
#[error("Can't use Python at `{interpreter}`")]
Expand Down
Loading

0 comments on commit 4967555

Please sign in to comment.