mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Improve toolchain and environment missing error messages (#4596)
The journey here can be seen in: - #4587 - #4589 - #4594 I collapsed all the commits here because only the last one in the stack got us to a "correct" error message. There are a few architectural changes: - We have a dedicated `MissingEnvironment` and `EnvironmentNotFound` type for `PythonEnvironment::find` allowing different error messages when searching for environments - `ToolchainNotFound` becomes a struct with the `ToolchainRequest` which greatly simplifies missing toolchain error formatting - `ToolchainNotFound` tracks the `EnvironmentPreference` so it can accurately report the locations checked The messages look like this now, instead of the bland (and often incorrect): "No Python interpreter found in system toolchains". ``` ❯ cargo run -q -- pip sync requirements.txt error: No virtual environment found ❯ UV_TEST_PYTHON_PATH="" cargo run -q -- pip sync requirements.txt --system error: No system environment found ❯ UV_TEST_PYTHON_PATH="" cargo run -q -- pip sync requirements.txt --python 3.12 error: No virtual environment found for Python 3.12 ❯ UV_TEST_PYTHON_PATH="" cargo run -q -- pip sync requirements.txt --python 3.12 --system error: No system environment found for Python 3.12 ❯ UV_TEST_PYTHON_PATH="" cargo run -q -- toolchain find 3.12 --preview error: No toolchain found for Python 3.12 in system path ❯ UV_TEST_PYTHON_PATH="" cargo run -q -- pip compile requirements.in error: No toolchain found in virtual environments or system path ``` I'd like to follow this with hints, suggesting creating an environment or using system in some cases.
This commit is contained in:
parent
6b45c41d76
commit
3a62ba3809
9 changed files with 212 additions and 208 deletions
|
@ -51,6 +51,7 @@ pub enum ToolchainRequest {
|
||||||
/// Generally these refer to uv-managed toolchain downloads.
|
/// Generally these refer to uv-managed toolchain downloads.
|
||||||
Key(PythonDownloadRequest),
|
Key(PythonDownloadRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
||||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||||
|
@ -115,29 +116,12 @@ type ToolchainResult = Result<Toolchain, ToolchainNotFound>;
|
||||||
|
|
||||||
/// The result of failed toolchain discovery.
|
/// The result of failed toolchain discovery.
|
||||||
///
|
///
|
||||||
/// See [`InterpreterResult`].
|
/// See [`ToolchainResult`].
|
||||||
#[derive(Clone, Debug, Error)]
|
#[derive(Clone, Debug, Error)]
|
||||||
pub enum ToolchainNotFound {
|
pub struct ToolchainNotFound {
|
||||||
/// No Python installations were found.
|
pub request: ToolchainRequest,
|
||||||
NoPythonInstallation(ToolchainPreference, Option<VersionRequest>),
|
pub toolchain_preference: ToolchainPreference,
|
||||||
/// No Python installations with the requested version were found.
|
pub environment_preference: EnvironmentPreference,
|
||||||
NoMatchingVersion(ToolchainPreference, VersionRequest),
|
|
||||||
/// No Python installations with the requested key were found.
|
|
||||||
NoMatchingKey(ToolchainPreference, PythonDownloadRequest),
|
|
||||||
/// No Python installations with the requested implementation name were found.
|
|
||||||
NoMatchingImplementation(ToolchainPreference, ImplementationName),
|
|
||||||
/// No Python installations with the requested implementation name and version were found.
|
|
||||||
NoMatchingImplementationVersion(ToolchainPreference, ImplementationName, VersionRequest),
|
|
||||||
/// The requested file path does not exist.
|
|
||||||
FileNotFound(PathBuf),
|
|
||||||
/// The requested directory path does not exist.
|
|
||||||
DirectoryNotFound(PathBuf),
|
|
||||||
/// No Python executables could be found in the requested directory.
|
|
||||||
ExecutableNotFoundInDirectory(PathBuf, PathBuf),
|
|
||||||
/// The Python executable name could not be found in the search path (i.e. PATH).
|
|
||||||
ExecutableNotFoundInSearchPath(String),
|
|
||||||
/// A Python executable was found but is not executable.
|
|
||||||
FileNotExecutable(PathBuf),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A location for discovery of a Python toolchain.
|
/// A location for discovery of a Python toolchain.
|
||||||
|
@ -559,34 +543,25 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_toolchain_at_file(path: &PathBuf, cache: &Cache) -> Result<ToolchainResult, Error> {
|
fn find_toolchain_at_file(
|
||||||
if !path.try_exists()? {
|
path: &PathBuf,
|
||||||
return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound(
|
cache: &Cache,
|
||||||
path.clone(),
|
) -> Result<Toolchain, crate::interpreter::Error> {
|
||||||
)));
|
Ok(Toolchain {
|
||||||
}
|
|
||||||
Ok(ToolchainResult::Ok(Toolchain {
|
|
||||||
source: ToolchainSource::ProvidedPath,
|
source: ToolchainSource::ProvidedPath,
|
||||||
interpreter: Interpreter::query(path, cache)?,
|
interpreter: Interpreter::query(path, cache)?,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_toolchain_at_directory(path: &PathBuf, cache: &Cache) -> Result<ToolchainResult, Error> {
|
fn find_toolchain_at_directory(
|
||||||
if !path.try_exists()? {
|
path: &PathBuf,
|
||||||
return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound(
|
cache: &Cache,
|
||||||
path.clone(),
|
) -> Result<Toolchain, crate::interpreter::Error> {
|
||||||
)));
|
|
||||||
}
|
|
||||||
let executable = virtualenv_python_executable(path);
|
let executable = virtualenv_python_executable(path);
|
||||||
if !executable.try_exists()? {
|
Ok(Toolchain {
|
||||||
return Ok(ToolchainResult::Err(
|
|
||||||
ToolchainNotFound::ExecutableNotFoundInDirectory(path.clone(), executable),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(ToolchainResult::Ok(Toolchain {
|
|
||||||
source: ToolchainSource::ProvidedPath,
|
source: ToolchainSource::ProvidedPath,
|
||||||
interpreter: Interpreter::query(executable, cache)?,
|
interpreter: Interpreter::query(executable, cache)?,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lazily iterate over all Python interpreters on the path with the given executable name.
|
/// Lazily iterate over all Python interpreters on the path with the given executable name.
|
||||||
|
@ -613,7 +588,17 @@ pub fn find_toolchains<'a>(
|
||||||
ToolchainRequest::File(path) => Box::new(std::iter::once({
|
ToolchainRequest::File(path) => Box::new(std::iter::once({
|
||||||
if preference.allows(ToolchainSource::ProvidedPath) {
|
if preference.allows(ToolchainSource::ProvidedPath) {
|
||||||
debug!("Checking for Python interpreter at {request}");
|
debug!("Checking for Python interpreter at {request}");
|
||||||
find_toolchain_at_file(path, cache)
|
match find_toolchain_at_file(path, cache) {
|
||||||
|
Ok(toolchain) => Ok(ToolchainResult::Ok(toolchain)),
|
||||||
|
Err(InterpreterError::NotFound(_)) => {
|
||||||
|
Ok(ToolchainResult::Err(ToolchainNotFound {
|
||||||
|
request: request.clone(),
|
||||||
|
toolchain_preference: preference,
|
||||||
|
environment_preference: environments,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::SourceNotAllowed(
|
Err(Error::SourceNotAllowed(
|
||||||
request.clone(),
|
request.clone(),
|
||||||
|
@ -626,7 +611,17 @@ pub fn find_toolchains<'a>(
|
||||||
debug!("Checking for Python interpreter in {request}");
|
debug!("Checking for Python interpreter in {request}");
|
||||||
if preference.allows(ToolchainSource::ProvidedPath) {
|
if preference.allows(ToolchainSource::ProvidedPath) {
|
||||||
debug!("Checking for Python interpreter at {request}");
|
debug!("Checking for Python interpreter at {request}");
|
||||||
find_toolchain_at_directory(path, cache)
|
match find_toolchain_at_directory(path, cache) {
|
||||||
|
Ok(toolchain) => Ok(ToolchainResult::Ok(toolchain)),
|
||||||
|
Err(InterpreterError::NotFound(_)) => {
|
||||||
|
Ok(ToolchainResult::Err(ToolchainNotFound {
|
||||||
|
request: request.clone(),
|
||||||
|
toolchain_preference: preference,
|
||||||
|
environment_preference: environments,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::SourceNotAllowed(
|
Err(Error::SourceNotAllowed(
|
||||||
request.clone(),
|
request.clone(),
|
||||||
|
@ -731,31 +726,11 @@ pub(crate) fn find_toolchain(
|
||||||
}) {
|
}) {
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
let err = match request {
|
Ok(ToolchainResult::Err(ToolchainNotFound {
|
||||||
ToolchainRequest::Implementation(implementation) => {
|
request: request.clone(),
|
||||||
ToolchainNotFound::NoMatchingImplementation(preference, *implementation)
|
environment_preference: environments,
|
||||||
}
|
toolchain_preference: preference,
|
||||||
ToolchainRequest::ImplementationVersion(implementation, version) => {
|
}))
|
||||||
ToolchainNotFound::NoMatchingImplementationVersion(
|
|
||||||
preference,
|
|
||||||
*implementation,
|
|
||||||
version.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ToolchainRequest::Version(version) => {
|
|
||||||
ToolchainNotFound::NoMatchingVersion(preference, version.clone())
|
|
||||||
}
|
|
||||||
ToolchainRequest::ExecutableName(name) => {
|
|
||||||
ToolchainNotFound::ExecutableNotFoundInSearchPath(name.clone())
|
|
||||||
}
|
|
||||||
ToolchainRequest::Key(key) => ToolchainNotFound::NoMatchingKey(preference, key.clone()),
|
|
||||||
// TODO(zanieb): As currently implemented, these are unreachable as they are handled in `find_toolchains`
|
|
||||||
// We should avoid this duplication
|
|
||||||
ToolchainRequest::Directory(path) => ToolchainNotFound::DirectoryNotFound(path.clone()),
|
|
||||||
ToolchainRequest::File(path) => ToolchainNotFound::FileNotFound(path.clone()),
|
|
||||||
ToolchainRequest::Any => ToolchainNotFound::NoPythonInstallation(preference, None),
|
|
||||||
};
|
|
||||||
Ok(ToolchainResult::Err(err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -818,10 +793,10 @@ pub fn find_best_toolchain(
|
||||||
Ok(
|
Ok(
|
||||||
find_toolchain(&request, environments, preference, cache)?.map_err(|err| {
|
find_toolchain(&request, environments, preference, cache)?.map_err(|err| {
|
||||||
// Use a more general error in this case since we looked for multiple versions
|
// Use a more general error in this case since we looked for multiple versions
|
||||||
if matches!(err, ToolchainNotFound::NoMatchingVersion(..)) {
|
ToolchainNotFound {
|
||||||
ToolchainNotFound::NoPythonInstallation(preference, None)
|
request,
|
||||||
} else {
|
toolchain_preference: err.toolchain_preference,
|
||||||
err
|
environment_preference: err.environment_preference,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -1149,6 +1124,10 @@ impl ToolchainRequest {
|
||||||
ToolchainRequest::Key(request) => request.satisfied_by_interpreter(interpreter),
|
ToolchainRequest::Key(request) => request.satisfied_by_interpreter(interpreter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_explicit_system(&self) -> bool {
|
||||||
|
matches!(self, Self::File(_) | Self::Directory(_))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolchainPreference {
|
impl ToolchainPreference {
|
||||||
|
@ -1468,10 +1447,10 @@ impl fmt::Display for ToolchainRequest {
|
||||||
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
||||||
Self::ExecutableName(name) => write!(f, "executable name `{name}`"),
|
Self::ExecutableName(name) => write!(f, "executable name `{name}`"),
|
||||||
Self::Implementation(implementation) => {
|
Self::Implementation(implementation) => {
|
||||||
write!(f, "{implementation}")
|
write!(f, "{}", implementation.pretty())
|
||||||
}
|
}
|
||||||
Self::ImplementationVersion(implementation, version) => {
|
Self::ImplementationVersion(implementation, version) => {
|
||||||
write!(f, "{implementation} {version}")
|
write!(f, "{} {version}", implementation.pretty())
|
||||||
}
|
}
|
||||||
Self::Key(request) => write!(f, "{request}"),
|
Self::Key(request) => write!(f, "{request}"),
|
||||||
}
|
}
|
||||||
|
@ -1495,75 +1474,49 @@ impl fmt::Display for ToolchainSource {
|
||||||
|
|
||||||
impl fmt::Display for ToolchainPreference {
|
impl fmt::Display for ToolchainPreference {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
let s = match self {
|
||||||
Self::OnlyManaged => f.write_str("managed toolchains"),
|
Self::OnlyManaged => "managed toolchains",
|
||||||
Self::OnlySystem => f.write_str("system toolchains"),
|
Self::PreferManaged | Self::PreferInstalledManaged | Self::PreferSystem => {
|
||||||
Self::PreferInstalledManaged | Self::PreferManaged | Self::PreferSystem => {
|
if cfg!(windows) {
|
||||||
f.write_str("managed or system toolchains")
|
"managed toolchains, system path, or `py` launcher"
|
||||||
|
} else {
|
||||||
|
"managed toolchains or system path"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Self::OnlySystem => {
|
||||||
|
if cfg!(windows) {
|
||||||
|
"system path or `py` launcher"
|
||||||
|
} else {
|
||||||
|
"system path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
f.write_str(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ToolchainNotFound {
|
impl fmt::Display for ToolchainNotFound {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
let sources = match self.environment_preference {
|
||||||
Self::NoPythonInstallation(sources, None | Some(VersionRequest::Any)) => {
|
EnvironmentPreference::Any => {
|
||||||
write!(f, "No Python interpreters found in {sources}")
|
format!("virtual environments or {}", self.toolchain_preference)
|
||||||
}
|
}
|
||||||
Self::NoPythonInstallation(sources, Some(version)) => {
|
EnvironmentPreference::ExplicitSystem => {
|
||||||
write!(f, "No Python {version} interpreters found in {sources}")
|
if self.request.is_explicit_system() {
|
||||||
|
"virtual or system environment".to_string()
|
||||||
|
} else {
|
||||||
|
"virtual environment".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::NoMatchingVersion(sources, VersionRequest::Any) => {
|
EnvironmentPreference::OnlySystem => self.toolchain_preference.to_string(),
|
||||||
write!(f, "No Python interpreter found in {sources}")
|
EnvironmentPreference::OnlyVirtual => "virtual environments".to_string(),
|
||||||
|
};
|
||||||
|
match self.request {
|
||||||
|
ToolchainRequest::Any => {
|
||||||
|
write!(f, "No interpreter found in {sources}")
|
||||||
}
|
}
|
||||||
Self::NoMatchingVersion(sources, version) => {
|
_ => {
|
||||||
write!(f, "No interpreter found for Python {version} in {sources}")
|
write!(f, "No interpreter found for {} in {sources}", self.request)
|
||||||
}
|
|
||||||
Self::NoMatchingImplementation(sources, implementation) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"No interpreter found for {} in {sources}",
|
|
||||||
implementation.pretty()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::NoMatchingImplementationVersion(sources, implementation, version) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"No interpreter found for {} {version} in {sources}",
|
|
||||||
implementation.pretty()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::NoMatchingKey(sources, key) => {
|
|
||||||
write!(f, "No interpreter found key {key} in {sources}")
|
|
||||||
}
|
|
||||||
Self::FileNotFound(path) => write!(
|
|
||||||
f,
|
|
||||||
"Requested interpreter path `{}` does not exist",
|
|
||||||
path.user_display()
|
|
||||||
),
|
|
||||||
Self::DirectoryNotFound(path) => write!(
|
|
||||||
f,
|
|
||||||
"Requested interpreter directory `{}` does not exist",
|
|
||||||
path.user_display()
|
|
||||||
),
|
|
||||||
Self::ExecutableNotFoundInDirectory(directory, executable) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Interpreter directory `{}` does not contain Python executable at `{}`",
|
|
||||||
directory.user_display(),
|
|
||||||
executable.user_display_from(directory)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::ExecutableNotFoundInSearchPath(name) => {
|
|
||||||
write!(f, "Requested Python executable `{name}` not found in PATH")
|
|
||||||
}
|
|
||||||
Self::FileNotExecutable(path) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Python interpreter at `{}` is not executable",
|
|
||||||
path.user_display()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -10,8 +11,8 @@ use crate::discovery::find_toolchain;
|
||||||
use crate::toolchain::Toolchain;
|
use crate::toolchain::Toolchain;
|
||||||
use crate::virtualenv::{virtualenv_python_executable, PyVenvConfiguration};
|
use crate::virtualenv::{virtualenv_python_executable, PyVenvConfiguration};
|
||||||
use crate::{
|
use crate::{
|
||||||
EnvironmentPreference, Error, Interpreter, Prefix, Target, ToolchainPreference,
|
EnvironmentPreference, Error, Interpreter, Prefix, Target, ToolchainNotFound,
|
||||||
ToolchainRequest,
|
ToolchainPreference, ToolchainRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
|
/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
|
||||||
|
@ -24,6 +25,50 @@ struct PythonEnvironmentShared {
|
||||||
interpreter: Interpreter,
|
interpreter: Interpreter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of failed environment discovery.
|
||||||
|
///
|
||||||
|
/// Generally this is cast from [`ToolchainNotFound`] by [`PythonEnvironment::find`].
|
||||||
|
#[derive(Clone, Debug, Error)]
|
||||||
|
pub struct EnvironmentNotFound {
|
||||||
|
request: ToolchainRequest,
|
||||||
|
preference: EnvironmentPreference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ToolchainNotFound> for EnvironmentNotFound {
|
||||||
|
fn from(value: ToolchainNotFound) -> Self {
|
||||||
|
Self {
|
||||||
|
request: value.request,
|
||||||
|
preference: value.environment_preference,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for EnvironmentNotFound {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let environment = match self.preference {
|
||||||
|
EnvironmentPreference::Any => "virtual or system environment",
|
||||||
|
EnvironmentPreference::ExplicitSystem => {
|
||||||
|
if self.request.is_explicit_system() {
|
||||||
|
"virtual or system environment"
|
||||||
|
} else {
|
||||||
|
// TODO(zanieb): We could add a hint to use the `--system` flag here
|
||||||
|
"virtual environment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EnvironmentPreference::OnlySystem => "system environment",
|
||||||
|
EnvironmentPreference::OnlyVirtual => "virtual environment",
|
||||||
|
};
|
||||||
|
match self.request {
|
||||||
|
ToolchainRequest::Any => {
|
||||||
|
write!(f, "No {environment} found")
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
write!(f, "No {environment} found for {}", self.request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PythonEnvironment {
|
impl PythonEnvironment {
|
||||||
/// Find a [`PythonEnvironment`] matching the given request and preference.
|
/// Find a [`PythonEnvironment`] matching the given request and preference.
|
||||||
///
|
///
|
||||||
|
@ -34,13 +79,16 @@ impl PythonEnvironment {
|
||||||
preference: EnvironmentPreference,
|
preference: EnvironmentPreference,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let toolchain = find_toolchain(
|
let toolchain = match find_toolchain(
|
||||||
request,
|
request,
|
||||||
preference,
|
preference,
|
||||||
// Ignore managed toolchains when looking for environments
|
// Ignore managed toolchains when looking for environments
|
||||||
ToolchainPreference::OnlySystem,
|
ToolchainPreference::OnlySystem,
|
||||||
cache,
|
cache,
|
||||||
)??;
|
)? {
|
||||||
|
Ok(toolchain) => toolchain,
|
||||||
|
Err(err) => return Err(EnvironmentNotFound::from(err).into()),
|
||||||
|
};
|
||||||
Ok(Self::from_toolchain(toolchain))
|
Ok(Self::from_toolchain(toolchain))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +97,10 @@ impl PythonEnvironment {
|
||||||
let venv = match fs_err::canonicalize(root.as_ref()) {
|
let venv = match fs_err::canonicalize(root.as_ref()) {
|
||||||
Ok(venv) => venv,
|
Ok(venv) => venv,
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
return Err(Error::NotFound(
|
return Err(Error::MissingEnvironment(EnvironmentNotFound {
|
||||||
crate::ToolchainNotFound::DirectoryNotFound(root.as_ref().to_path_buf()),
|
preference: EnvironmentPreference::Any,
|
||||||
));
|
request: ToolchainRequest::Directory(root.as_ref().to_owned()),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
Err(err) => return Err(Error::Discovery(err.into())),
|
Err(err) => return Err(Error::Discovery(err.into())),
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,7 +70,10 @@ pub enum Error {
|
||||||
KeyError(#[from] toolchain::ToolchainKeyError),
|
KeyError(#[from] toolchain::ToolchainKeyError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
NotFound(#[from] ToolchainNotFound),
|
MissingToolchain(#[from] ToolchainNotFound),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
MissingEnvironment(#[from] environment::EnvironmentNotFound),
|
||||||
}
|
}
|
||||||
|
|
||||||
// The mock interpreters are not valid on Windows so we don't have unit test coverage there
|
// The mock interpreters are not valid on Windows so we don't have unit test coverage there
|
||||||
|
@ -99,7 +102,7 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
implementation::ImplementationName, managed::InstalledToolchains, toolchain::Toolchain,
|
implementation::ImplementationName, managed::InstalledToolchains, toolchain::Toolchain,
|
||||||
virtualenv::virtualenv_python_executable, PythonVersion, ToolchainNotFound,
|
virtualenv::virtualenv_python_executable, PythonVersion, ToolchainNotFound,
|
||||||
ToolchainRequest, ToolchainSource, VersionRequest,
|
ToolchainRequest, ToolchainSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TestContext {
|
struct TestContext {
|
||||||
|
@ -410,7 +413,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))),
|
matches!(result, Ok(Err(ToolchainNotFound { .. }))),
|
||||||
"With an empty path, no Python installation should be detected got {result:?}"
|
"With an empty path, no Python installation should be detected got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -424,7 +427,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))),
|
matches!(result, Ok(Err(ToolchainNotFound { .. }))),
|
||||||
"With an unset path, no Python installation should be detected got {result:?}"
|
"With an unset path, no Python installation should be detected got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -450,7 +453,7 @@ mod tests {
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(
|
||||||
result,
|
result,
|
||||||
Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))
|
Ok(Err(ToolchainNotFound { .. }))
|
||||||
),
|
),
|
||||||
"With an non-executable Python, no Python installation should be detected; got {result:?}"
|
"With an non-executable Python, no Python installation should be detected; got {result:?}"
|
||||||
);
|
);
|
||||||
|
@ -563,7 +566,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))),
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
// TODO(zanieb): We could improve the error handling to hint this to the user
|
// TODO(zanieb): We could improve the error handling to hint this to the user
|
||||||
"If only Python 2 is available, we should not find a toolchain; got {result:?}"
|
"If only Python 2 is available, we should not find a toolchain; got {result:?}"
|
||||||
);
|
);
|
||||||
|
@ -789,13 +792,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
result,
|
|
||||||
Err(ToolchainNotFound::NoMatchingVersion(
|
|
||||||
_,
|
|
||||||
VersionRequest::MajorMinor(3, 9)
|
|
||||||
))
|
|
||||||
),
|
|
||||||
"We should not find a toolchain; got {result:?}"
|
"We should not find a toolchain; got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -816,13 +813,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
result,
|
|
||||||
Err(ToolchainNotFound::NoMatchingVersion(
|
|
||||||
_,
|
|
||||||
VersionRequest::MajorMinorPatch(3, 11, 9)
|
|
||||||
))
|
|
||||||
),
|
|
||||||
"We should not find a toolchain; got {result:?}"
|
"We should not find a toolchain; got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1298,14 +1289,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
result,
|
|
||||||
Err(ToolchainNotFound::NoPythonInstallation(
|
|
||||||
// TODO(zanieb): We need the environment preference in the error
|
|
||||||
ToolchainPreference::OnlySystem,
|
|
||||||
None
|
|
||||||
))
|
|
||||||
),
|
|
||||||
"We should not find an toolchain; got {result:?}"
|
"We should not find an toolchain; got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1322,13 +1306,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
result,
|
|
||||||
Err(ToolchainNotFound::NoMatchingVersion(
|
|
||||||
ToolchainPreference::OnlySystem,
|
|
||||||
VersionRequest::MajorMinorPatch(3, 12, 3)
|
|
||||||
))
|
|
||||||
),
|
|
||||||
"We should not find an toolchain; got {result:?}"
|
"We should not find an toolchain; got {result:?}"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1362,7 +1340,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))),
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
"We should not find it without a specific request"
|
"We should not find it without a specific request"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1375,7 +1353,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Err(ToolchainNotFound::NoMatchingVersion(..))),
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
"We should not find it via a matching version request"
|
"We should not find it via a matching version request"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1569,7 +1547,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
toolchain.interpreter().python_full_version().to_string(),
|
toolchain.interpreter().python_full_version().to_string(),
|
||||||
"3.10.0",
|
"3.10.0",
|
||||||
"We should prefer the requested directory over the system and active virtul toolchains"
|
"We should prefer the requested directory over the system and active virtual environments"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1589,7 +1567,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Err(ToolchainNotFound::FileNotFound(_))),
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
"We should not find the file; got {result:?}"
|
"We should not find the file; got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1639,7 +1617,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
assert!(
|
assert!(
|
||||||
matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))),
|
matches!(result, Err(ToolchainNotFound { .. })),
|
||||||
"We should not the pypy interpreter if not named `python` or requested; got {result:?}"
|
"We should not the pypy interpreter if not named `python` or requested; got {result:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl Toolchain {
|
||||||
// Perform a find first
|
// Perform a find first
|
||||||
match Self::find(&request, environments, preference, cache) {
|
match Self::find(&request, environments, preference, cache) {
|
||||||
Ok(venv) => Ok(venv),
|
Ok(venv) => Ok(venv),
|
||||||
Err(Error::NotFound(_))
|
Err(Error::MissingToolchain(_))
|
||||||
if preference.allows_managed() && client_builder.connectivity.is_online() =>
|
if preference.allows_managed() && client_builder.connectivity.is_online() =>
|
||||||
{
|
{
|
||||||
debug!("Requested Python not found, checking for available download...");
|
debug!("Requested Python not found, checking for available download...");
|
||||||
|
|
|
@ -173,7 +173,7 @@ pub(crate) async fn find_interpreter(
|
||||||
return Ok(venv.into_interpreter());
|
return Ok(venv.into_interpreter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(uv_toolchain::Error::NotFound(_)) => {}
|
Err(uv_toolchain::Error::MissingEnvironment(_)) => {}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ pub(crate) async fn init_environment(
|
||||||
fs_err::remove_dir_all(venv.root())
|
fs_err::remove_dir_all(venv.root())
|
||||||
.context("Failed to remove existing virtual environment")?;
|
.context("Failed to remove existing virtual environment")?;
|
||||||
}
|
}
|
||||||
Err(uv_toolchain::Error::NotFound(_)) => {}
|
Err(uv_toolchain::Error::MissingEnvironment(_)) => {}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2034,6 +2034,8 @@ fn lock_requires_python() -> Result<()> {
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(context.filters())
|
.chain(context.filters())
|
||||||
|
// Platform independent message for the missing toolchain
|
||||||
|
.chain([(" or `py` launcher", "")])
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Install from the lockfile.
|
// Install from the lockfile.
|
||||||
|
@ -2046,7 +2048,7 @@ fn lock_requires_python() -> Result<()> {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: `uv sync` is experimental and may change without warning.
|
warning: `uv sync` is experimental and may change without warning.
|
||||||
Removing virtual environment at: .venv
|
Removing virtual environment at: .venv
|
||||||
error: No interpreter found for Python >=3.12 in system toolchains
|
error: No interpreter found for Python >=3.12 in system path
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -66,7 +66,7 @@ fn missing_venv() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: No Python interpreters found in system toolchains
|
error: No virtual environment found
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
assert!(predicates::path::missing().eval(&context.venv));
|
assert!(predicates::path::missing().eval(&context.venv));
|
||||||
|
|
|
@ -10,14 +10,25 @@ fn toolchain_find() {
|
||||||
let mut context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
let mut context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
||||||
|
|
||||||
// No interpreters on the path
|
// No interpreters on the path
|
||||||
uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###"
|
if cfg!(windows) {
|
||||||
success: false
|
uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###"
|
||||||
exit_code: 2
|
success: false
|
||||||
----- stdout -----
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: No Python interpreters found in system toolchains
|
error: No interpreter found in system path or `py` launcher
|
||||||
"###);
|
"###);
|
||||||
|
} else {
|
||||||
|
uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found in system path
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
// We find the first interpreter on the path
|
// We find the first interpreter on the path
|
||||||
uv_snapshot!(context.filters(), context.toolchain_find(), @r###"
|
uv_snapshot!(context.filters(), context.toolchain_find(), @r###"
|
||||||
|
@ -94,15 +105,26 @@ fn toolchain_find() {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Request PyPy
|
// Request PyPy (which should be missing)
|
||||||
uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###"
|
if cfg!(windows) {
|
||||||
success: false
|
uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###"
|
||||||
exit_code: 2
|
success: false
|
||||||
----- stdout -----
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: No interpreter found for PyPy in system toolchains
|
error: No interpreter found for PyPy in system path or `py` launcher
|
||||||
"###);
|
"###);
|
||||||
|
} else {
|
||||||
|
uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found for PyPy in system path
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
// Swap the order of the Python versions
|
// Swap the order of the Python versions
|
||||||
context.python_versions.reverse();
|
context.python_versions.reverse();
|
||||||
|
|
|
@ -264,7 +264,7 @@ fn create_venv_unknown_python_minor() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No interpreter found for Python 3.100 in system toolchains
|
× No interpreter found for Python 3.100 in system path or `py` launcher
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -274,7 +274,7 @@ fn create_venv_unknown_python_minor() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No interpreter found for Python 3.100 in system toolchains
|
× No interpreter found for Python 3.100 in system path
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,7 @@ fn create_venv_unknown_python_patch() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No interpreter found for Python 3.12.100 in system toolchains
|
× No interpreter found for Python 3.12.100 in system path or `py` launcher
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -312,7 +312,7 @@ fn create_venv_unknown_python_patch() {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No interpreter found for Python 3.12.100 in system toolchains
|
× No interpreter found for Python 3.12.100 in system path
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue