mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 04:48:18 +00:00
Rename and document venv discoveries (#2334)
Preparing for #2058, i found it hard to follow where which discovery
function gets called. I moved all the discovery functions to a
`find_python` module (some exposed through `PythonEnvironment`) and
documented which subcommand uses which python discovery strategy.
No functional changes.

This commit is contained in:
parent
73b30ba8ed
commit
262ca8b576
5 changed files with 149 additions and 145 deletions
|
|
@ -1,5 +1,3 @@
|
|||
//! Find a user requested python version/interpreter.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
|
@ -11,9 +9,10 @@ use platform_host::Platform;
|
|||
use uv_cache::Cache;
|
||||
use uv_fs::normalize_path;
|
||||
|
||||
use crate::{Error, Interpreter};
|
||||
use crate::python_environment::{detect_python_executable, detect_virtual_env};
|
||||
use crate::{Error, Interpreter, PythonVersion};
|
||||
|
||||
/// Find a python version/interpreter of a specific version.
|
||||
/// Find a Python of a specific version, a binary with a name or a path to a binary.
|
||||
///
|
||||
/// Supported formats:
|
||||
/// * `-p 3.10` searches for an installed Python 3.10 (`py --list-paths` on Windows, `python3.10` on
|
||||
|
|
@ -38,36 +37,27 @@ pub fn find_requested_python(
|
|||
.collect::<Result<Vec<_>, _>>();
|
||||
if let Ok(versions) = versions {
|
||||
// `-p 3.10` or `-p 3.10.1`
|
||||
match versions.as_slice() {
|
||||
[requested_major] => find_python(
|
||||
PythonVersionSelector::Major(*requested_major),
|
||||
platform,
|
||||
cache,
|
||||
),
|
||||
[major, minor] => find_python(
|
||||
PythonVersionSelector::MajorMinor(*major, *minor),
|
||||
platform,
|
||||
cache,
|
||||
),
|
||||
[major, minor, requested_patch] => find_python(
|
||||
PythonVersionSelector::MajorMinorPatch(*major, *minor, *requested_patch),
|
||||
platform,
|
||||
cache,
|
||||
),
|
||||
let selector = match versions.as_slice() {
|
||||
[requested_major] => PythonVersionSelector::Major(*requested_major),
|
||||
[major, minor] => PythonVersionSelector::MajorMinor(*major, *minor),
|
||||
[major, minor, requested_patch] => {
|
||||
PythonVersionSelector::MajorMinorPatch(*major, *minor, *requested_patch)
|
||||
}
|
||||
// SAFETY: Guaranteed by the Ok(versions) guard
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
find_python(selector, platform, cache)
|
||||
} else if !request.contains(std::path::MAIN_SEPARATOR) {
|
||||
// `-p python3.10`; Generally not used on windows because all Python are `python.exe`.
|
||||
let Some(executable) = find_executable(request)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Interpreter::query(&executable, platform.clone(), cache).map(Some)
|
||||
Interpreter::query(executable, platform.clone(), cache).map(Some)
|
||||
} else {
|
||||
// `-p /home/ferris/.local/bin/python3.10`
|
||||
let executable = normalize_path(request);
|
||||
|
||||
Interpreter::query(&executable, platform.clone(), cache).map(Some)
|
||||
Interpreter::query(executable, platform.clone(), cache).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +85,8 @@ pub(crate) fn try_find_default_python(
|
|||
find_python(PythonVersionSelector::Default, platform, cache)
|
||||
}
|
||||
|
||||
/// Finds a python version matching `selector`.
|
||||
/// Find a Python version matching `selector`.
|
||||
///
|
||||
/// It searches for an existing installation in the following order:
|
||||
/// * Search for the python binary in `PATH` (or `UV_TEST_PYTHON_PATH` if set). Visits each path and for each path resolves the
|
||||
/// files in the following order:
|
||||
|
|
@ -106,7 +97,7 @@ pub(crate) fn try_find_default_python(
|
|||
/// * (windows): For each of the above, test for the existence of `python.bat` shim (pyenv-windows) last.
|
||||
/// * (windows): Discover installations using `py --list-paths` (PEP514). Continue if `py` is not installed.
|
||||
///
|
||||
/// (Windows): Filter out the windows store shim (Enabled in Settings/Apps/Advanced app settings/App execution aliases).
|
||||
/// (Windows): Filter out the Windows store shim (Enabled in Settings/Apps/Advanced app settings/App execution aliases).
|
||||
fn find_python(
|
||||
selector: PythonVersionSelector,
|
||||
platform: &Platform,
|
||||
|
|
@ -350,7 +341,7 @@ impl PythonInstallation {
|
|||
match self {
|
||||
Self::PyListPath(PyListPath {
|
||||
executable_path, ..
|
||||
}) => Interpreter::query(&executable_path, platform.clone(), cache),
|
||||
}) => Interpreter::query(executable_path, platform.clone(), cache),
|
||||
Self::Interpreter(interpreter) => Ok(interpreter),
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +402,113 @@ impl PythonVersionSelector {
|
|||
}
|
||||
}
|
||||
|
||||
/// Find a matching Python or any fallback Python.
|
||||
///
|
||||
/// If no Python version is provided, we will use the first available interpreter.
|
||||
///
|
||||
/// If a Python version is provided, we will first try to find an exact match. If
|
||||
/// that cannot be found and a patch version was requested, we will look for a match
|
||||
/// without comparing the patch version number. If that cannot be found, we fall back to
|
||||
/// the first available version.
|
||||
///
|
||||
/// See [`Self::find_version`] for details on the precedence of Python lookup locations.
|
||||
#[instrument(skip_all, fields(?python_version))]
|
||||
pub fn find_best_python(
|
||||
python_version: Option<&PythonVersion>,
|
||||
platform: &Platform,
|
||||
cache: &Cache,
|
||||
) -> Result<Interpreter, Error> {
|
||||
if let Some(python_version) = python_version {
|
||||
debug!(
|
||||
"Starting interpreter discovery for Python {}",
|
||||
python_version
|
||||
);
|
||||
} else {
|
||||
debug!("Starting interpreter discovery for active Python");
|
||||
}
|
||||
|
||||
// First, check for an exact match (or the first available version if no Python version was provided)
|
||||
if let Some(interpreter) = find_version(python_version, platform, cache)? {
|
||||
return Ok(interpreter);
|
||||
}
|
||||
|
||||
if let Some(python_version) = python_version {
|
||||
// If that fails, and a specific patch version was requested try again allowing a
|
||||
// different patch version
|
||||
if python_version.patch().is_some() {
|
||||
if let Some(interpreter) =
|
||||
find_version(Some(&python_version.without_patch()), platform, cache)?
|
||||
{
|
||||
return Ok(interpreter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a Python version was requested but cannot be fulfilled, just take any version
|
||||
if let Some(interpreter) = find_version(None, platform, cache)? {
|
||||
return Ok(interpreter);
|
||||
}
|
||||
|
||||
Err(Error::PythonNotFound)
|
||||
}
|
||||
|
||||
/// Find a Python interpreter.
|
||||
///
|
||||
/// We check, in order, the following locations:
|
||||
///
|
||||
/// - `UV_DEFAULT_PYTHON`, which is set to the python interpreter when using `python -m uv`.
|
||||
/// - `VIRTUAL_ENV` and `CONDA_PREFIX`
|
||||
/// - A `.venv` folder
|
||||
/// - If a python version is given: Search `PATH` and `py --list-paths`, see `find_python`
|
||||
/// - `python3` (unix) or `python.exe` (windows)
|
||||
///
|
||||
/// If `UV_TEST_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`].
|
||||
fn find_version(
|
||||
python_version: Option<&PythonVersion>,
|
||||
platform: &Platform,
|
||||
cache: &Cache,
|
||||
) -> Result<Option<Interpreter>, Error> {
|
||||
let version_matches = |interpreter: &Interpreter| -> bool {
|
||||
if let Some(python_version) = python_version {
|
||||
// If a patch version was provided, check for an exact match
|
||||
python_version.is_satisfied_by(interpreter)
|
||||
} else {
|
||||
// The version always matches if one was not provided
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the venv Python matches.
|
||||
if let Some(venv) = detect_virtual_env()? {
|
||||
let executable = detect_python_executable(venv);
|
||||
let interpreter = Interpreter::query(executable, platform.clone(), cache)?;
|
||||
|
||||
if version_matches(&interpreter) {
|
||||
return Ok(Some(interpreter));
|
||||
}
|
||||
};
|
||||
|
||||
// Look for the requested version with by search for `python{major}.{minor}` in `PATH` on
|
||||
// Unix and `py --list-paths` on Windows.
|
||||
let interpreter = if let Some(python_version) = python_version {
|
||||
find_requested_python(&python_version.string, platform, cache)?
|
||||
} else {
|
||||
try_find_default_python(platform, cache)?
|
||||
};
|
||||
|
||||
if let Some(interpreter) = interpreter {
|
||||
debug_assert!(version_matches(&interpreter));
|
||||
Ok(Some(interpreter))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
mod windows {
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
|
@ -419,7 +517,7 @@ mod windows {
|
|||
use regex::Regex;
|
||||
use tracing::info_span;
|
||||
|
||||
use crate::python_query::PyListPath;
|
||||
use crate::find_python::PyListPath;
|
||||
use crate::Error;
|
||||
|
||||
/// ```text
|
||||
|
|
@ -658,7 +756,7 @@ mod tests {
|
|||
use platform_host::Platform;
|
||||
use uv_cache::Cache;
|
||||
|
||||
use crate::python_query::find_requested_python;
|
||||
use crate::find_python::find_requested_python;
|
||||
use crate::Error;
|
||||
|
||||
fn format_err<T: std::fmt::Debug>(err: Result<T, Error>) -> String {
|
||||
|
|
@ -6,7 +6,7 @@ use configparser::ini::Ini;
|
|||
use fs_err as fs;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, instrument, warn};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use cache_key::digest;
|
||||
use install_wheel_rs::Layout;
|
||||
|
|
@ -18,9 +18,8 @@ use pypi_types::Scheme;
|
|||
use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp};
|
||||
use uv_fs::write_atomic_sync;
|
||||
|
||||
use crate::python_environment::{detect_python_executable, detect_virtual_env};
|
||||
use crate::python_query::try_find_default_python;
|
||||
use crate::{find_requested_python, Error, PythonVersion, Virtualenv};
|
||||
use crate::Error;
|
||||
use crate::Virtualenv;
|
||||
|
||||
/// A Python executable and its associated platform markers.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -40,8 +39,12 @@ pub struct Interpreter {
|
|||
|
||||
impl Interpreter {
|
||||
/// Detect the interpreter info for the given Python executable.
|
||||
pub fn query(executable: &Path, platform: Platform, cache: &Cache) -> Result<Self, Error> {
|
||||
let info = InterpreterInfo::query_cached(executable, cache)?;
|
||||
pub fn query(
|
||||
executable: impl AsRef<Path>,
|
||||
platform: Platform,
|
||||
cache: &Cache,
|
||||
) -> Result<Self, Error> {
|
||||
let info = InterpreterInfo::query_cached(executable.as_ref(), cache)?;
|
||||
|
||||
debug_assert!(
|
||||
info.sys_executable.is_absolute(),
|
||||
|
|
@ -104,112 +107,6 @@ impl Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Find the best available Python interpreter to use.
|
||||
///
|
||||
/// If no Python version is provided, we will use the first available interpreter.
|
||||
///
|
||||
/// If a Python version is provided, we will first try to find an exact match. If
|
||||
/// that cannot be found and a patch version was requested, we will look for a match
|
||||
/// without comparing the patch version number. If that cannot be found, we fall back to
|
||||
/// the first available version.
|
||||
///
|
||||
/// See [`Self::find_version`] for details on the precedence of Python lookup locations.
|
||||
#[instrument(skip_all, fields(?python_version))]
|
||||
pub fn find_best(
|
||||
python_version: Option<&PythonVersion>,
|
||||
platform: &Platform,
|
||||
cache: &Cache,
|
||||
) -> Result<Self, Error> {
|
||||
if let Some(python_version) = python_version {
|
||||
debug!(
|
||||
"Starting interpreter discovery for Python {}",
|
||||
python_version
|
||||
);
|
||||
} else {
|
||||
debug!("Starting interpreter discovery for active Python");
|
||||
}
|
||||
|
||||
// First, check for an exact match (or the first available version if no Python version was provided)
|
||||
if let Some(interpreter) = Self::find_version(python_version, platform, cache)? {
|
||||
return Ok(interpreter);
|
||||
}
|
||||
|
||||
if let Some(python_version) = python_version {
|
||||
// If that fails, and a specific patch version was requested try again allowing a
|
||||
// different patch version
|
||||
if python_version.patch().is_some() {
|
||||
if let Some(interpreter) =
|
||||
Self::find_version(Some(&python_version.without_patch()), platform, cache)?
|
||||
{
|
||||
return Ok(interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
// If a Python version was requested but cannot be fulfilled, just take any version
|
||||
if let Some(interpreter) = Self::find_version(None, platform, cache)? {
|
||||
return Ok(interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::PythonNotFound)
|
||||
}
|
||||
|
||||
/// Find a Python interpreter.
|
||||
///
|
||||
/// We check, in order, the following locations:
|
||||
///
|
||||
/// - `VIRTUAL_ENV` and `CONDA_PREFIX`
|
||||
/// - A `.venv` folder
|
||||
/// - If a python version is given: `pythonx.y`
|
||||
/// - `python3` (unix) or `python.exe` (windows)
|
||||
///
|
||||
/// If `UV_TEST_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(crate) fn find_version(
|
||||
python_version: Option<&PythonVersion>,
|
||||
platform: &Platform,
|
||||
cache: &Cache,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let version_matches = |interpreter: &Self| -> bool {
|
||||
if let Some(python_version) = python_version {
|
||||
// If a patch version was provided, check for an exact match
|
||||
python_version.is_satisfied_by(interpreter)
|
||||
} else {
|
||||
// The version always matches if one was not provided
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the venv Python matches.
|
||||
if let Some(venv) = detect_virtual_env()? {
|
||||
let executable = detect_python_executable(venv);
|
||||
let interpreter = Self::query(&executable, platform.clone(), cache)?;
|
||||
|
||||
if version_matches(&interpreter) {
|
||||
return Ok(Some(interpreter));
|
||||
}
|
||||
};
|
||||
|
||||
// Look for the requested version with by search for `python{major}.{minor}` in `PATH` on
|
||||
// Unix and `py --list-paths` on Windows.
|
||||
let interpreter = if let Some(python_version) = python_version {
|
||||
find_requested_python(&python_version.string, platform, cache)?
|
||||
} else {
|
||||
try_find_default_python(platform, cache)?
|
||||
};
|
||||
|
||||
if let Some(interpreter) = interpreter {
|
||||
debug_assert!(version_matches(&interpreter));
|
||||
Ok(Some(interpreter))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the Python virtual environment.
|
||||
#[inline]
|
||||
pub fn platform(&self) -> &Platform {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
//! Find matching Python interpreter and query information about python interpreter.
|
||||
//!
|
||||
//! * The `venv` subcommand uses [`find_requested_python`] if `-p`/`--python` is used and
|
||||
//! `find_default_python` otherwise.
|
||||
//! * The `compile` subcommand uses [`find_best_python`].
|
||||
//! * The `sync`, `install`, `uninstall`, `freeze`, `list` and `show` subcommands use
|
||||
//! [`find_default_python`] when `--python` is used, [`find_default_python`] when `--system` is used
|
||||
//! and the current venv by default.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -6,16 +15,16 @@ use std::process::ExitStatus;
|
|||
use thiserror::Error;
|
||||
|
||||
pub use crate::cfg::PyVenvConfiguration;
|
||||
pub use crate::find_python::{find_best_python, find_default_python, find_requested_python};
|
||||
pub use crate::interpreter::Interpreter;
|
||||
pub use crate::python_environment::PythonEnvironment;
|
||||
pub use crate::python_query::{find_default_python, find_requested_python};
|
||||
pub use crate::python_version::PythonVersion;
|
||||
pub use crate::virtualenv::Virtualenv;
|
||||
|
||||
mod cfg;
|
||||
mod find_python;
|
||||
mod interpreter;
|
||||
mod python_environment;
|
||||
mod python_query;
|
||||
mod python_version;
|
||||
mod virtualenv;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use uv_fs::{LockedFile, Simplified};
|
|||
use crate::cfg::PyVenvConfiguration;
|
||||
use crate::{find_default_python, find_requested_python, Error, Interpreter};
|
||||
|
||||
/// A Python environment, consisting of a Python [`Interpreter`] and a root directory.
|
||||
/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PythonEnvironment {
|
||||
root: PathBuf,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder}
|
|||
use uv_dispatch::BuildDispatch;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::{Downloader, NoBinary};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment, PythonVersion};
|
||||
use uv_interpreter::{find_best_python, PythonEnvironment, PythonVersion};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_resolver::{
|
||||
AnnotationStyle, DependencyMode, DisplayResolutionGraph, InMemoryIndex, Manifest,
|
||||
|
|
@ -132,7 +132,7 @@ pub(crate) async fn pip_compile(
|
|||
|
||||
// Find an interpreter to use for building distributions
|
||||
let platform = Platform::current()?;
|
||||
let interpreter = Interpreter::find_best(python_version.as_ref(), &platform, &cache)?;
|
||||
let interpreter = find_best_python(python_version.as_ref(), &platform, &cache)?;
|
||||
debug!(
|
||||
"Using Python {} interpreter at {} for builds",
|
||||
interpreter.python_version(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue