From 53035d65a14503769c9749e9ce60e4c22a72af1c Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 7 Jun 2024 15:20:28 -0400 Subject: [PATCH] Refactor `uv-toolchain` types (#4121) Extends #4120 Part of #2607 There should be no behavior changes here. Restructures the discovery API to be focused on a toolchain first perspective in preparation for exposing a `find_or_fetch` method for toolchains in https://github.com/astral-sh/uv/pull/4138. --- crates/bench/benches/uv.rs | 4 +- crates/uv-dev/src/build.rs | 6 +- crates/uv-dev/src/compile.rs | 6 +- crates/uv-toolchain/src/discovery.rs | 393 ++++++++-------- crates/uv-toolchain/src/downloads.rs | 16 +- crates/uv-toolchain/src/environment.rs | 110 +---- crates/uv-toolchain/src/lib.rs | 600 ++++++++++++------------ crates/uv-toolchain/src/managed.rs | 26 +- crates/uv-toolchain/src/toolchain.rs | 136 ++++++ crates/uv-virtualenv/src/lib.rs | 2 +- crates/uv/src/commands/pip/check.rs | 11 +- crates/uv/src/commands/pip/compile.rs | 23 +- crates/uv/src/commands/pip/freeze.rs | 11 +- crates/uv/src/commands/pip/install.rs | 43 +- crates/uv/src/commands/pip/list.rs | 10 +- crates/uv/src/commands/pip/show.rs | 13 +- crates/uv/src/commands/pip/sync.rs | 43 +- crates/uv/src/commands/pip/uninstall.rs | 34 +- crates/uv/src/commands/project/mod.rs | 4 +- crates/uv/src/commands/project/run.rs | 6 +- crates/uv/src/commands/tool/run.rs | 7 +- crates/uv/src/commands/venv.rs | 9 +- crates/uv/tests/common/mod.rs | 10 +- 23 files changed, 777 insertions(+), 746 deletions(-) create mode 100644 crates/uv-toolchain/src/toolchain.rs diff --git a/crates/bench/benches/uv.rs b/crates/bench/benches/uv.rs index 5c93a67785db..72f28681a076 100644 --- a/crates/bench/benches/uv.rs +++ b/crates/bench/benches/uv.rs @@ -15,7 +15,7 @@ fn resolve_warm_jupyter(c: &mut Criterion) { .unwrap(); let cache = &Cache::from_path("../../.cache").init().unwrap(); - let venv = PythonEnvironment::from_virtualenv(cache).unwrap(); + let venv = PythonEnvironment::from_root("../../.venv", cache).unwrap(); let client = &RegistryClientBuilder::new(cache.clone()).build(); let manifest = &Manifest::simple(vec![Requirement::from( pep508_rs::Requirement::from_str("jupyter").unwrap(), @@ -44,7 +44,7 @@ fn resolve_warm_airflow(c: &mut Criterion) { .unwrap(); let cache = &Cache::from_path("../../.cache").init().unwrap(); - let venv = PythonEnvironment::from_virtualenv(cache).unwrap(); + let venv = PythonEnvironment::from_root("../../.venv", cache).unwrap(); let client = &RegistryClientBuilder::new(cache.clone()).build(); let manifest = &Manifest::simple(vec![ Requirement::from(pep508_rs::Requirement::from_str("apache-airflow[all]").unwrap()), diff --git a/crates/uv-dev/src/build.rs b/crates/uv-dev/src/build.rs index a3d715574c92..43dbfc1a71c7 100644 --- a/crates/uv-dev/src/build.rs +++ b/crates/uv-dev/src/build.rs @@ -16,7 +16,7 @@ use uv_configuration::{ use uv_dispatch::BuildDispatch; use uv_git::GitResolver; use uv_resolver::{FlatIndex, InMemoryIndex}; -use uv_toolchain::PythonEnvironment; +use uv_toolchain::Toolchain; use uv_types::{BuildContext, BuildIsolation, InFlight}; #[derive(Parser)] @@ -65,12 +65,12 @@ pub(crate) async fn build(args: BuildArgs) -> Result { let index = InMemoryIndex::default(); let index_urls = IndexLocations::default(); let setup_py = SetupPyStrategy::default(); - let venv = PythonEnvironment::from_virtualenv(&cache)?; + let toolchain = Toolchain::find_virtualenv(&cache)?; let build_dispatch = BuildDispatch::new( &client, &cache, - venv.interpreter(), + toolchain.interpreter(), &index_urls, &flat_index, &index, diff --git a/crates/uv-dev/src/compile.rs b/crates/uv-dev/src/compile.rs index 7c88e4e2179b..1fd6250d28ef 100644 --- a/crates/uv-dev/src/compile.rs +++ b/crates/uv-dev/src/compile.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use clap::Parser; use tracing::info; use uv_cache::{Cache, CacheArgs}; -use uv_toolchain::PythonEnvironment; +use uv_toolchain::Toolchain; #[derive(Parser)] pub(crate) struct CompileArgs { @@ -20,8 +20,8 @@ pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> { let interpreter = if let Some(python) = args.python { python } else { - let venv = PythonEnvironment::from_virtualenv(&cache)?; - venv.python_executable().to_path_buf() + let interpreter = Toolchain::find_virtualenv(&cache)?.into_interpreter(); + interpreter.sys_executable().to_path_buf() }; let files = uv_installer::compile_tree( diff --git a/crates/uv-toolchain/src/discovery.rs b/crates/uv-toolchain/src/discovery.rs index 68cffd9a7e60..b8d59c2f8cad 100644 --- a/crates/uv-toolchain/src/discovery.rs +++ b/crates/uv-toolchain/src/discovery.rs @@ -11,6 +11,7 @@ use crate::implementation::{ImplementationName, LenientImplementationName}; use crate::interpreter::Error as InterpreterError; use crate::managed::InstalledToolchains; use crate::py_launcher::py_list_paths; +use crate::toolchain::Toolchain; use crate::virtualenv::{ conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir, virtualenv_python_executable, @@ -24,12 +25,12 @@ use std::num::ParseIntError; use std::{env, io}; use std::{path::Path, path::PathBuf, str::FromStr}; -/// A request to find a Python interpreter. +/// A request to find a Python toolchain. /// /// See [`InterpreterRequest::from_str`]. #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub enum InterpreterRequest { - /// Use any discovered Python interpreter +pub enum ToolchainRequest { + /// Use any discovered Python toolchain #[default] Any, /// A Python version without an implementation name e.g. `3.10` @@ -46,20 +47,20 @@ pub enum InterpreterRequest { ImplementationVersion(ImplementationName, VersionRequest), } -/// The sources to consider when finding a Python interpreter. +/// The sources to consider when finding a Python toolchain. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum SourceSelector { - // Consider all interpreter sources. +pub enum ToolchainSources { + // Consider all toolchain sources. All(PreviewMode), - // Only consider system interpreter sources + // Only consider system toolchain sources System(PreviewMode), // Only consider virtual environment sources VirtualEnv, // Only consider a custom set of sources - Custom(HashSet), + Custom(HashSet), } -/// A Python interpreter version request. +/// A Python toolchain version request. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum VersionRequest { #[default] @@ -72,7 +73,7 @@ pub enum VersionRequest { /// The policy for discovery of "system" Python interpreters. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum SystemPython { - /// Only allow a system Python if passed directly i.e. via [`InterpreterSource::ProvidedPath`] or [`InterpreterSource::ParentInterpreter`] + /// Only allow a system Python if passed directly i.e. via [`ToolchainSource::ProvidedPath`] or [`ToolchainSource::ParentInterpreter`] #[default] Explicit, /// Do not allow a system Python @@ -83,24 +84,24 @@ pub enum SystemPython { Required, } -/// The result of an interpreter search. +/// The result of an toolchain search. /// -/// Returned by [`find_interpreter`]. -type InterpreterResult = Result; +/// Returned by [`find_toolchain`]. +type ToolchainResult = Result; -/// The result of failed interpreter discovery. +/// The result of failed toolchain discovery. /// /// See [`InterpreterResult`]. #[derive(Clone, Debug, Error)] -pub enum InterpreterNotFound { +pub enum ToolchainNotFound { /// No Python installations were found. - NoPythonInstallation(SourceSelector, Option), + NoPythonInstallation(ToolchainSources, Option), /// No Python installations with the requested version were found. - NoMatchingVersion(SourceSelector, VersionRequest), + NoMatchingVersion(ToolchainSources, VersionRequest), /// No Python installations with the requested implementation name were found. - NoMatchingImplementation(SourceSelector, ImplementationName), + NoMatchingImplementation(ToolchainSources, ImplementationName), /// No Python installations with the requested implementation name and version were found. - NoMatchingImplementationVersion(SourceSelector, ImplementationName, VersionRequest), + NoMatchingImplementationVersion(ToolchainSources, ImplementationName, VersionRequest), /// The requested file path does not exist. FileNotFound(PathBuf), /// The requested directory path does not exist. @@ -113,19 +114,10 @@ pub enum InterpreterNotFound { FileNotExecutable(PathBuf), } -/// The result of successful interpreter discovery. -/// -/// See [`InterpreterResult`]. -#[derive(Clone, Debug)] -pub struct DiscoveredInterpreter { - pub(crate) source: InterpreterSource, - pub(crate) interpreter: Interpreter, -} - -/// The source of a discovered Python interpreter. +/// The source of a discovered Python toolchain. #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)] -pub enum InterpreterSource { - /// The interpreter path was provided directly +pub enum ToolchainSource { + /// The toolchain path was provided directly ProvidedPath, /// An environment was active e.g. via `VIRTUAL_ENV` ActiveEnvironment, @@ -137,9 +129,9 @@ pub enum InterpreterSource { SearchPath, /// An executable was found via the `py` launcher PyLauncher, - /// The interpreter was found in the uv toolchain directory - ManagedToolchain, - /// The interpreter invoked uv i.e. via `python -m uv ...` + /// The toolchain was found in the uv toolchain directory + Managed, + /// The toolchain was found via the invoking interpreter i.e. via `python -m uv ...` ParentInterpreter, // TODO(zanieb): Add support for fetching the interpreter from a remote source } @@ -166,7 +158,7 @@ pub enum Error { PyLauncher(#[from] crate::py_launcher::Error), #[error("Interpreter discovery for `{0}` requires `{1}` but it is not selected; the following are selected: {2}")] - SourceNotSelected(InterpreterRequest, InterpreterSource, SourceSelector), + SourceNotSelected(ToolchainRequest, ToolchainSource, ToolchainSources), } /// Lazily iterate over all discoverable Python executables. @@ -191,43 +183,43 @@ pub enum Error { fn python_executables<'a>( version: Option<&'a VersionRequest>, implementation: Option<&'a ImplementationName>, - sources: &SourceSelector, -) -> impl Iterator> + 'a { + sources: &ToolchainSources, +) -> impl Iterator> + 'a { // Note we are careful to ensure the iterator chain is lazy to avoid unnecessary work // (1) The parent interpreter - sources.contains(InterpreterSource::ParentInterpreter).then(|| + sources.contains(ToolchainSource::ParentInterpreter).then(|| std::env::var_os("UV_INTERNAL__PARENT_INTERPRETER") .into_iter() - .map(|path| Ok((InterpreterSource::ParentInterpreter, PathBuf::from(path)))) + .map(|path| Ok((ToolchainSource::ParentInterpreter, PathBuf::from(path)))) ).into_iter().flatten() // (2) An active virtual environment .chain( - sources.contains(InterpreterSource::ActiveEnvironment).then(|| + sources.contains(ToolchainSource::ActiveEnvironment).then(|| virtualenv_from_env() .into_iter() .map(virtualenv_python_executable) - .map(|path| Ok((InterpreterSource::ActiveEnvironment, path))) + .map(|path| Ok((ToolchainSource::ActiveEnvironment, path))) ).into_iter().flatten() ) // (3) An active conda environment .chain( - sources.contains(InterpreterSource::CondaPrefix).then(|| + sources.contains(ToolchainSource::CondaPrefix).then(|| conda_prefix_from_env() .into_iter() .map(virtualenv_python_executable) - .map(|path| Ok((InterpreterSource::CondaPrefix, path))) + .map(|path| Ok((ToolchainSource::CondaPrefix, path))) ).into_iter().flatten() ) // (4) A discovered environment .chain( - sources.contains(InterpreterSource::DiscoveredEnvironment).then(|| + sources.contains(ToolchainSource::DiscoveredEnvironment).then(|| std::iter::once( virtualenv_from_working_dir() .map(|path| path .map(virtualenv_python_executable) - .map(|path| (InterpreterSource::DiscoveredEnvironment, path)) + .map(|path| (ToolchainSource::DiscoveredEnvironment, path)) .into_iter() ) .map_err(Error::from) @@ -236,7 +228,7 @@ fn python_executables<'a>( ) // (5) Managed toolchains .chain( - sources.contains(InterpreterSource::ManagedToolchain).then(move || + sources.contains(ToolchainSource::Managed).then(move || std::iter::once( InstalledToolchains::from_settings().map_err(Error::from).and_then(|installed_toolchains| { debug!("Searching for managed toolchains at `{}`", installed_toolchains.root().user_display()); @@ -249,7 +241,7 @@ fn python_executables<'a>( ) ) .inspect(|toolchain| debug!("Found managed toolchain `{toolchain}`")) - .map(|toolchain| (InterpreterSource::ManagedToolchain, toolchain.executable())) + .map(|toolchain| (ToolchainSource::Managed, toolchain.executable())) ) }) ).flatten_ok() @@ -257,15 +249,15 @@ fn python_executables<'a>( ) // (6) The search path .chain( - sources.contains(InterpreterSource::SearchPath).then(move || + sources.contains(ToolchainSource::SearchPath).then(move || python_executables_from_search_path(version, implementation) - .map(|path| Ok((InterpreterSource::SearchPath, path))), + .map(|path| Ok((ToolchainSource::SearchPath, path))), ).into_iter().flatten() ) // (7) The `py` launcher (windows only) // TODO(konstin): Implement to read python installations from the registry instead. .chain( - (sources.contains(InterpreterSource::PyLauncher) && cfg!(windows)).then(|| + (sources.contains(ToolchainSource::PyLauncher) && cfg!(windows)).then(|| std::iter::once( py_list_paths() .map(|entries| @@ -275,7 +267,7 @@ fn python_executables<'a>( version.has_patch() || version.matches_major_minor(entry.major, entry.minor) ) ) - .map(|entry| (InterpreterSource::PyLauncher, entry.executable_path)) + .map(|entry| (ToolchainSource::PyLauncher, entry.executable_path)) ) .map_err(Error::from) ).flatten_ok() @@ -354,14 +346,14 @@ fn python_executables_from_search_path<'a>( /// Lazily iterate over all discoverable Python interpreters. /// -///See [`python_executables`] for more information on discovery. +/// See [`python_executables`] for more information on discovery. fn python_interpreters<'a>( version: Option<&'a VersionRequest>, implementation: Option<&'a ImplementationName>, system: SystemPython, - sources: &SourceSelector, + sources: &ToolchainSources, cache: &'a Cache, -) -> impl Iterator> + 'a { +) -> impl Iterator> + 'a { python_executables(version, implementation, sources) .map(|result| match result { Ok((source, path)) => Interpreter::query(&path, cache) @@ -383,13 +375,13 @@ fn python_interpreters<'a>( Ok((source, interpreter)) => match ( system, // Conda environments are not conformant virtual environments but we should not treat them as system interpreters - interpreter.is_virtualenv() || matches!(source, InterpreterSource::CondaPrefix), + interpreter.is_virtualenv() || matches!(source, ToolchainSource::CondaPrefix), ) { (SystemPython::Allowed, _) => true, (SystemPython::Explicit, false) => { if matches!( source, - InterpreterSource::ProvidedPath | InterpreterSource::ParentInterpreter + ToolchainSource::ProvidedPath | ToolchainSource::ParentInterpreter ) { debug!( "Allowing system Python interpreter at `{}`", @@ -429,11 +421,11 @@ fn python_interpreters<'a>( /// Check if an encountered error should stop discovery. /// -/// Returns false when an error could be due to a faulty interpreter and we should continue searching for a working one. +/// Returns false when an error could be due to a faulty toolchain and we should continue searching for a working one. fn should_stop_discovery(err: &Error) -> bool { match err { - // When querying the interpreter fails, we will only raise errors that demonstrate that something is broken - // If the interpreter returned a bad response, we'll continue searching for one that works + // When querying the toolchain interpreter fails, we will only raise errors that demonstrate that something is broken + // If the toolchain interpreter returned a bad response, we'll continue searching for one that works Error::Query(err) => match err { InterpreterError::Encode(_) | InterpreterError::Io(_) @@ -449,81 +441,81 @@ fn should_stop_discovery(err: &Error) -> bool { } } -/// Find an interpreter that satisfies the given request. +/// Find a toolchain that satisfies the given request. /// -/// If an error is encountered while locating or inspecting a candidate interpreter, +/// If an error is encountered while locating or inspecting a candidate toolchain, /// the error will raised instead of attempting further candidates. -pub fn find_interpreter( - request: &InterpreterRequest, +pub fn find_toolchain( + request: &ToolchainRequest, system: SystemPython, - sources: &SourceSelector, + sources: &ToolchainSources, cache: &Cache, -) -> Result { +) -> Result { let result = match request { - InterpreterRequest::File(path) => { + ToolchainRequest::File(path) => { debug!("Checking for Python interpreter at {request}"); - if !sources.contains(InterpreterSource::ProvidedPath) { + if !sources.contains(ToolchainSource::ProvidedPath) { return Err(Error::SourceNotSelected( request.clone(), - InterpreterSource::ProvidedPath, + ToolchainSource::ProvidedPath, sources.clone(), )); } if !path.try_exists()? { - return Ok(InterpreterResult::Err(InterpreterNotFound::FileNotFound( + return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound( path.clone(), ))); } - DiscoveredInterpreter { - source: InterpreterSource::ProvidedPath, + Toolchain { + source: ToolchainSource::ProvidedPath, interpreter: Interpreter::query(path, cache)?, } } - InterpreterRequest::Directory(path) => { + ToolchainRequest::Directory(path) => { debug!("Checking for Python interpreter in {request}"); - if !sources.contains(InterpreterSource::ProvidedPath) { + if !sources.contains(ToolchainSource::ProvidedPath) { return Err(Error::SourceNotSelected( request.clone(), - InterpreterSource::ProvidedPath, + ToolchainSource::ProvidedPath, sources.clone(), )); } if !path.try_exists()? { - return Ok(InterpreterResult::Err(InterpreterNotFound::FileNotFound( + return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound( path.clone(), ))); } let executable = virtualenv_python_executable(path); if !executable.try_exists()? { - return Ok(InterpreterResult::Err( - InterpreterNotFound::ExecutableNotFoundInDirectory(path.clone(), executable), + return Ok(ToolchainResult::Err( + ToolchainNotFound::ExecutableNotFoundInDirectory(path.clone(), executable), )); } - DiscoveredInterpreter { - source: InterpreterSource::ProvidedPath, + Toolchain { + source: ToolchainSource::ProvidedPath, interpreter: Interpreter::query(executable, cache)?, } } - InterpreterRequest::ExecutableName(name) => { + ToolchainRequest::ExecutableName(name) => { debug!("Searching for Python interpreter with {request}"); - if !sources.contains(InterpreterSource::SearchPath) { + if !sources.contains(ToolchainSource::SearchPath) { return Err(Error::SourceNotSelected( request.clone(), - InterpreterSource::SearchPath, + ToolchainSource::SearchPath, sources.clone(), )); } let Some(executable) = which(name).ok() else { - return Ok(InterpreterResult::Err( - InterpreterNotFound::ExecutableNotFoundInSearchPath(name.clone()), + return Ok(ToolchainResult::Err( + ToolchainNotFound::ExecutableNotFoundInSearchPath(name.clone()), )); }; - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: Interpreter::query(executable, cache)?, } } - InterpreterRequest::Implementation(implementation) => { + ToolchainRequest::Implementation(implementation) => { debug!("Searching for a {request} interpreter in {sources}"); let Some((source, interpreter)) = python_interpreters(None, Some(implementation), system, sources, cache) @@ -538,16 +530,16 @@ pub fn find_interpreter( }) .transpose()? else { - return Ok(InterpreterResult::Err( - InterpreterNotFound::NoMatchingImplementation(sources.clone(), *implementation), + return Ok(ToolchainResult::Err( + ToolchainNotFound::NoMatchingImplementation(sources.clone(), *implementation), )); }; - DiscoveredInterpreter { + Toolchain { source, interpreter, } } - InterpreterRequest::ImplementationVersion(implementation, version) => { + ToolchainRequest::ImplementationVersion(implementation, version) => { debug!("Searching for {request} in {sources}"); let Some((source, interpreter)) = python_interpreters(Some(version), Some(implementation), system, sources, cache) @@ -565,20 +557,20 @@ pub fn find_interpreter( else { // TODO(zanieb): Peek if there are any interpreters with the requested implementation // to improve the error message e.g. using `NoMatchingImplementation` instead - return Ok(InterpreterResult::Err( - InterpreterNotFound::NoMatchingImplementationVersion( + return Ok(ToolchainResult::Err( + ToolchainNotFound::NoMatchingImplementationVersion( sources.clone(), *implementation, *version, ), )); }; - DiscoveredInterpreter { + Toolchain { source, interpreter, } } - InterpreterRequest::Any => { + ToolchainRequest::Any => { debug!("Searching for Python interpreter in {sources}"); let Some((source, interpreter)) = python_interpreters(None, None, system, sources, cache) @@ -591,16 +583,16 @@ pub fn find_interpreter( }) .transpose()? else { - return Ok(InterpreterResult::Err( - InterpreterNotFound::NoPythonInstallation(sources.clone(), None), + return Ok(ToolchainResult::Err( + ToolchainNotFound::NoPythonInstallation(sources.clone(), None), )); }; - DiscoveredInterpreter { + Toolchain { source, interpreter, } } - InterpreterRequest::Version(version) => { + ToolchainRequest::Version(version) => { debug!("Searching for {request} in {sources}"); let Some((source, interpreter)) = python_interpreters(Some(version), None, system, sources, cache) @@ -614,106 +606,106 @@ pub fn find_interpreter( .transpose()? else { let err = if matches!(version, VersionRequest::Any) { - InterpreterNotFound::NoPythonInstallation(sources.clone(), Some(*version)) + ToolchainNotFound::NoPythonInstallation(sources.clone(), Some(*version)) } else { - InterpreterNotFound::NoMatchingVersion(sources.clone(), *version) + ToolchainNotFound::NoMatchingVersion(sources.clone(), *version) }; - return Ok(InterpreterResult::Err(err)); + return Ok(ToolchainResult::Err(err)); }; - DiscoveredInterpreter { + Toolchain { source, interpreter, } } }; - Ok(InterpreterResult::Ok(result)) + Ok(ToolchainResult::Ok(result)) } -/// Find the default Python interpreter on the system. +/// Find the default Python toolchain on the system. /// /// Virtual environments are not included in discovery. /// -/// See [`find_interpreter`] for more details on interpreter discovery. -pub fn find_default_interpreter( +/// See [`find_toolchain`] for more details on toolchain discovery. +pub fn find_default_toolchain( preview: PreviewMode, cache: &Cache, -) -> Result { - let request = InterpreterRequest::default(); - let sources = SourceSelector::System(preview); +) -> Result { + let request = ToolchainRequest::default(); + let sources = ToolchainSources::System(preview); - let result = find_interpreter(&request, SystemPython::Required, &sources, cache)?; - if let Ok(ref found) = result { - warn_on_unsupported_python(found.interpreter()); + let result = find_toolchain(&request, SystemPython::Required, &sources, cache)?; + if let Ok(ref toolchain) = result { + warn_on_unsupported_python(toolchain.interpreter()); } Ok(result) } -/// Find the best-matching Python interpreter. +/// Find the best-matching Python toolchain. /// -/// If no Python version is provided, we will use the first available interpreter. +/// If no Python version is provided, we will use the first available toolchain. /// /// 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 [`find_interpreter`] for more details on interpreter discovery. +/// See [`find_toolchain`] for more details on toolchain discovery. #[instrument(skip_all, fields(request))] -pub fn find_best_interpreter( - request: &InterpreterRequest, +pub fn find_best_toolchain( + request: &ToolchainRequest, system: SystemPython, preview: PreviewMode, cache: &Cache, -) -> Result { - debug!("Starting interpreter discovery for {}", request); +) -> Result { + debug!("Starting toolchain discovery for {}", request); // Determine if we should be allowed to look outside of virtual environments. - let sources = SourceSelector::from_settings(system, preview); + let sources = ToolchainSources::from_settings(system, preview); // First, check for an exact match (or the first available version if no Python versfion was provided) debug!("Looking for exact match for request {request}"); - let result = find_interpreter(request, system, &sources, cache)?; - if let Ok(ref found) = result { - warn_on_unsupported_python(found.interpreter()); + let result = find_toolchain(request, system, &sources, cache)?; + if let Ok(ref toolchain) = result { + warn_on_unsupported_python(toolchain.interpreter()); return Ok(result); } // If that fails, and a specific patch version was requested try again allowing a // different patch version if let Some(request) = match request { - InterpreterRequest::Version(version) => { + ToolchainRequest::Version(version) => { if version.has_patch() { - Some(InterpreterRequest::Version((*version).without_patch())) + Some(ToolchainRequest::Version((*version).without_patch())) } else { None } } - InterpreterRequest::ImplementationVersion(implementation, version) => Some( - InterpreterRequest::ImplementationVersion(*implementation, (*version).without_patch()), + ToolchainRequest::ImplementationVersion(implementation, version) => Some( + ToolchainRequest::ImplementationVersion(*implementation, (*version).without_patch()), ), _ => None, } { debug!("Looking for relaxed patch version {request}"); - let result = find_interpreter(&request, system, &sources, cache)?; - if let Ok(ref found) = result { - warn_on_unsupported_python(found.interpreter()); + let result = find_toolchain(&request, system, &sources, cache)?; + if let Ok(ref toolchain) = result { + warn_on_unsupported_python(toolchain.interpreter()); return Ok(result); } } // If a Python version was requested but cannot be fulfilled, just take any version - debug!("Looking for Python interpreter with any version"); - let request = InterpreterRequest::Any; - Ok(find_interpreter( + debug!("Looking for Python toolchain with any version"); + let request = ToolchainRequest::Any; + Ok(find_toolchain( // TODO(zanieb): Add a dedicated `Default` variant to `InterpreterRequest` &request, system, &sources, cache, )? .map_err(|err| { // Use a more general error in this case since we looked for multiple versions - if matches!(err, InterpreterNotFound::NoMatchingVersion(..)) { - InterpreterNotFound::NoPythonInstallation(sources.clone(), None) + if matches!(err, ToolchainNotFound::NoMatchingVersion(..)) { + ToolchainNotFound::NoPythonInstallation(sources.clone(), None) } else { err } @@ -865,7 +857,7 @@ fn is_windows_store_shim(_path: &Path) -> bool { false } -impl InterpreterRequest { +impl ToolchainRequest { /// Create a request from a string. /// /// This cannot fail, which means weird inputs will be parsed as [`InterpreterRequest::File`] or [`InterpreterRequest::ExecutableName`]. @@ -1132,38 +1124,38 @@ impl fmt::Display for VersionRequest { } } -impl SourceSelector { +impl ToolchainSources { /// Create a new [`SourceSelector::Some`] from an iterator. - pub(crate) fn from_sources(iter: impl IntoIterator) -> Self { + pub(crate) fn from_sources(iter: impl IntoIterator) -> Self { let inner = HashSet::from_iter(iter); assert!(!inner.is_empty(), "Source selectors cannot be empty"); Self::Custom(inner) } - /// Return true if this selector includes the given [`InterpreterSource`]. - fn contains(&self, source: InterpreterSource) -> bool { + /// Return true if this selector includes the given [`ToolchainSource`]. + fn contains(&self, source: ToolchainSource) -> bool { match self { Self::All(preview) => { // Always return `true` except for `ManagedToolchain` which requires preview mode - source != InterpreterSource::ManagedToolchain || preview.is_enabled() + source != ToolchainSource::Managed || preview.is_enabled() } Self::System(preview) => { [ - InterpreterSource::ProvidedPath, - InterpreterSource::SearchPath, + ToolchainSource::ProvidedPath, + ToolchainSource::SearchPath, #[cfg(windows)] - InterpreterSource::PyLauncher, - InterpreterSource::ParentInterpreter, + ToolchainSource::PyLauncher, + ToolchainSource::ParentInterpreter, ] .contains(&source) // Allow `ManagedToolchain` in preview - || (source == InterpreterSource::ManagedToolchain + || (source == ToolchainSource::Managed && preview.is_enabled()) } Self::VirtualEnv => [ - InterpreterSource::DiscoveredEnvironment, - InterpreterSource::ActiveEnvironment, - InterpreterSource::CondaPrefix, + ToolchainSource::DiscoveredEnvironment, + ToolchainSource::ActiveEnvironment, + ToolchainSource::CondaPrefix, ] .contains(&source), Self::Custom(sources) => sources.contains(&source), @@ -1174,15 +1166,15 @@ impl SourceSelector { pub fn from_settings(system: SystemPython, preview: PreviewMode) -> Self { if env::var_os("UV_FORCE_MANAGED_PYTHON").is_some() { debug!("Only considering managed toolchains due to `UV_FORCE_MANAGED_PYTHON`"); - Self::from_sources([InterpreterSource::ManagedToolchain]) + Self::from_sources([ToolchainSource::Managed]) } else if env::var_os("UV_TEST_PYTHON_PATH").is_some() { debug!( "Only considering search path, provided path, and active environments due to `UV_TEST_PYTHON_PATH`" ); Self::from_sources([ - InterpreterSource::ActiveEnvironment, - InterpreterSource::SearchPath, - InterpreterSource::ProvidedPath, + ToolchainSource::ActiveEnvironment, + ToolchainSource::SearchPath, + ToolchainSource::ProvidedPath, ]) } else { match system { @@ -1206,7 +1198,7 @@ impl SystemPython { } } -impl fmt::Display for InterpreterRequest { +impl fmt::Display for ToolchainRequest { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Any => write!(f, "any Python"), @@ -1224,7 +1216,7 @@ impl fmt::Display for InterpreterRequest { } } -impl fmt::Display for InterpreterSource { +impl fmt::Display for ToolchainSource { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::ProvidedPath => f.write_str("provided path"), @@ -1233,13 +1225,13 @@ impl fmt::Display for InterpreterSource { Self::DiscoveredEnvironment => f.write_str("virtual environment"), Self::SearchPath => f.write_str("search path"), Self::PyLauncher => f.write_str("`py` launcher output"), - Self::ManagedToolchain => f.write_str("managed toolchains"), + Self::Managed => f.write_str("managed toolchains"), Self::ParentInterpreter => f.write_str("parent interpreter"), } } } -impl fmt::Display for InterpreterNotFound { +impl fmt::Display for ToolchainNotFound { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::NoPythonInstallation(sources, None | Some(VersionRequest::Any)) => { @@ -1295,7 +1287,7 @@ impl fmt::Display for InterpreterNotFound { } } -impl fmt::Display for SourceSelector { +impl fmt::Display for ToolchainSources { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::All(_) => f.write_str("all sources"), @@ -1306,27 +1298,27 @@ impl fmt::Display for SourceSelector { write!( f, "{} or {}", - InterpreterSource::SearchPath, - InterpreterSource::PyLauncher + ToolchainSource::SearchPath, + ToolchainSource::PyLauncher ) } else { write!( f, "{}, {}, or {}", - InterpreterSource::SearchPath, - InterpreterSource::PyLauncher, - InterpreterSource::ManagedToolchain + ToolchainSource::SearchPath, + ToolchainSource::PyLauncher, + ToolchainSource::Managed ) } } else { if preview.is_disabled() { - write!(f, "{}", InterpreterSource::SearchPath) + write!(f, "{}", ToolchainSource::SearchPath) } else { write!( f, "{} or {}", - InterpreterSource::SearchPath, - InterpreterSource::ManagedToolchain + ToolchainSource::SearchPath, + ToolchainSource::Managed ) } } @@ -1335,7 +1327,7 @@ impl fmt::Display for SourceSelector { let sources: Vec<_> = sources .iter() .sorted() - .map(InterpreterSource::to_string) + .map(ToolchainSource::to_string) .collect(); match sources[..] { [] => unreachable!("Source selectors must contain at least one source"), @@ -1348,21 +1340,6 @@ impl fmt::Display for SourceSelector { } } -impl DiscoveredInterpreter { - #[allow(dead_code)] - pub fn source(&self) -> &InterpreterSource { - &self.source - } - - pub fn interpreter(&self) -> &Interpreter { - &self.interpreter - } - - pub fn into_interpreter(self) -> Interpreter { - self.interpreter - } -} - #[cfg(test)] mod tests { @@ -1373,74 +1350,74 @@ mod tests { use assert_fs::{prelude::*, TempDir}; use crate::{ - discovery::{InterpreterRequest, VersionRequest}, + discovery::{ToolchainRequest, VersionRequest}, implementation::ImplementationName, }; #[test] fn interpreter_request_from_str() { assert_eq!( - InterpreterRequest::parse("3.12"), - InterpreterRequest::Version(VersionRequest::from_str("3.12").unwrap()) + ToolchainRequest::parse("3.12"), + ToolchainRequest::Version(VersionRequest::from_str("3.12").unwrap()) ); assert_eq!( - InterpreterRequest::parse("foo"), - InterpreterRequest::ExecutableName("foo".to_string()) + ToolchainRequest::parse("foo"), + ToolchainRequest::ExecutableName("foo".to_string()) ); assert_eq!( - InterpreterRequest::parse("cpython"), - InterpreterRequest::Implementation(ImplementationName::CPython) + ToolchainRequest::parse("cpython"), + ToolchainRequest::Implementation(ImplementationName::CPython) ); assert_eq!( - InterpreterRequest::parse("cpython3.12.2"), - InterpreterRequest::ImplementationVersion( + ToolchainRequest::parse("cpython3.12.2"), + ToolchainRequest::ImplementationVersion( ImplementationName::CPython, VersionRequest::from_str("3.12.2").unwrap() ) ); assert_eq!( - InterpreterRequest::parse("pypy"), - InterpreterRequest::Implementation(ImplementationName::PyPy) + ToolchainRequest::parse("pypy"), + ToolchainRequest::Implementation(ImplementationName::PyPy) ); assert_eq!( - InterpreterRequest::parse("pypy3.10"), - InterpreterRequest::ImplementationVersion( + ToolchainRequest::parse("pypy3.10"), + ToolchainRequest::ImplementationVersion( ImplementationName::PyPy, VersionRequest::from_str("3.10").unwrap() ) ); assert_eq!( - InterpreterRequest::parse("pypy@3.10"), - InterpreterRequest::ImplementationVersion( + ToolchainRequest::parse("pypy@3.10"), + ToolchainRequest::ImplementationVersion( ImplementationName::PyPy, VersionRequest::from_str("3.10").unwrap() ) ); assert_eq!( - InterpreterRequest::parse("pypy310"), - InterpreterRequest::ExecutableName("pypy310".to_string()) + ToolchainRequest::parse("pypy310"), + ToolchainRequest::ExecutableName("pypy310".to_string()) ); let tempdir = TempDir::new().unwrap(); assert_eq!( - InterpreterRequest::parse(tempdir.path().to_str().unwrap()), - InterpreterRequest::Directory(tempdir.path().to_path_buf()), + ToolchainRequest::parse(tempdir.path().to_str().unwrap()), + ToolchainRequest::Directory(tempdir.path().to_path_buf()), "An existing directory is treated as a directory" ); assert_eq!( - InterpreterRequest::parse(tempdir.child("foo").path().to_str().unwrap()), - InterpreterRequest::File(tempdir.child("foo").path().to_path_buf()), + ToolchainRequest::parse(tempdir.child("foo").path().to_str().unwrap()), + ToolchainRequest::File(tempdir.child("foo").path().to_path_buf()), "A path that does not exist is treated as a file" ); tempdir.child("bar").touch().unwrap(); assert_eq!( - InterpreterRequest::parse(tempdir.child("bar").path().to_str().unwrap()), - InterpreterRequest::File(tempdir.child("bar").path().to_path_buf()), + ToolchainRequest::parse(tempdir.child("bar").path().to_str().unwrap()), + ToolchainRequest::File(tempdir.child("bar").path().to_path_buf()), "An existing file is treated as a file" ); assert_eq!( - InterpreterRequest::parse("./foo"), - InterpreterRequest::File(PathBuf::from_str("./foo").unwrap()), + ToolchainRequest::parse("./foo"), + ToolchainRequest::File(PathBuf::from_str("./foo").unwrap()), "A string with a file system separator is treated as a file" ); } diff --git a/crates/uv-toolchain/src/downloads.rs b/crates/uv-toolchain/src/downloads.rs index f5cc5f8ad918..57fd45d41416 100644 --- a/crates/uv-toolchain/src/downloads.rs +++ b/crates/uv-toolchain/src/downloads.rs @@ -24,31 +24,31 @@ pub enum Error { PlatformError(#[from] PlatformError), #[error(transparent)] ImplementationError(#[from] ImplementationError), - #[error("invalid python version: {0}")] + #[error("Invalid python version: {0}")] InvalidPythonVersion(String), - #[error("download failed")] + #[error("Download failed")] NetworkError(#[from] BetterReqwestError), - #[error("download failed")] + #[error("Download failed")] NetworkMiddlewareError(#[source] anyhow::Error), #[error(transparent)] ExtractError(#[from] uv_extract::Error), - #[error("invalid download url")] + #[error("Invalid download url")] InvalidUrl(#[from] url::ParseError), - #[error("failed to create download directory")] + #[error("Failed to create download directory")] DownloadDirError(#[source] io::Error), - #[error("failed to copy to: {0}", to.user_display())] + #[error("Failed to copy to: {0}", to.user_display())] CopyError { to: PathBuf, #[source] err: io::Error, }, - #[error("failed to read toolchain directory: {0}", dir.user_display())] + #[error("Failed to read toolchain directory: {0}", dir.user_display())] ReadError { dir: PathBuf, #[source] err: io::Error, }, - #[error("failed to parse toolchain directory name: {0}")] + #[error("Failed to parse toolchain directory name: {0}")] NameError(String), } diff --git a/crates/uv-toolchain/src/environment.rs b/crates/uv-toolchain/src/environment.rs index 71b8ed0233d6..fbe737c9b103 100644 --- a/crates/uv-toolchain/src/environment.rs +++ b/crates/uv-toolchain/src/environment.rs @@ -4,15 +4,11 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use uv_cache::Cache; -use uv_configuration::PreviewMode; use uv_fs::{LockedFile, Simplified}; -use crate::discovery::{InterpreterRequest, SourceSelector, SystemPython}; +use crate::toolchain::Toolchain; use crate::virtualenv::{virtualenv_python_executable, PyVenvConfiguration}; -use crate::{ - find_default_interpreter, find_interpreter, Error, Interpreter, InterpreterSource, Prefix, - Target, -}; +use crate::{Error, Interpreter, Prefix, Target}; /// A Python environment, consisting of a Python [`Interpreter`] and its associated paths. #[derive(Debug, Clone)] @@ -25,83 +21,13 @@ struct PythonEnvironmentShared { } impl PythonEnvironment { - /// Find a [`PythonEnvironment`]. - /// - /// This is the standard interface for discovering a Python environment for use with uv. - pub fn find( - python: Option<&str>, - system: SystemPython, - preview: PreviewMode, - cache: &Cache, - ) -> Result { - // Detect the current Python interpreter. - if let Some(python) = python { - Self::from_requested_python(python, system, preview, cache) - } else if system.is_preferred() { - Self::from_default_python(preview, cache) - } else { - // First check for a parent intepreter - // We gate this check to avoid an extra log message when it is not set - if std::env::var_os("UV_INTERNAL__PARENT_INTERPRETER").is_some() { - match Self::from_parent_interpreter(system, cache) { - Ok(env) => return Ok(env), - Err(Error::NotFound(_)) => {} - Err(err) => return Err(err), - } - } - - // Then a virtual environment - match Self::from_virtualenv(cache) { - Ok(venv) => Ok(venv), - Err(Error::NotFound(_)) if system.is_allowed() => { - Self::from_default_python(preview, cache) - } - Err(err) => Err(err), - } - } - } - - /// Create a [`PythonEnvironment`] for an existing virtual environment. - /// - /// Allows Conda environments (via `CONDA_PREFIX`) though they are not technically virtual environments. - pub fn from_virtualenv(cache: &Cache) -> Result { - let sources = SourceSelector::VirtualEnv; - let request = InterpreterRequest::Any; - let found = find_interpreter(&request, SystemPython::Disallowed, &sources, cache)??; - - debug_assert!( - found.interpreter().is_virtualenv() - || matches!(found.source(), InterpreterSource::CondaPrefix), - "Not a virtualenv (source: {}, prefix: {})", - found.source(), - found.interpreter().sys_base_prefix().display() - ); - - Ok(Self(Arc::new(PythonEnvironmentShared { - root: found.interpreter().sys_prefix().to_path_buf(), - interpreter: found.into_interpreter(), - }))) - } - - /// Create a [`PythonEnvironment`] for the parent interpreter i.e. the executable in `python -m uv ...` - pub fn from_parent_interpreter(system: SystemPython, cache: &Cache) -> Result { - let sources = SourceSelector::from_sources([InterpreterSource::ParentInterpreter]); - let request = InterpreterRequest::Any; - let found = find_interpreter(&request, system, &sources, cache)??; - - Ok(Self(Arc::new(PythonEnvironmentShared { - root: found.interpreter().sys_prefix().to_path_buf(), - interpreter: found.into_interpreter(), - }))) - } - /// Create a [`PythonEnvironment`] from the virtual environment at the given root. - pub fn from_root(root: &Path, cache: &Cache) -> Result { - let venv = match fs_err::canonicalize(root) { + pub fn from_root(root: impl AsRef, cache: &Cache) -> Result { + let venv = match fs_err::canonicalize(root.as_ref()) { Ok(venv) => venv, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { return Err(Error::NotFound( - crate::InterpreterNotFound::DirectoryNotFound(root.to_path_buf()), + crate::ToolchainNotFound::DirectoryNotFound(root.as_ref().to_path_buf()), )); } Err(err) => return Err(Error::Discovery(err.into())), @@ -115,29 +41,9 @@ impl PythonEnvironment { }))) } - /// Create a [`PythonEnvironment`] for a Python interpreter specifier (e.g., a path or a binary name). - pub fn from_requested_python( - request: &str, - system: SystemPython, - preview: PreviewMode, - cache: &Cache, - ) -> Result { - let sources = SourceSelector::from_settings(system, preview); - let request = InterpreterRequest::parse(request); - let interpreter = find_interpreter(&request, system, &sources, cache)??.into_interpreter(); - Ok(Self(Arc::new(PythonEnvironmentShared { - root: interpreter.sys_prefix().to_path_buf(), - interpreter, - }))) - } - - /// Create a [`PythonEnvironment`] for the default Python interpreter. - pub fn from_default_python(preview: PreviewMode, cache: &Cache) -> Result { - let interpreter = find_default_interpreter(preview, cache)??.into_interpreter(); - Ok(Self(Arc::new(PythonEnvironmentShared { - root: interpreter.sys_prefix().to_path_buf(), - interpreter, - }))) + /// Create a [`PythonEnvironment`] from an existing [`Toolchain`]. + pub fn from_toolchain(toolchain: Toolchain) -> Self { + Self::from_interpreter(toolchain.into_interpreter()) } /// Create a [`PythonEnvironment`] from an existing [`Interpreter`]. diff --git a/crates/uv-toolchain/src/lib.rs b/crates/uv-toolchain/src/lib.rs index 1958dcab81fa..63d4e0d41ad3 100644 --- a/crates/uv-toolchain/src/lib.rs +++ b/crates/uv-toolchain/src/lib.rs @@ -2,8 +2,8 @@ use thiserror::Error; pub use crate::discovery::{ - find_best_interpreter, find_default_interpreter, find_interpreter, Error as DiscoveryError, - InterpreterNotFound, InterpreterRequest, InterpreterSource, SourceSelector, SystemPython, + find_best_toolchain, find_default_toolchain, find_toolchain, Error as DiscoveryError, + SystemPython, ToolchainNotFound, ToolchainRequest, ToolchainSource, ToolchainSources, VersionRequest, }; pub use crate::environment::PythonEnvironment; @@ -12,6 +12,7 @@ pub use crate::pointer_size::PointerSize; pub use crate::prefix::Prefix; pub use crate::python_version::PythonVersion; pub use crate::target::Target; +pub use crate::toolchain::Toolchain; pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment}; mod discovery; @@ -26,6 +27,7 @@ mod prefix; mod py_launcher; mod python_version; mod target; +mod toolchain; mod virtualenv; #[cfg(not(test))] @@ -56,7 +58,7 @@ pub enum Error { PyLauncher(#[from] py_launcher::Error), #[error(transparent)] - NotFound(#[from] InterpreterNotFound), + NotFound(#[from] ToolchainNotFound), } // The mock interpreters are not valid on Windows so we don't have unit test coverage there @@ -80,11 +82,10 @@ mod tests { use uv_configuration::PreviewMode; use crate::{ - discovery::DiscoveredInterpreter, find_best_interpreter, find_default_interpreter, - find_interpreter, implementation::ImplementationName, managed::InstalledToolchains, - virtualenv::virtualenv_python_executable, Error, InterpreterNotFound, InterpreterRequest, - InterpreterSource, PythonEnvironment, PythonVersion, SourceSelector, SystemPython, - VersionRequest, + find_default_toolchain, find_toolchain, implementation::ImplementationName, + managed::InstalledToolchains, toolchain::Toolchain, + virtualenv::virtualenv_python_executable, Error, PythonVersion, SystemPython, + ToolchainNotFound, ToolchainRequest, ToolchainSource, ToolchainSources, VersionRequest, }; struct TestContext { @@ -150,6 +151,8 @@ mod tests { let mut run_vars = vec![ // Ensure `PATH` is used ("UV_TEST_PYTHON_PATH", None), + // Ignore active virtual envrionments (i.e. that the dev is using) + ("VIRTUAL_ENV", None), ("PATH", path.as_deref()), // Use the temporary toolchain directory ("UV_TOOLCHAIN_DIR", Some(self.toolchains.root().as_os_str())), @@ -384,24 +387,16 @@ mod tests { let mut context = TestContext::new()?; context.search_path = Some(vec![]); - let result = - context.run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache)); + let result = context.run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache)); assert!( - matches!( - result, - Ok(Err(InterpreterNotFound::NoPythonInstallation(..))) - ), + matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))), "With an empty path, no Python installation should be detected got {result:?}" ); context.search_path = None; - let result = - context.run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache)); + let result = context.run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache)); assert!( - matches!( - result, - Ok(Err(InterpreterNotFound::NoPythonInstallation(..))) - ), + matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))), "With an unset path, no Python installation should be detected got {result:?}" ); @@ -416,12 +411,11 @@ mod tests { .child(format!("python{}", env::consts::EXE_SUFFIX)) .touch()?; - let result = - context.run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache)); + let result = context.run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache)); assert!( matches!( result, - Ok(Err(InterpreterNotFound::NoPythonInstallation(..))) + Ok(Err(ToolchainNotFound::NoPythonInstallation(..))) ), "With an non-executable Python, no Python installation should be detected; got {result:?}" ); @@ -435,12 +429,12 @@ mod tests { context.add_python_versions(&["3.12.1"])?; let interpreter = - context.run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache))??; + context.run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache))??; assert!( matches!( interpreter, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), @@ -488,19 +482,19 @@ mod tests { true, )?; - let found = - context.run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache))??; + let toolchain = + context.run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache))??; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should skip the bad executables in favor of the good one; got {found:?}" + "We should skip the bad executables in favor of the good one; got {toolchain:?}" ); - assert_eq!(found.interpreter().sys_executable(), python); + assert_eq!(toolchain.interpreter().sys_executable(), python); Ok(()) } @@ -514,12 +508,12 @@ mod tests { TestContext::create_mock_python2_interpreter(&python)?; let result = context - .run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache)) - .expect("An environment should be found"); + .run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache)) + .expect("An toolchain should be toolchain"); assert!( - matches!(result, Err(InterpreterNotFound::NoPythonInstallation(..))), + matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))), // TODO(zanieb): We could improve the error handling to hint this to the user - "If only Python 2 is available, we should not find an interpreter; got {result:?}" + "If only Python 2 is available, we should not find a toolchain; got {result:?}" ); Ok(()) @@ -544,41 +538,41 @@ mod tests { true, )?; - let found = - context.run(|| find_default_interpreter(PreviewMode::Disabled, &context.cache))??; + let toolchain = + context.run(|| find_default_toolchain(PreviewMode::Disabled, &context.cache))??; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should skip the Python 2 installation and find the Python 3 interpreter; got {found:?}" + "We should skip the Python 2 installation and find the Python 3 interpreter; got {toolchain:?}" ); - assert_eq!(found.interpreter().sys_executable(), python3.path()); + assert_eq!(toolchain.interpreter().sys_executable(), python3.path()); Ok(()) } #[test] - fn find_interpreter_system_python_allowed() -> Result<()> { + fn find_toolchain_system_python_allowed() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[ (false, ImplementationName::CPython, "python", "3.10.0"), (true, ImplementationName::CPython, "python", "3.10.1"), ])?; - let found = context.run(|| { - find_interpreter( - &InterpreterRequest::Any, + let toolchain = context.run(|| { + find_toolchain( + &ToolchainRequest::Any, SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })??; assert_eq!( - found.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "Should find the first interpreter regardless of system" ); @@ -590,16 +584,16 @@ mod tests { (false, ImplementationName::CPython, "python", "3.10.0"), ])?; - let found = context.run(|| { - find_interpreter( - &InterpreterRequest::Any, + let toolchain = context.run(|| { + find_toolchain( + &ToolchainRequest::Any, SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })??; assert_eq!( - found.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "Should find the first interpreter regardless of system" ); @@ -608,23 +602,23 @@ mod tests { } #[test] - fn find_interpreter_system_python_required() -> Result<()> { + fn find_toolchain_system_python_required() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[ (false, ImplementationName::CPython, "python", "3.10.0"), (true, ImplementationName::CPython, "python", "3.10.1"), ])?; - let found = context.run(|| { - find_interpreter( - &InterpreterRequest::Any, + let toolchain = context.run(|| { + find_toolchain( + &ToolchainRequest::Any, SystemPython::Required, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })??; assert_eq!( - found.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "Should skip the virtual environment" ); @@ -633,23 +627,23 @@ mod tests { } #[test] - fn find_interpreter_system_python_disallowed() -> Result<()> { + fn find_toolchain_system_python_disallowed() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[ (true, ImplementationName::CPython, "python", "3.10.0"), (false, ImplementationName::CPython, "python", "3.10.1"), ])?; - let found = context.run(|| { - find_interpreter( - &InterpreterRequest::Any, + let toolchain = context.run(|| { + find_toolchain( + &ToolchainRequest::Any, SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })??; assert_eq!( - found.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "Should skip the system Python" ); @@ -658,31 +652,31 @@ mod tests { } #[test] - fn find_interpreter_version_minor() -> Result<()> { + fn find_toolchain_version_minor() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; - let found = context.run(|| { - find_interpreter( - &InterpreterRequest::parse("3.11"), + let toolchain = context.run(|| { + find_toolchain( + &ToolchainRequest::parse("3.11"), SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })??; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should find an interpreter; got {found:?}" + "We should find a toolchain; got {toolchain:?}" ); assert_eq!( - &found.interpreter().python_full_version().to_string(), + &toolchain.interpreter().python_full_version().to_string(), "3.11.2", "We should find the correct interpreter for the request" ); @@ -691,31 +685,31 @@ mod tests { } #[test] - fn find_interpreter_version_patch() -> Result<()> { + fn find_toolchain_version_patch() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.3", "3.11.2", "3.12.3"])?; - let found = context.run(|| { - find_interpreter( - &InterpreterRequest::parse("3.11.2"), + let toolchain = context.run(|| { + find_toolchain( + &ToolchainRequest::parse("3.11.2"), SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })??; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should find an interpreter; got {found:?}" + "We should find a toolchain; got {toolchain:?}" ); assert_eq!( - &found.interpreter().python_full_version().to_string(), + &toolchain.interpreter().python_full_version().to_string(), "3.11.2", "We should find the correct interpreter for the request" ); @@ -724,85 +718,85 @@ mod tests { } #[test] - fn find_interpreter_version_minor_no_match() -> Result<()> { + fn find_toolchain_version_minor_no_match() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; let result = context.run(|| { - find_interpreter( - &InterpreterRequest::parse("3.9"), + find_toolchain( + &ToolchainRequest::parse("3.9"), SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })?; assert!( matches!( result, - Err(InterpreterNotFound::NoMatchingVersion( + Err(ToolchainNotFound::NoMatchingVersion( _, VersionRequest::MajorMinor(3, 9) )) ), - "We should not find an interpreter; got {result:?}" + "We should not find a toolchain; got {result:?}" ); Ok(()) } #[test] - fn find_interpreter_version_patch_no_match() -> Result<()> { + fn find_toolchain_version_patch_no_match() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; let result = context.run(|| { - find_interpreter( - &InterpreterRequest::parse("3.11.9"), + find_toolchain( + &ToolchainRequest::parse("3.11.9"), SystemPython::Allowed, - &SourceSelector::All(PreviewMode::Disabled), + &ToolchainSources::All(PreviewMode::Disabled), &context.cache, ) })?; assert!( matches!( result, - Err(InterpreterNotFound::NoMatchingVersion( + Err(ToolchainNotFound::NoMatchingVersion( _, VersionRequest::MajorMinorPatch(3, 11, 9) )) ), - "We should not find an interpreter; got {result:?}" + "We should not find a toolchain; got {result:?}" ); Ok(()) } #[test] - fn find_best_interpreter_version_patch_exact() -> Result<()> { + fn find_best_toolchain_version_patch_exact() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.2", "3.11.4", "3.11.3", "3.12.5"])?; - let found = context.run(|| { - find_best_interpreter( - &InterpreterRequest::parse("3.11.3"), + let toolchain = context.run(|| { + Toolchain::find_best( + &ToolchainRequest::parse("3.11.3"), SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) - })??; + })?; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should find an interpreter; got {found:?}" + "We should find a toolchain; got {toolchain:?}" ); assert_eq!( - &found.interpreter().python_full_version().to_string(), + &toolchain.interpreter().python_full_version().to_string(), "3.11.3", "We should prefer the exact request" ); @@ -811,31 +805,31 @@ mod tests { } #[test] - fn find_best_interpreter_version_patch_fallback() -> Result<()> { + fn find_best_toolchain_version_patch_fallback() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.2", "3.11.4", "3.11.3", "3.12.5"])?; - let found = context.run(|| { - find_best_interpreter( - &InterpreterRequest::parse("3.11.11"), + let toolchain = context.run(|| { + Toolchain::find_best( + &ToolchainRequest::parse("3.11.11"), SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) - })??; + })?; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should find an interpreter; got {found:?}" + "We should find a toolchain; got {toolchain:?}" ); assert_eq!( - &found.interpreter().python_full_version().to_string(), + &toolchain.interpreter().python_full_version().to_string(), "3.11.2", "We should fallback to the first matching minor" ); @@ -844,61 +838,63 @@ mod tests { } #[test] - fn find_best_interpreter_skips_source_without_match() -> Result<()> { + fn find_best_toolchain_skips_source_without_match() -> Result<()> { let mut context = TestContext::new()?; let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.12.0")?; context.add_python_versions(&["3.10.1"])?; - let found = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_best_interpreter( - &InterpreterRequest::parse("3.10"), - SystemPython::Allowed, - PreviewMode::Disabled, - &context.cache, - ) - })??; + let toolchain = + context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { + Toolchain::find_best( + &ToolchainRequest::parse("3.10"), + SystemPython::Allowed, + PreviewMode::Disabled, + &context.cache, + ) + })?; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::SearchPath, + toolchain, + Toolchain { + source: ToolchainSource::SearchPath, interpreter: _ } ), - "We should skip the active environment in favor of the requested version; got {found:?}" + "We should skip the active environment in favor of the requested version; got {toolchain:?}" ); Ok(()) } #[test] - fn find_best_interpreter_returns_to_earlier_source_on_fallback() -> Result<()> { + fn find_best_toolchain_returns_to_earlier_source_on_fallback() -> Result<()> { let mut context = TestContext::new()?; let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.10.1")?; context.add_python_versions(&["3.10.3"])?; - let found = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_best_interpreter( - &InterpreterRequest::parse("3.10.2"), - SystemPython::Allowed, - PreviewMode::Disabled, - &context.cache, - ) - })??; + let toolchain = + context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { + Toolchain::find_best( + &ToolchainRequest::parse("3.10.2"), + SystemPython::Allowed, + PreviewMode::Disabled, + &context.cache, + ) + })?; assert!( matches!( - found, - DiscoveredInterpreter { - source: InterpreterSource::ActiveEnvironment, + toolchain, + Toolchain { + source: ToolchainSource::ActiveEnvironment, interpreter: _ } ), - "We should prefer the active environment after relaxing; got {found:?}" + "We should prefer the active environment after relaxing; got {toolchain:?}" ); assert_eq!( - found.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should prefer the active environment" ); @@ -907,14 +903,14 @@ mod tests { } #[test] - fn find_environment_from_active_environment() -> Result<()> { + fn find_toolchain_from_active_toolchain() -> Result<()> { let context = TestContext::new()?; let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.12.0")?; - let environment = + let toolchain = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -922,7 +918,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", "We should prefer the active environment" ); @@ -931,15 +927,15 @@ mod tests { } #[test] - fn find_environment_from_conda_prefix() -> Result<()> { + fn find_toolchain_from_conda_prefix() -> Result<()> { let context = TestContext::new()?; let condaenv = context.tempdir.child("condaenv"); TestContext::mock_conda_prefix(&condaenv, "3.12.0")?; - let environment = + let toolchain = context.run_with_vars(&[("CONDA_PREFIX", Some(condaenv.as_os_str()))], || { - // Note this environment is not treated as a system interpreter - PythonEnvironment::find( + // Note this toolchain is not treated as a system interpreter + Toolchain::find( None, SystemPython::Disallowed, PreviewMode::Disabled, @@ -947,29 +943,29 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", - "We should allow the active conda environment" + "We should allow the active conda toolchain" ); Ok(()) } #[test] - fn find_environment_from_conda_prefix_and_virtualenv() -> Result<()> { + fn find_toolchain_from_conda_prefix_and_virtualenv() -> Result<()> { let context = TestContext::new()?; let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.12.0")?; let condaenv = context.tempdir.child("condaenv"); TestContext::mock_conda_prefix(&condaenv, "3.12.1")?; - let environment = context.run_with_vars( + let toolchain = context.run_with_vars( &[ ("VIRTUAL_ENV", Some(venv.as_os_str())), ("CONDA_PREFIX", Some(condaenv.as_os_str())), ], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -978,17 +974,17 @@ mod tests { }, )?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", - "We should prefer the non-conda environment" + "We should prefer the non-conda toolchain" ); // Put a virtual environment in the working directory let venv = context.workdir.child(".venv"); TestContext::mock_venv(venv, "3.12.2")?; - let environment = + let toolchain = context.run_with_vars(&[("CONDA_PREFIX", Some(condaenv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -996,52 +992,52 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.1", - "We should prefer the conda environment over inactive virtual environments" + "We should prefer the conda toolchain over inactive virtual environments" ); Ok(()) } #[test] - fn find_environment_from_discovered_environment() -> Result<()> { + fn find_toolchain_from_discovered_toolchain() -> Result<()> { let mut context = TestContext::new()?; // Create a virtual environment in a parent of the workdir let venv = context.tempdir.child(".venv"); TestContext::mock_venv(venv, "3.12.0")?; - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", - "We should find the environment" + "We should find the toolchain" ); // Add some system versions to ensure we don't use those context.add_python_versions(&["3.12.1", "3.12.2"])?; - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", "We should prefer the discovered virtual environment over available system versions" ); @@ -1050,7 +1046,7 @@ mod tests { } #[test] - fn find_environment_skips_broken_active_environment() -> Result<()> { + fn find_toolchain_skips_broken_active_toolchain() -> Result<()> { let context = TestContext::new()?; let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.12.0")?; @@ -1058,9 +1054,9 @@ mod tests { // Delete the pyvenv cfg to break the virtualenv fs_err::remove_file(venv.join("pyvenv.cfg"))?; - let environment = + let toolchain = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -1068,9 +1064,9 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", - // TODO(zanieb): We should skip this environment, why don't we? + // TODO(zanieb): We should skip this toolchain, why don't we? "We should prefer the active environment" ); @@ -1078,7 +1074,7 @@ mod tests { } #[test] - fn find_environment_from_parent_interpreter() -> Result<()> { + fn find_toolchain_from_parent_interpreter() -> Result<()> { let mut context = TestContext::new()?; let parent = context.tempdir.child("python").to_path_buf(); @@ -1090,10 +1086,10 @@ mod tests { true, )?; - let environment = context.run_with_vars( + let toolchain = context.run_with_vars( &[("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -1102,7 +1098,7 @@ mod tests { }, )?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", "We should find the parent interpreter" ); @@ -1111,13 +1107,13 @@ mod tests { let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.12.2")?; context.add_python_versions(&["3.12.3"])?; - let environment = context.run_with_vars( + let toolchain = context.run_with_vars( &[ ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), ("VIRTUAL_ENV", Some(venv.as_os_str())), ], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -1126,19 +1122,19 @@ mod tests { }, )?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", "We should prefer the parent interpreter" ); // Test with `SystemPython::Explicit` - let environment = context.run_with_vars( + let toolchain = context.run_with_vars( &[ ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), ("VIRTUAL_ENV", Some(venv.as_os_str())), ], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Explicit, PreviewMode::Disabled, @@ -1147,19 +1143,19 @@ mod tests { }, )?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.0", "We should prefer the parent interpreter" ); // Test with `SystemPython::Disallowed` - let environment = context.run_with_vars( + let toolchain = context.run_with_vars( &[ ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), ("VIRTUAL_ENV", Some(venv.as_os_str())), ], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Disallowed, PreviewMode::Disabled, @@ -1168,7 +1164,7 @@ mod tests { }, )?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.2", "We find the virtual environment Python because a system is explicitly not allowed" ); @@ -1177,16 +1173,16 @@ mod tests { } #[test] - fn find_environment_active_environment_skipped_if_system_required() -> Result<()> { + fn find_toolchain_active_toolchain_skipped_if_system_required() -> Result<()> { let mut context = TestContext::new()?; let venv = context.tempdir.child(".venv"); TestContext::mock_venv(&venv, "3.9.0")?; context.add_python_versions(&["3.10.0", "3.11.1", "3.12.2"])?; // Without a specific request - let environment = + let toolchain = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Required, PreviewMode::Disabled, @@ -1194,15 +1190,15 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should skip the active environment" ); // With a requested minor version - let environment = + let toolchain = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( Some("3.12"), SystemPython::Required, PreviewMode::Disabled, @@ -1210,14 +1206,14 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.12.2", "We should skip the active environment" ); - // With a patch version that cannot be found + // With a patch version that cannot be toolchain let result = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( Some("3.12.3"), SystemPython::Required, PreviewMode::Disabled, @@ -1226,19 +1222,19 @@ mod tests { }); assert!( result.is_err(), - "We should not find an environment; got {result:?}" + "We should not find an toolchain; got {result:?}" ); Ok(()) } #[test] - fn find_environment_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> { + fn find_toolchain_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> { let mut context = TestContext::new()?; context.add_python_versions(&["3.10.1", "3.11.2"])?; let result = context.run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Disallowed, PreviewMode::Disabled, @@ -1248,19 +1244,19 @@ mod tests { assert!( matches!( result, - Err(Error::NotFound(InterpreterNotFound::NoPythonInstallation( - SourceSelector::VirtualEnv, + Err(Error::NotFound(ToolchainNotFound::NoPythonInstallation( + ToolchainSources::VirtualEnv, None ))) ), - "We should not find an environment; got {result:?}" + "We should not find an toolchain; got {result:?}" ); // With an invalid virtual environment variable let result = context.run_with_vars( &[("VIRTUAL_ENV", Some(context.tempdir.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( Some("3.12.3"), SystemPython::Required, PreviewMode::Disabled, @@ -1271,23 +1267,23 @@ mod tests { assert!( matches!( result, - Err(Error::NotFound(InterpreterNotFound::NoMatchingVersion( - SourceSelector::System(PreviewMode::Disabled), + Err(Error::NotFound(ToolchainNotFound::NoMatchingVersion( + ToolchainSources::System(PreviewMode::Disabled), VersionRequest::MajorMinorPatch(3, 12, 3) ))) ), - "We should not find an environment; got {result:?}" + "We should not find an toolchain; got {result:?}" ); Ok(()) } #[test] - fn find_environment_allows_name_in_working_directory() -> Result<()> { + fn find_toolchain_allows_name_in_working_directory() -> Result<()> { let context = TestContext::new()?; context.add_python_to_workdir("foobar", "3.10.0")?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("foobar"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1295,13 +1291,13 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the named executbale" ); let result = context.run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -1314,7 +1310,7 @@ mod tests { ); let result = context.run(|| { - PythonEnvironment::find( + Toolchain::find( Some("3.10.0"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1330,7 +1326,7 @@ mod tests { } #[test] - fn find_environment_allows_relative_file_path() -> Result<()> { + fn find_toolchain_allows_relative_file_path() -> Result<()> { let mut context = TestContext::new()?; let python = context.workdir.child("foo").join("bar"); TestContext::create_mock_interpreter( @@ -1340,8 +1336,8 @@ mod tests { true, )?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("./foo/bar"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1349,14 +1345,14 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the `bar` executable" ); context.add_python_versions(&["3.11.1"])?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("./foo/bar"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1364,7 +1360,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should prefer the `bar` executable over the system and virtualenvs" ); @@ -1373,7 +1369,7 @@ mod tests { } #[test] - fn find_environment_allows_absolute_file_path() -> Result<()> { + fn find_toolchain_allows_absolute_file_path() -> Result<()> { let mut context = TestContext::new()?; let python = context.tempdir.child("foo").join("bar"); TestContext::create_mock_interpreter( @@ -1383,8 +1379,8 @@ mod tests { true, )?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some(python.to_str().unwrap()), SystemPython::Allowed, PreviewMode::Disabled, @@ -1392,14 +1388,14 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the `bar` executable" ); // With `SystemPython::Explicit - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some(python.to_str().unwrap()), SystemPython::Explicit, PreviewMode::Disabled, @@ -1407,13 +1403,13 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should allow the `bar` executable with explicit system" ); let result = context.run(|| { - PythonEnvironment::find( + Toolchain::find( Some(python.to_str().unwrap()), SystemPython::Disallowed, PreviewMode::Disabled, @@ -1424,11 +1420,7 @@ mod tests { matches!( result, Err(Error::Discovery( - crate::discovery::Error::SourceNotSelected( - _, - InterpreterSource::ProvidedPath, - _ - ) + crate::discovery::Error::SourceNotSelected(_, ToolchainSource::ProvidedPath, _) )) ), // TODO(zanieb): We should allow this, just enforce it's a virtualenv @@ -1436,8 +1428,8 @@ mod tests { ); context.add_python_versions(&["3.11.1"])?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some(python.to_str().unwrap()), SystemPython::Allowed, PreviewMode::Disabled, @@ -1445,7 +1437,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should prefer the `bar` executable over the system and virtualenvs" ); @@ -1454,13 +1446,13 @@ mod tests { } #[test] - fn find_environment_allows_venv_directory_path() -> Result<()> { + fn find_toolchain_allows_venv_directory_path() -> Result<()> { let mut context = TestContext::new()?; let venv = context.tempdir.child("foo").child(".venv"); TestContext::mock_venv(&venv, "3.10.0")?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("../foo/.venv"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1468,13 +1460,13 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the relative venv path" ); - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some(venv.to_str().unwrap()), SystemPython::Allowed, PreviewMode::Disabled, @@ -1482,7 +1474,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the absolute venv path" ); @@ -1495,8 +1487,8 @@ mod tests { ImplementationName::default(), true, )?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some(context.tempdir.child("bar").to_str().unwrap()), SystemPython::Allowed, PreviewMode::Disabled, @@ -1504,7 +1496,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the executable in the directory" ); @@ -1512,9 +1504,9 @@ mod tests { let other_venv = context.tempdir.child("foobar").child(".venv"); TestContext::mock_venv(&other_venv, "3.11.1")?; context.add_python_versions(&["3.12.2"])?; - let environment = + let toolchain = context.run_with_vars(&[("VIRTUAL_ENV", Some(other_venv.as_os_str()))], || { - PythonEnvironment::find( + Toolchain::find( Some(venv.to_str().unwrap()), SystemPython::Allowed, PreviewMode::Disabled, @@ -1522,21 +1514,21 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", - "We should prefer the requested directory over the system and active virtul environments" + "We should prefer the requested directory over the system and active virtul toolchains" ); Ok(()) } #[test] - fn find_environment_treats_missing_file_path_as_file() -> Result<()> { + fn find_toolchain_treats_missing_file_path_as_file() -> Result<()> { let context = TestContext::new()?; context.workdir.child("foo").create_dir_all()?; let result = context.run(|| { - PythonEnvironment::find( + Toolchain::find( Some("./foo/bar"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1546,7 +1538,7 @@ mod tests { assert!( matches!( result, - Err(Error::NotFound(InterpreterNotFound::FileNotFound(_))) + Err(Error::NotFound(ToolchainNotFound::FileNotFound(_))) ), "We should not find the file; got {result:?}" ); @@ -1555,7 +1547,7 @@ mod tests { } #[test] - fn find_environment_executable_name_in_search_path() -> Result<()> { + fn find_toolchain_executable_name_in_search_path() -> Result<()> { let mut context = TestContext::new()?; let python = context.tempdir.child("foo").join("bar"); TestContext::create_mock_interpreter( @@ -1566,18 +1558,18 @@ mod tests { )?; context.add_to_search_path(context.tempdir.child("foo").to_path_buf()); - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( Some("bar"), SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should find the `bar` executable" ); @@ -1586,12 +1578,12 @@ mod tests { } #[test] - fn find_environment_pypy() -> Result<()> { + fn find_toolchain_pypy() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[(true, ImplementationName::PyPy, "pypy", "3.10.0")])?; let result = context.run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, @@ -1606,34 +1598,34 @@ mod tests { // But we should find it context.reset_search_path(); context.add_python_interpreters(&[(true, ImplementationName::PyPy, "python", "3.10.1")])?; - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should find the pypy interpreter if it's the only one" ); - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( Some("pypy"), SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should find the pypy interpreter if it's requested" ); @@ -1642,41 +1634,41 @@ mod tests { } #[test] - fn find_environment_pypy_request_ignores_cpython() -> Result<()> { + fn find_toolchain_pypy_request_ignores_cpython() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[ (true, ImplementationName::CPython, "python", "3.10.0"), (true, ImplementationName::PyPy, "pypy", "3.10.1"), ])?; - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( Some("pypy"), SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should skip the CPython interpreter" ); - let environment = context + let toolchain = context .run(|| { - PythonEnvironment::find( + Toolchain::find( None, SystemPython::Allowed, PreviewMode::Disabled, &context.cache, ) }) - .expect("An environment should be found"); + .expect("An toolchain should be toolchain"); assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should take the first interpreter without a specific request" ); @@ -1685,15 +1677,15 @@ mod tests { } #[test] - fn find_environment_pypy_request_skips_wrong_versions() -> Result<()> { + fn find_toolchain_pypy_request_skips_wrong_versions() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[ (true, ImplementationName::PyPy, "pypy", "3.9"), (true, ImplementationName::PyPy, "pypy", "3.10.1"), ])?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("pypy3.10"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1701,7 +1693,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should skip the first interpreter" ); @@ -1710,7 +1702,7 @@ mod tests { } #[test] - fn find_environment_pypy_finds_executable_with_version_name() -> Result<()> { + fn find_toolchain_pypy_finds_executable_with_version_name() -> Result<()> { let mut context = TestContext::new()?; context.add_python_interpreters(&[ (true, ImplementationName::PyPy, "pypy3.9", "3.10.0"), // We don't consider this one because of the executable name @@ -1718,8 +1710,8 @@ mod tests { (true, ImplementationName::PyPy, "pypy", "3.10.2"), ])?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("pypy@3.10"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1727,7 +1719,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should find the requested interpreter version" ); @@ -1736,7 +1728,7 @@ mod tests { } #[test] - fn find_environment_pypy_prefers_executable_with_implementation_name() -> Result<()> { + fn find_toolchain_pypy_prefers_executable_with_implementation_name() -> Result<()> { let mut context = TestContext::new()?; // We should prefer `pypy` executables over `python` executables in the same directory @@ -1755,8 +1747,8 @@ mod tests { )?; context.add_to_search_path(context.tempdir.to_path_buf()); - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("pypy@3.10"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1764,7 +1756,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", ); @@ -1774,8 +1766,8 @@ mod tests { (true, ImplementationName::PyPy, "python", "3.10.2"), (true, ImplementationName::PyPy, "pypy", "3.10.3"), ])?; - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("pypy@3.10"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1783,7 +1775,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.2", ); @@ -1791,7 +1783,7 @@ mod tests { } #[test] - fn find_environment_pypy_prefers_executable_with_version() -> Result<()> { + fn find_toolchain_pypy_prefers_executable_with_version() -> Result<()> { let mut context = TestContext::new()?; TestContext::create_mock_interpreter( &context.tempdir.join("pypy3.10"), @@ -1807,8 +1799,8 @@ mod tests { )?; context.add_to_search_path(context.tempdir.to_path_buf()); - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("pypy@3.10"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1816,7 +1808,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.0", "We should prefer executables with the version number over those with implementation names" ); @@ -1836,8 +1828,8 @@ mod tests { )?; context.add_to_search_path(context.tempdir.to_path_buf()); - let environment = context.run(|| { - PythonEnvironment::find( + let toolchain = context.run(|| { + Toolchain::find( Some("pypy@3.10"), SystemPython::Allowed, PreviewMode::Disabled, @@ -1845,7 +1837,7 @@ mod tests { ) })?; assert_eq!( - environment.interpreter().python_full_version().to_string(), + toolchain.interpreter().python_full_version().to_string(), "3.10.1", "We should prefer an implementation name executable over a generic name with a version" ); diff --git a/crates/uv-toolchain/src/managed.rs b/crates/uv-toolchain/src/managed.rs index c4d59d843a0b..3fe11efbf7d7 100644 --- a/crates/uv-toolchain/src/managed.rs +++ b/crates/uv-toolchain/src/managed.rs @@ -13,7 +13,7 @@ pub use crate::downloads::Error; use crate::platform::{Arch, Libc, Os}; use crate::python_version::PythonVersion; -/// A collection of installed Python toolchains. +/// A collection of uv-managed Python toolchains installed on the current system. #[derive(Debug, Clone)] pub struct InstalledToolchains { /// The path to the top-level directory of the installed toolchains. @@ -72,7 +72,7 @@ impl InstalledToolchains { /// ordering across platforms. This also results in newer Python versions coming first, /// but should not be relied on — instead the toolchains should be sorted later by /// the parsed Python version. - fn find_all(&self) -> Result, Error> { + fn find_all(&self) -> Result, Error> { let dirs = match fs_err::read_dir(&self.root) { Ok(toolchain_dirs) => { // Collect sorted directory paths; `read_dir` is not stable across platforms @@ -101,14 +101,14 @@ impl InstalledToolchains { }; Ok(dirs .into_iter() - .map(|path| Toolchain::new(path).unwrap()) + .map(|path| InstalledToolchain::new(path).unwrap()) .rev()) } /// Iterate over toolchains that support the current platform. pub fn find_matching_current_platform( &self, - ) -> Result, Error> { + ) -> Result, Error> { let platform_key = platform_key_from_env()?; let iter = InstalledToolchains::from_settings()? @@ -133,7 +133,7 @@ impl InstalledToolchains { pub fn find_version<'a>( &self, version: &'a PythonVersion, - ) -> Result + 'a, Error> { + ) -> Result + 'a, Error> { Ok(self .find_matching_current_platform()? .filter(move |toolchain| { @@ -150,28 +150,28 @@ impl InstalledToolchains { } } -/// An installed Python toolchain. +/// A uv-managed Python toolchain installed on the current system.. #[derive(Debug, Clone)] -pub struct Toolchain { +pub struct InstalledToolchain { /// The path to the top-level directory of the installed toolchain. path: PathBuf, python_version: PythonVersion, } -impl Toolchain { +impl InstalledToolchain { pub fn new(path: PathBuf) -> Result { let python_version = PythonVersion::from_str( path.file_name() - .ok_or(Error::NameError("No directory name".to_string()))? + .ok_or(Error::NameError("name is empty".to_string()))? .to_str() - .ok_or(Error::NameError("Name not a valid string".to_string()))? + .ok_or(Error::NameError("not a valid string".to_string()))? .split('-') .nth(1) .ok_or(Error::NameError( - "Not enough `-`-separated values".to_string(), + "not enough `-`-separated values".to_string(), ))?, ) - .map_err(|err| Error::NameError(format!("Name has invalid Python version: {err}")))?; + .map_err(|err| Error::NameError(format!("invalid Python version: {err}")))?; Ok(Self { path, @@ -202,7 +202,7 @@ fn platform_key_from_env() -> Result { Ok(format!("{os}-{arch}-{libc}").to_lowercase()) } -impl fmt::Display for Toolchain { +impl fmt::Display for InstalledToolchain { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, diff --git a/crates/uv-toolchain/src/toolchain.rs b/crates/uv-toolchain/src/toolchain.rs new file mode 100644 index 000000000000..a277150eed36 --- /dev/null +++ b/crates/uv-toolchain/src/toolchain.rs @@ -0,0 +1,136 @@ +use uv_configuration::PreviewMode; + +use uv_cache::Cache; + +use crate::discovery::{SystemPython, ToolchainRequest, ToolchainSources}; +use crate::{ + find_best_toolchain, find_default_toolchain, find_toolchain, Error, Interpreter, + ToolchainSource, +}; + +/// A Python interpreter and accompanying tools. +#[derive(Clone, Debug)] +pub struct Toolchain { + // Public in the crate for test assertions + pub(crate) source: ToolchainSource, + pub(crate) interpreter: Interpreter, +} + +impl Toolchain { + /// Find an installed [`Toolchain`]. + /// + /// This is the standard interface for discovering a Python toolchain for use with uv. + /// + /// See [`uv-toolchain::discovery`] for implementation details. + pub fn find( + python: Option<&str>, + system: SystemPython, + preview: PreviewMode, + cache: &Cache, + ) -> Result { + if let Some(python) = python { + Self::find_requested(python, system, preview, cache) + } else if system.is_preferred() { + Self::find_default(preview, cache) + } else { + // First check for a parent intepreter + // We gate this check to avoid an extra log message when it is not set + if std::env::var_os("UV_INTERNAL__PARENT_INTERPRETER").is_some() { + match Self::find_parent_interpreter(system, cache) { + Ok(env) => return Ok(env), + Err(Error::NotFound(_)) => {} + Err(err) => return Err(err), + } + } + + // Then a virtual environment + match Self::find_virtualenv(cache) { + Ok(venv) => Ok(venv), + Err(Error::NotFound(_)) if system.is_allowed() => { + Self::find_default(preview, cache) + } + Err(err) => Err(err), + } + } + } + + /// Find an installed [`Toolchain`] that satisfies a request. + pub fn find_requested( + request: &str, + system: SystemPython, + preview: PreviewMode, + cache: &Cache, + ) -> Result { + let sources = ToolchainSources::from_settings(system, preview); + let request = ToolchainRequest::parse(request); + let toolchain = find_toolchain(&request, system, &sources, cache)??; + + Ok(toolchain) + } + + /// Find an installed [`Toolchain`] that satisfies a requested version, if the request cannot + /// be satisfied, fallback to the best available toolchain. + pub fn find_best( + request: &ToolchainRequest, + system: SystemPython, + preview: PreviewMode, + cache: &Cache, + ) -> Result { + Ok(find_best_toolchain(request, system, preview, cache)??) + } + + /// Find an installed [`Toolchain`] in an existing virtual environment. + /// + /// Allows Conda environments (via `CONDA_PREFIX`) though they are not technically virtual environments. + pub fn find_virtualenv(cache: &Cache) -> Result { + let sources = ToolchainSources::VirtualEnv; + let request = ToolchainRequest::Any; + let toolchain = find_toolchain(&request, SystemPython::Disallowed, &sources, cache)??; + + debug_assert!( + toolchain.interpreter().is_virtualenv() + || matches!(toolchain.source(), ToolchainSource::CondaPrefix), + "Not a virtualenv (source: {}, prefix: {})", + toolchain.source(), + toolchain.interpreter().sys_base_prefix().display() + ); + + Ok(toolchain) + } + + /// Find the [`Toolchain`] belonging to the parent interpreter i.e. from `python -m uv ...` + /// + /// If not spawned by `python -m uv`, the toolchain will not be found. + pub fn find_parent_interpreter(system: SystemPython, cache: &Cache) -> Result { + let sources = ToolchainSources::from_sources([ToolchainSource::ParentInterpreter]); + let request = ToolchainRequest::Any; + let toolchain = find_toolchain(&request, system, &sources, cache)??; + Ok(toolchain) + } + + /// Find the default installed [`Toolchain`]. + pub fn find_default(preview: PreviewMode, cache: &Cache) -> Result { + let toolchain = find_default_toolchain(preview, cache)??; + Ok(toolchain) + } + + /// Create a [`Toolchain`] from an existing [`Interpreter`]. + pub fn from_interpreter(interpreter: Interpreter) -> Self { + Self { + source: ToolchainSource::ProvidedPath, + interpreter, + } + } + + pub fn source(&self) -> &ToolchainSource { + &self.source + } + + pub fn interpreter(&self) -> &Interpreter { + &self.interpreter + } + + pub fn into_interpreter(self) -> Interpreter { + self.interpreter + } +} diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index f9be322e13ce..31a02f3a5605 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -17,7 +17,7 @@ pub enum Error { #[error("Failed to determine Python interpreter to use")] Discovery(#[from] uv_toolchain::DiscoveryError), #[error("Failed to determine Python interpreter to use")] - InterpreterNotFound(#[from] uv_toolchain::InterpreterNotFound), + InterpreterNotFound(#[from] uv_toolchain::ToolchainNotFound), #[error(transparent)] Platform(#[from] PlatformError), #[error("Could not find a suitable Python executable for the virtual environment based on the interpreter: {0}")] diff --git a/crates/uv/src/commands/pip/check.rs b/crates/uv/src/commands/pip/check.rs index 36e1f791330c..80e533935fa4 100644 --- a/crates/uv/src/commands/pip/check.rs +++ b/crates/uv/src/commands/pip/check.rs @@ -10,7 +10,7 @@ use uv_cache::Cache; use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_installer::{SitePackages, SitePackagesDiagnostic}; -use uv_toolchain::{PythonEnvironment, SystemPython}; +use uv_toolchain::{PythonEnvironment, SystemPython, Toolchain}; use crate::commands::{elapsed, ExitStatus}; use crate::printer::Printer; @@ -31,16 +31,17 @@ pub(crate) fn pip_check( } else { SystemPython::Allowed }; - let venv = PythonEnvironment::find(python, system, preview, cache)?; + let environment = + PythonEnvironment::from_toolchain(Toolchain::find(python, system, preview, cache)?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan() + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan() ); // Build the installed index. - let site_packages = SitePackages::from_executable(&venv)?; + let site_packages = SitePackages::from_executable(&environment)?; let packages: Vec<&InstalledDist> = site_packages.iter().collect(); let s = if packages.len() == 1 { "" } else { "s" }; diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index c1d48c630ede..b930a5467af3 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -42,8 +42,7 @@ use uv_resolver::{ Resolver, }; use uv_toolchain::{ - find_best_interpreter, InterpreterRequest, PythonEnvironment, PythonVersion, SystemPython, - VersionRequest, + PythonEnvironment, PythonVersion, SystemPython, Toolchain, ToolchainRequest, VersionRequest, }; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; @@ -162,17 +161,19 @@ pub(crate) async fn pip_compile( SystemPython::Allowed }; let interpreter = if let Some(python) = python.as_ref() { - PythonEnvironment::from_requested_python(python, system, preview, &cache)? - .into_interpreter() + Toolchain::find_requested(python, system, preview, &cache) } else { + // TODO(zanieb): The split here hints at a problem with the abstraction; we should be able to use + // `Toolchain::find(...)` here. let request = if let Some(version) = python_version.as_ref() { // TODO(zanieb): We should consolidate `VersionRequest` and `PythonVersion` - InterpreterRequest::Version(VersionRequest::from(version)) + ToolchainRequest::Version(VersionRequest::from(version)) } else { - InterpreterRequest::default() + ToolchainRequest::default() }; - find_best_interpreter(&request, system, preview, &cache)??.into_interpreter() - }; + Toolchain::find_best(&request, system, preview, &cache) + }? + .into_interpreter(); debug!( "Using Python {} interpreter at {} for builds", @@ -297,10 +298,10 @@ pub(crate) async fn pip_compile( let in_flight = InFlight::default(); // Determine whether to enable build isolation. - let venv; + let environment; let build_isolation = if no_build_isolation { - venv = PythonEnvironment::from_interpreter(interpreter.clone()); - BuildIsolation::Shared(&venv) + environment = PythonEnvironment::from_interpreter(interpreter.clone()); + BuildIsolation::Shared(&environment) } else { BuildIsolation::Isolated }; diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index 7cbde4be1fc4..3b7f78b302ca 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -10,7 +10,7 @@ use uv_cache::Cache; use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_installer::SitePackages; -use uv_toolchain::{PythonEnvironment, SystemPython}; +use uv_toolchain::{PythonEnvironment, SystemPython, Toolchain}; use crate::commands::ExitStatus; use crate::printer::Printer; @@ -31,16 +31,17 @@ pub(crate) fn pip_freeze( } else { SystemPython::Allowed }; - let venv = PythonEnvironment::find(python, system, preview, cache)?; + let environment = + PythonEnvironment::from_toolchain(Toolchain::find(python, system, preview, cache)?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan() + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan() ); // Build the installed index. - let site_packages = SitePackages::from_executable(&venv)?; + let site_packages = SitePackages::from_executable(&environment)?; for dist in site_packages .iter() .filter(|dist| !(exclude_editable && dist.is_editable())) diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index ca4d01562cee..28b49785282d 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -27,7 +27,7 @@ use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder, PreReleaseMode, ResolutionMode, }; -use uv_toolchain::{Prefix, PythonEnvironment, PythonVersion, SystemPython, Target}; +use uv_toolchain::{Prefix, PythonEnvironment, PythonVersion, SystemPython, Target, Toolchain}; use uv_types::{BuildIsolation, HashStrategy, InFlight}; use crate::commands::pip::operations; @@ -122,57 +122,62 @@ pub(crate) async fn pip_install( } else { SystemPython::Explicit }; - let venv = PythonEnvironment::find(python.as_deref(), system, preview, &cache)?; + let environment = PythonEnvironment::from_toolchain(Toolchain::find( + python.as_deref(), + system, + preview, + &cache, + )?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan() + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan() ); // Apply any `--target` or `--prefix` directories. - let venv = if let Some(target) = target { + let environment = if let Some(target) = target { debug!( "Using `--target` directory at {}", target.root().user_display() ); target.init()?; - venv.with_target(target) + environment.with_target(target) } else if let Some(prefix) = prefix { debug!( "Using `--prefix` directory at {}", prefix.root().user_display() ); prefix.init()?; - venv.with_prefix(prefix) + environment.with_prefix(prefix) } else { - venv + environment }; // If the environment is externally managed, abort. - if let Some(externally_managed) = venv.interpreter().is_externally_managed() { + if let Some(externally_managed) = environment.interpreter().is_externally_managed() { if break_system_packages { debug!("Ignoring externally managed environment due to `--break-system-packages`"); } else { return if let Some(error) = externally_managed.into_error() { Err(anyhow::anyhow!( - "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", - venv.root().user_display().cyan(), + "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv environment`.", + environment.root().user_display().cyan(), textwrap::indent(&error, " ").green(), )) } else { Err(anyhow::anyhow!( - "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", - venv.root().user_display().cyan() + "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv environment`.", + environment.root().user_display().cyan() )) }; } } - let _lock = venv.lock()?; + let _lock = environment.lock()?; // Determine the set of installed packages. - let site_packages = SitePackages::from_executable(&venv)?; + let site_packages = SitePackages::from_executable(&environment)?; // Check if the current environment satisfies the requirements. // Ideally, the resolver would be fast enough to let us remove this check. But right now, for large environments, @@ -215,7 +220,7 @@ pub(crate) async fn pip_install( } } - let interpreter = venv.interpreter(); + let interpreter = environment.interpreter(); // Determine the tags, markers, and interpreter to use for resolution. let tags = match (python_platform, python_version.as_ref()) { @@ -302,7 +307,7 @@ pub(crate) async fn pip_install( // Determine whether to enable build isolation. let build_isolation = if no_build_isolation { - BuildIsolation::Shared(&venv) + BuildIsolation::Shared(&environment) } else { BuildIsolation::Isolated }; @@ -431,7 +436,7 @@ pub(crate) async fn pip_install( concurrency, &install_dispatch, &cache, - &venv, + &environment, dry_run, printer, preview, @@ -443,7 +448,7 @@ pub(crate) async fn pip_install( // Notify the user of any environment diagnostics. if strict && !dry_run { - operations::diagnose_environment(&resolution, &venv, printer)?; + operations::diagnose_environment(&resolution, &environment, printer)?; } Ok(ExitStatus::Success) diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index 6e91ed29a962..063ff4984c11 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -14,6 +14,7 @@ use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_installer::SitePackages; use uv_normalize::PackageName; +use uv_toolchain::Toolchain; use uv_toolchain::{PythonEnvironment, SystemPython}; use crate::commands::ExitStatus; @@ -40,16 +41,17 @@ pub(crate) fn pip_list( } else { SystemPython::Allowed }; - let venv = PythonEnvironment::find(python, system, preview, cache)?; + let environment = + PythonEnvironment::from_toolchain(Toolchain::find(python, system, preview, cache)?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan() + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan() ); // Build the installed index. - let site_packages = SitePackages::from_executable(&venv)?; + let site_packages = SitePackages::from_executable(&environment)?; // Filter if `--editable` is specified; always sort by name. let results = site_packages diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index 55f32770e8d0..00ac5865083b 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -12,7 +12,7 @@ use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_installer::SitePackages; use uv_normalize::PackageName; -use uv_toolchain::{PythonEnvironment, SystemPython}; +use uv_toolchain::{PythonEnvironment, SystemPython, Toolchain}; use crate::commands::ExitStatus; use crate::printer::Printer; @@ -46,19 +46,20 @@ pub(crate) fn pip_show( } else { SystemPython::Allowed }; - let venv = PythonEnvironment::find(python, system, preview, cache)?; + let environment = + PythonEnvironment::from_toolchain(Toolchain::find(python, system, preview, cache)?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan() + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan() ); // Build the installed index. - let site_packages = SitePackages::from_executable(&venv)?; + let site_packages = SitePackages::from_executable(&environment)?; // Determine the markers to use for resolution. - let markers = venv.interpreter().markers(); + let markers = environment.interpreter().markers(); // Sort and deduplicate the packages, which are keyed by name. packages.sort_unstable(); diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 7a69da0c70d4..f6289b9ce196 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -26,7 +26,7 @@ use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder, PreReleaseMode, ResolutionMode, }; -use uv_toolchain::{Prefix, PythonEnvironment, PythonVersion, SystemPython, Target}; +use uv_toolchain::{Prefix, PythonEnvironment, PythonVersion, SystemPython, Target, Toolchain}; use uv_types::{BuildIsolation, HashStrategy, InFlight}; use crate::commands::pip::operations; @@ -117,56 +117,61 @@ pub(crate) async fn pip_sync( } else { SystemPython::Explicit }; - let venv = PythonEnvironment::find(python.as_deref(), system, preview, &cache)?; + let environment = PythonEnvironment::from_toolchain(Toolchain::find( + python.as_deref(), + system, + preview, + &cache, + )?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan() + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan() ); // Apply any `--target` or `--prefix` directories. - let venv = if let Some(target) = target { + let environment = if let Some(target) = target { debug!( "Using `--target` directory at {}", target.root().user_display() ); target.init()?; - venv.with_target(target) + environment.with_target(target) } else if let Some(prefix) = prefix { debug!( "Using `--prefix` directory at {}", prefix.root().user_display() ); prefix.init()?; - venv.with_prefix(prefix) + environment.with_prefix(prefix) } else { - venv + environment }; // If the environment is externally managed, abort. - if let Some(externally_managed) = venv.interpreter().is_externally_managed() { + if let Some(externally_managed) = environment.interpreter().is_externally_managed() { if break_system_packages { debug!("Ignoring externally managed environment due to `--break-system-packages`"); } else { return if let Some(error) = externally_managed.into_error() { Err(anyhow::anyhow!( - "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", - venv.root().user_display().cyan(), + "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv environment`.", + environment.root().user_display().cyan(), textwrap::indent(&error, " ").green(), )) } else { Err(anyhow::anyhow!( - "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", - venv.root().user_display().cyan() + "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv environment`.", + environment.root().user_display().cyan() )) }; } } - let _lock = venv.lock()?; + let _lock = environment.lock()?; - let interpreter = venv.interpreter(); + let interpreter = environment.interpreter(); // Determine the current environment markers. let tags = match (python_platform, python_version.as_ref()) { @@ -245,7 +250,7 @@ pub(crate) async fn pip_sync( // Determine whether to enable build isolation. let build_isolation = if no_build_isolation { - BuildIsolation::Shared(&venv) + BuildIsolation::Shared(&environment) } else { BuildIsolation::Isolated }; @@ -289,7 +294,7 @@ pub(crate) async fn pip_sync( .with_options(OptionsBuilder::new().exclude_newer(exclude_newer).build()); // Determine the set of installed packages. - let site_packages = SitePackages::from_executable(&venv)?; + let site_packages = SitePackages::from_executable(&environment)?; let options = OptionsBuilder::new() .resolution_mode(resolution_mode) @@ -383,7 +388,7 @@ pub(crate) async fn pip_sync( concurrency, &install_dispatch, &cache, - &venv, + &environment, dry_run, printer, preview, @@ -395,7 +400,7 @@ pub(crate) async fn pip_sync( // Notify the user of any environment diagnostics. if strict && !dry_run { - operations::diagnose_environment(&resolution, &venv, printer)?; + operations::diagnose_environment(&resolution, &environment, printer)?; } Ok(ExitStatus::Success) diff --git a/crates/uv/src/commands/pip/uninstall.rs b/crates/uv/src/commands/pip/uninstall.rs index 7ab38cc16f52..3aa5d4665e93 100644 --- a/crates/uv/src/commands/pip/uninstall.rs +++ b/crates/uv/src/commands/pip/uninstall.rs @@ -14,6 +14,7 @@ use uv_client::{BaseClientBuilder, Connectivity}; use uv_configuration::{KeyringProviderType, PreviewMode}; use uv_fs::Simplified; use uv_requirements::{RequirementsSource, RequirementsSpecification}; +use uv_toolchain::Toolchain; use uv_toolchain::{Prefix, PythonEnvironment, SystemPython, Target}; use crate::commands::{elapsed, ExitStatus}; @@ -50,57 +51,62 @@ pub(crate) async fn pip_uninstall( } else { SystemPython::Explicit }; - let venv = PythonEnvironment::find(python.as_deref(), system, preview, &cache)?; + let environment = PythonEnvironment::from_toolchain(Toolchain::find( + python.as_deref(), + system, + preview, + &cache, + )?); debug!( "Using Python {} environment at {}", - venv.interpreter().python_version(), - venv.python_executable().user_display().cyan(), + environment.interpreter().python_version(), + environment.python_executable().user_display().cyan(), ); // Apply any `--target` or `--prefix` directories. - let venv = if let Some(target) = target { + let environment = if let Some(target) = target { debug!( "Using `--target` directory at {}", target.root().user_display() ); target.init()?; - venv.with_target(target) + environment.with_target(target) } else if let Some(prefix) = prefix { debug!( "Using `--prefix` directory at {}", prefix.root().user_display() ); prefix.init()?; - venv.with_prefix(prefix) + environment.with_prefix(prefix) } else { - venv + environment }; // If the environment is externally managed, abort. - if let Some(externally_managed) = venv.interpreter().is_externally_managed() { + if let Some(externally_managed) = environment.interpreter().is_externally_managed() { if break_system_packages { debug!("Ignoring externally managed environment due to `--break-system-packages`"); } else { return if let Some(error) = externally_managed.into_error() { Err(anyhow::anyhow!( - "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.", - venv.root().user_display().cyan(), + "The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv environment`.", + environment.root().user_display().cyan(), textwrap::indent(&error, " ").green(), )) } else { Err(anyhow::anyhow!( - "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.", - venv.root().user_display().cyan() + "The interpreter at {} is externally managed. Instead, create a virtual environment with `uv environment`.", + environment.root().user_display().cyan() )) }; } } - let _lock = venv.lock()?; + let _lock = environment.lock()?; // Index the current `site-packages` directory. - let site_packages = uv_installer::SitePackages::from_executable(&venv)?; + let site_packages = uv_installer::SitePackages::from_executable(&environment)?; // Partition the requirements into named and unnamed requirements. let (named, unnamed): (Vec, Vec>) = spec diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 6b3cd6f81c1c..8bd2291d7f2d 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -21,7 +21,7 @@ use uv_git::GitResolver; use uv_installer::{SatisfiesResult, SitePackages}; use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_resolver::{FlatIndex, InMemoryIndex, Options, RequiresPython}; -use uv_toolchain::{find_default_interpreter, PythonEnvironment}; +use uv_toolchain::{find_default_toolchain, PythonEnvironment}; use uv_types::{BuildIsolation, HashStrategy, InFlight}; use crate::commands::pip; @@ -82,7 +82,7 @@ pub(crate) fn init_environment( Ok(venv) => Ok(venv), Err(uv_toolchain::Error::NotFound(_)) => { // TODO(charlie): Respect `--python`; if unset, respect `Requires-Python`. - let interpreter = find_default_interpreter(preview, cache) + let interpreter = find_default_toolchain(preview, cache) .map_err(uv_toolchain::Error::from)? .map_err(uv_toolchain::Error::from)? .into_interpreter(); diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 709297dbbe91..531cf90e448a 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -15,7 +15,7 @@ use uv_distribution::{ProjectWorkspace, Workspace}; use uv_normalize::PackageName; use uv_requirements::RequirementsSource; use uv_resolver::ExcludeNewer; -use uv_toolchain::{PythonEnvironment, SystemPython}; +use uv_toolchain::{PythonEnvironment, SystemPython, Toolchain}; use uv_warnings::warn_user; use crate::commands::{project, ExitStatus}; @@ -109,10 +109,10 @@ pub(crate) async fn run( let interpreter = if let Some(project_env) = &project_env { project_env.interpreter().clone() } else if let Some(python) = python.as_ref() { - PythonEnvironment::from_requested_python(python, SystemPython::Allowed, preview, cache)? + Toolchain::find_requested(python, SystemPython::Allowed, preview, cache)? .into_interpreter() } else { - PythonEnvironment::from_default_python(preview, cache)?.into_interpreter() + Toolchain::find_default(preview, cache)?.into_interpreter() }; // TODO(charlie): If the environment satisfies the requirements, skip creation. diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index a2b7945de0a2..3812a418ba79 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -12,7 +12,7 @@ use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::PreviewMode; use uv_requirements::RequirementsSource; -use uv_toolchain::{PythonEnvironment, SystemPython}; +use uv_toolchain::{PythonEnvironment, SystemPython, Toolchain}; use uv_warnings::warn_user; use crate::commands::project::update_environment; @@ -53,10 +53,9 @@ pub(crate) async fn run( // Discover an interpreter. let interpreter = if let Some(python) = python.as_ref() { - PythonEnvironment::from_requested_python(python, SystemPython::Allowed, preview, cache)? - .into_interpreter() + Toolchain::find_requested(python, SystemPython::Allowed, preview, cache)?.into_interpreter() } else { - PythonEnvironment::from_default_python(preview, cache)?.into_interpreter() + Toolchain::find_default(preview, cache)?.into_interpreter() }; // Create a virtual environment diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 010e05a2a5ff..33e256fb4561 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -21,7 +21,7 @@ use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_git::GitResolver; use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder}; -use uv_toolchain::{PythonEnvironment, SystemPython}; +use uv_toolchain::{SystemPython, Toolchain}; use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight}; use crate::commands::{pip, ExitStatus}; @@ -120,10 +120,9 @@ async fn venv_impl( printer: Printer, ) -> miette::Result { // Locate the Python interpreter to use in the environment - let interpreter = - PythonEnvironment::find(python_request, SystemPython::Required, preview, cache) - .into_diagnostic()? - .into_interpreter(); + let interpreter = Toolchain::find(python_request, SystemPython::Required, preview, cache) + .into_diagnostic()? + .into_interpreter(); // Add all authenticated sources to the cache. for url in index_locations.urls() { diff --git a/crates/uv/tests/common/mod.rs b/crates/uv/tests/common/mod.rs index 9b131777dde0..3bf9fb7ae46c 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -19,7 +19,7 @@ use uv_cache::Cache; use uv_fs::Simplified; use uv_toolchain::managed::InstalledToolchains; use uv_toolchain::{ - find_interpreter, InterpreterRequest, PythonVersion, SourceSelector, VersionRequest, + find_toolchain, PythonVersion, ToolchainRequest, ToolchainSources, VersionRequest, }; // Exclude any packages uploaded after this date. @@ -416,7 +416,7 @@ pub fn create_venv