Add environment variable to isolate Python version lookups

This commit is contained in:
Zanie 2024-01-24 14:22:19 -06:00
parent b95ae00f3c
commit 930dda1086
6 changed files with 40 additions and 11 deletions

1
Cargo.lock generated
View File

@ -2616,6 +2616,7 @@ dependencies = [
"platform-tags",
"puffin-cache",
"puffin-fs",
"puffin-warnings",
"regex",
"rmp-serde",
"serde",

View File

@ -20,6 +20,7 @@ platform-host = { path = "../platform-host" }
platform-tags = { path = "../platform-tags" }
puffin-cache = { path = "../puffin-cache" }
puffin-fs = { path = "../puffin-fs" }
puffin-warnings = { path = "../puffin-warnings" }
fs-err = { workspace = true, features = ["tokio"] }
once_cell = { workspace = true }

View File

@ -1,3 +1,4 @@
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::process::Command;
@ -18,6 +19,7 @@ use crate::python_platform::PythonPlatform;
use crate::python_query::find_python_windows;
use crate::virtual_env::detect_virtual_env;
use crate::{Error, PythonVersion};
use puffin_warnings::warn_user_once;
/// A Python executable and its associated platform markers.
#[derive(Debug, Clone)]
@ -132,6 +134,10 @@ impl Interpreter {
/// - If a python version is given: `pythonx.y`
/// - `python3` (unix) or `python.exe` (windows)
///
/// If `PUFFIN_PYTHON_PATH` is set, we will not check for Python versions in the
/// global PATH, instead we will search using the provided path. Virtual environments
/// will still be respected.
///
/// If a version is provided and an interpreter cannot be found with the given version,
/// we will return [`None`].
pub fn find_version(
@ -166,7 +172,8 @@ impl Interpreter {
python_version.major(),
python_version.minor()
);
if let Ok(executable) = which::which(&requested) {
if let Ok(executable) = Interpreter::find_executable(&requested) {
debug!("Resolved {requested} to {}", executable.display());
let interpreter = Interpreter::query(&executable, &platform.0, cache)?;
if version_matches(&interpreter) {
@ -175,7 +182,7 @@ impl Interpreter {
}
}
if let Ok(executable) = which::which("python3") {
if let Ok(executable) = Interpreter::find_executable("python3") {
debug!("Resolved python3 to {}", executable.display());
let interpreter = Interpreter::query(&executable, &platform.0, cache)?;
if version_matches(&interpreter) {
@ -194,7 +201,7 @@ impl Interpreter {
}
}
if let Ok(executable) = which::which("python.exe") {
if let Ok(executable) = Interpreter::find_executable("python.exe") {
let interpreter = Interpreter::query(&executable, &platform.0, cache)?;
if version_matches(&interpreter) {
return Ok(Some(interpreter));
@ -207,6 +214,26 @@ impl Interpreter {
Ok(None)
}
pub fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
requested: R,
) -> Result<PathBuf, Error> {
if let Some(isolated) = std::env::var_os("PUFFIN_PYTHON_PATH") {
warn_user_once!(
"PUFFIN_PYTHON_PATH is set; ignoring system PATH when looking for binaries."
);
if let Ok(cwd) = std::env::current_dir() {
which::which_in(&requested, Some(isolated), cwd)
.map_err(|err| Error::Which(requested.into(), err))
} else {
which::which_in_global(requested, Some(isolated))
.map_err(|err| Error::Which(requested.into(), err))
.and_then(|mut paths| paths.next().ok_or(Error::PythonNotFound))
}
} else {
which::which(&requested).map_err(|err| Error::Which(requested.into(), err))
}
}
/// Returns the path to the Python virtual environment.
#[inline]
pub fn platform(&self) -> &Platform {

View File

@ -1,3 +1,4 @@
use std::ffi::OsString;
use std::io;
use std::path::PathBuf;
use std::time::SystemTimeError;
@ -59,6 +60,6 @@ pub enum Error {
Encode(#[from] rmp_serde::encode::Error),
#[error("Failed to parse pyvenv.cfg")]
Cfg(#[from] cfg::Error),
#[error("Couldn't find `{0}` in PATH")]
Which(PathBuf, #[source] which::Error),
#[error("Couldn't find {0:?} in PATH")]
Which(OsString, #[source] which::Error),
}

View File

@ -7,7 +7,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use tracing::info_span;
use crate::Error;
use crate::{Error, Interpreter};
/// ```text
/// -V:3.12 C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
@ -34,7 +34,7 @@ pub fn find_requested_python(request: &str) -> Result<PathBuf, Error> {
// `-p 3.10` or `-p 3.10.1`
if cfg!(unix) {
let formatted = PathBuf::from(format!("python{request}"));
which::which_global(&formatted).map_err(|err| Error::Which(formatted, err))
Interpreter::find_executable(&formatted)
} else if cfg!(windows) {
if let [major, minor] = versions.as_slice() {
find_python_windows(*major, *minor)?.ok_or(Error::NoSuchPython {
@ -49,8 +49,7 @@ pub fn find_requested_python(request: &str) -> Result<PathBuf, Error> {
}
} else if !request.contains(std::path::MAIN_SEPARATOR) {
// `-p python3.10`; Generally not used on windows because all Python are `python.exe`.
let request = PathBuf::from(request);
which::which_global(&request).map_err(|err| Error::Which(request, err))
Interpreter::find_executable(&request)
} else {
// `-p /home/ferris/.local/bin/python3.10`
Ok(fs_err::canonicalize(request)?)

View File

@ -85,8 +85,8 @@ async fn venv_impl(
find_requested_python(python_request).into_diagnostic()?
} else {
fs::canonicalize(
which::which_global("python3")
.or_else(|_| which::which_global("python"))
Interpreter::find_executable("python3")
.or_else(|_| Interpreter::find_executable("python"))
.map_err(|_| VenvError::PythonNotFound)?,
)
.into_diagnostic()?