mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-02 23:04:37 +00:00
Skip invalid interpreters when searching for requested interpreter executable name (#4308)
Previously, we took the first executable on the `PATH` but if it was not a usable interpreter we'd fail. Now, we'll continue searching in the path until we find an interpreter as we do with the standard executable names.
This commit is contained in:
parent
30126950fe
commit
b07c132ede
1 changed files with 99 additions and 78 deletions
|
@ -9,7 +9,7 @@ use pep440_rs::{Version, VersionSpecifiers};
|
||||||
use same_file::is_same_file;
|
use same_file::is_same_file;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, instrument, trace};
|
use tracing::{debug, instrument, trace};
|
||||||
use which::which;
|
use which::{which, which_all};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
|
@ -361,8 +361,22 @@ fn python_interpreters<'a>(
|
||||||
sources: &ToolchainSources,
|
sources: &ToolchainSources,
|
||||||
cache: &'a Cache,
|
cache: &'a Cache,
|
||||||
) -> impl Iterator<Item = Result<(ToolchainSource, Interpreter), Error>> + 'a {
|
) -> impl Iterator<Item = Result<(ToolchainSource, Interpreter), Error>> + 'a {
|
||||||
python_executables(version, implementation, sources)
|
// TODO(zanieb): Move filtering to callers
|
||||||
.map(|result| match result {
|
filter_by_system_python(
|
||||||
|
python_interpreters_from_executables(
|
||||||
|
python_executables(version, implementation, sources),
|
||||||
|
cache,
|
||||||
|
),
|
||||||
|
system,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lazily convert Python executables into interpreters.
|
||||||
|
fn python_interpreters_from_executables<'a>(
|
||||||
|
executables: impl Iterator<Item = Result<(ToolchainSource, PathBuf), Error>> + 'a,
|
||||||
|
cache: &'a Cache,
|
||||||
|
) -> impl Iterator<Item = Result<(ToolchainSource, Interpreter), Error>> + 'a {
|
||||||
|
executables.map(|result| match result {
|
||||||
Ok((source, path)) => Interpreter::query(&path, cache)
|
Ok((source, path)) => Interpreter::query(&path, cache)
|
||||||
.map(|interpreter| (source, interpreter))
|
.map(|interpreter| (source, interpreter))
|
||||||
.inspect(|(source, interpreter)| {
|
.inspect(|(source, interpreter)| {
|
||||||
|
@ -377,7 +391,13 @@ fn python_interpreters<'a>(
|
||||||
.inspect_err(|err| debug!("{err}")),
|
.inspect_err(|err| debug!("{err}")),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
})
|
})
|
||||||
.filter(move |result| match result {
|
}
|
||||||
|
|
||||||
|
fn filter_by_system_python<'a>(
|
||||||
|
interpreters: impl Iterator<Item = Result<(ToolchainSource, Interpreter), Error>> + 'a,
|
||||||
|
system: SystemPython,
|
||||||
|
) -> impl Iterator<Item = Result<(ToolchainSource, Interpreter), Error>> + 'a {
|
||||||
|
interpreters.filter(move |result| match result {
|
||||||
// Filter the returned interpreters to conform to the system request
|
// Filter the returned interpreters to conform to the system request
|
||||||
Ok((source, interpreter)) => match (
|
Ok((source, interpreter)) => match (
|
||||||
system,
|
system,
|
||||||
|
@ -482,19 +502,17 @@ fn find_toolchain_at_directory(path: &PathBuf, cache: &Cache) -> Result<Toolchai
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_toolchain_with_executable_name(
|
/// Lazily iterate over all Python interpreters on the path with the given executable name.
|
||||||
name: &str,
|
fn python_interpreters_with_executable_name<'a>(
|
||||||
cache: &Cache,
|
name: &'a str,
|
||||||
) -> Result<ToolchainResult, Error> {
|
cache: &'a Cache,
|
||||||
let Some(executable) = which(name).ok() else {
|
) -> impl Iterator<Item = Result<(ToolchainSource, Interpreter), Error>> + 'a {
|
||||||
return Ok(ToolchainResult::Err(
|
python_interpreters_from_executables(
|
||||||
ToolchainNotFound::ExecutableNotFoundInSearchPath(name.to_string()),
|
which_all(name)
|
||||||
));
|
.into_iter()
|
||||||
};
|
.flat_map(|inner| inner.map(|path| Ok((ToolchainSource::SearchPath, path)))),
|
||||||
Ok(ToolchainResult::Ok(Toolchain {
|
cache,
|
||||||
source: ToolchainSource::SearchPath,
|
)
|
||||||
interpreter: Interpreter::query(executable, cache)?,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all toolchains that satisfy the given request.
|
/// Iterate over all toolchains that satisfy the given request.
|
||||||
|
@ -530,19 +548,22 @@ pub fn find_toolchains<'a>(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
ToolchainRequest::ExecutableName(name) => Box::new(std::iter::once({
|
ToolchainRequest::ExecutableName(name) => {
|
||||||
debug!("Searching for Python interpreter with {request}");
|
debug!("Searching for Python interpreter with {request}");
|
||||||
if sources.contains(ToolchainSource::SearchPath) {
|
if sources.contains(ToolchainSource::SearchPath) {
|
||||||
debug!("Checking for Python interpreter at {request}");
|
debug!("Checking for Python interpreter at {request}");
|
||||||
find_toolchain_with_executable_name(name, cache)
|
Box::new(
|
||||||
|
python_interpreters_with_executable_name(name, cache)
|
||||||
|
.map(|result| result.map(Toolchain::from_tuple).map(ToolchainResult::Ok)),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::SourceNotSelected(
|
Box::new(std::iter::once(Err(Error::SourceNotSelected(
|
||||||
request.clone(),
|
request.clone(),
|
||||||
ToolchainSource::SearchPath,
|
ToolchainSource::SearchPath,
|
||||||
sources.clone(),
|
sources.clone(),
|
||||||
))
|
))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})),
|
|
||||||
ToolchainRequest::Any => Box::new({
|
ToolchainRequest::Any => Box::new({
|
||||||
debug!("Searching for Python interpreter in {sources}");
|
debug!("Searching for Python interpreter in {sources}");
|
||||||
python_interpreters(None, None, system, sources, cache)
|
python_interpreters(None, None, system, sources, cache)
|
||||||
|
@ -614,13 +635,13 @@ pub(crate) fn find_toolchain(
|
||||||
ToolchainRequest::Version(version) => {
|
ToolchainRequest::Version(version) => {
|
||||||
ToolchainNotFound::NoMatchingVersion(sources.clone(), version.clone())
|
ToolchainNotFound::NoMatchingVersion(sources.clone(), version.clone())
|
||||||
}
|
}
|
||||||
|
ToolchainRequest::ExecutableName(name) => {
|
||||||
|
ToolchainNotFound::ExecutableNotFoundInSearchPath(name.clone())
|
||||||
|
}
|
||||||
// TODO(zanieb): As currently implemented, these are unreachable as they are handled in `find_toolchains`
|
// TODO(zanieb): As currently implemented, these are unreachable as they are handled in `find_toolchains`
|
||||||
// We should avoid this duplication
|
// We should avoid this duplication
|
||||||
ToolchainRequest::Directory(path) => ToolchainNotFound::DirectoryNotFound(path.clone()),
|
ToolchainRequest::Directory(path) => ToolchainNotFound::DirectoryNotFound(path.clone()),
|
||||||
ToolchainRequest::File(path) => ToolchainNotFound::FileNotFound(path.clone()),
|
ToolchainRequest::File(path) => ToolchainNotFound::FileNotFound(path.clone()),
|
||||||
ToolchainRequest::ExecutableName(name) => {
|
|
||||||
ToolchainNotFound::ExecutableNotFoundInSearchPath(name.clone())
|
|
||||||
}
|
|
||||||
ToolchainRequest::Any => ToolchainNotFound::NoPythonInstallation(sources.clone(), None),
|
ToolchainRequest::Any => ToolchainNotFound::NoPythonInstallation(sources.clone(), None),
|
||||||
};
|
};
|
||||||
Ok(ToolchainResult::Err(err))
|
Ok(ToolchainResult::Err(err))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue