mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25: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.
|
||||
Key(PythonDownloadRequest),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
|
@ -115,29 +116,12 @@ type ToolchainResult = Result<Toolchain, ToolchainNotFound>;
|
|||
|
||||
/// The result of failed toolchain discovery.
|
||||
///
|
||||
/// See [`InterpreterResult`].
|
||||
/// See [`ToolchainResult`].
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum ToolchainNotFound {
|
||||
/// No Python installations were found.
|
||||
NoPythonInstallation(ToolchainPreference, Option<VersionRequest>),
|
||||
/// No Python installations with the requested version were found.
|
||||
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),
|
||||
pub struct ToolchainNotFound {
|
||||
pub request: ToolchainRequest,
|
||||
pub toolchain_preference: ToolchainPreference,
|
||||
pub environment_preference: EnvironmentPreference,
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
if !path.try_exists()? {
|
||||
return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound(
|
||||
path.clone(),
|
||||
)));
|
||||
}
|
||||
Ok(ToolchainResult::Ok(Toolchain {
|
||||
fn find_toolchain_at_file(
|
||||
path: &PathBuf,
|
||||
cache: &Cache,
|
||||
) -> Result<Toolchain, crate::interpreter::Error> {
|
||||
Ok(Toolchain {
|
||||
source: ToolchainSource::ProvidedPath,
|
||||
interpreter: Interpreter::query(path, cache)?,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
fn find_toolchain_at_directory(path: &PathBuf, cache: &Cache) -> Result<ToolchainResult, Error> {
|
||||
if !path.try_exists()? {
|
||||
return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound(
|
||||
path.clone(),
|
||||
)));
|
||||
}
|
||||
fn find_toolchain_at_directory(
|
||||
path: &PathBuf,
|
||||
cache: &Cache,
|
||||
) -> Result<Toolchain, crate::interpreter::Error> {
|
||||
let executable = virtualenv_python_executable(path);
|
||||
if !executable.try_exists()? {
|
||||
return Ok(ToolchainResult::Err(
|
||||
ToolchainNotFound::ExecutableNotFoundInDirectory(path.clone(), executable),
|
||||
));
|
||||
}
|
||||
Ok(ToolchainResult::Ok(Toolchain {
|
||||
Ok(Toolchain {
|
||||
source: ToolchainSource::ProvidedPath,
|
||||
interpreter: Interpreter::query(executable, cache)?,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// 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({
|
||||
if preference.allows(ToolchainSource::ProvidedPath) {
|
||||
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 {
|
||||
Err(Error::SourceNotAllowed(
|
||||
request.clone(),
|
||||
|
@ -626,7 +611,17 @@ pub fn find_toolchains<'a>(
|
|||
debug!("Checking for Python interpreter in {request}");
|
||||
if preference.allows(ToolchainSource::ProvidedPath) {
|
||||
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 {
|
||||
Err(Error::SourceNotAllowed(
|
||||
request.clone(),
|
||||
|
@ -731,31 +726,11 @@ pub(crate) fn find_toolchain(
|
|||
}) {
|
||||
result
|
||||
} else {
|
||||
let err = match request {
|
||||
ToolchainRequest::Implementation(implementation) => {
|
||||
ToolchainNotFound::NoMatchingImplementation(preference, *implementation)
|
||||
}
|
||||
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))
|
||||
Ok(ToolchainResult::Err(ToolchainNotFound {
|
||||
request: request.clone(),
|
||||
environment_preference: environments,
|
||||
toolchain_preference: preference,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -818,10 +793,10 @@ pub fn find_best_toolchain(
|
|||
Ok(
|
||||
find_toolchain(&request, environments, preference, cache)?.map_err(|err| {
|
||||
// Use a more general error in this case since we looked for multiple versions
|
||||
if matches!(err, ToolchainNotFound::NoMatchingVersion(..)) {
|
||||
ToolchainNotFound::NoPythonInstallation(preference, None)
|
||||
} else {
|
||||
err
|
||||
ToolchainNotFound {
|
||||
request,
|
||||
toolchain_preference: err.toolchain_preference,
|
||||
environment_preference: err.environment_preference,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
@ -1149,6 +1124,10 @@ impl ToolchainRequest {
|
|||
ToolchainRequest::Key(request) => request.satisfied_by_interpreter(interpreter),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_explicit_system(&self) -> bool {
|
||||
matches!(self, Self::File(_) | Self::Directory(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolchainPreference {
|
||||
|
@ -1468,10 +1447,10 @@ impl fmt::Display for ToolchainRequest {
|
|||
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
||||
Self::ExecutableName(name) => write!(f, "executable name `{name}`"),
|
||||
Self::Implementation(implementation) => {
|
||||
write!(f, "{implementation}")
|
||||
write!(f, "{}", implementation.pretty())
|
||||
}
|
||||
Self::ImplementationVersion(implementation, version) => {
|
||||
write!(f, "{implementation} {version}")
|
||||
write!(f, "{} {version}", implementation.pretty())
|
||||
}
|
||||
Self::Key(request) => write!(f, "{request}"),
|
||||
}
|
||||
|
@ -1495,75 +1474,49 @@ impl fmt::Display for ToolchainSource {
|
|||
|
||||
impl fmt::Display for ToolchainPreference {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::OnlyManaged => f.write_str("managed toolchains"),
|
||||
Self::OnlySystem => f.write_str("system toolchains"),
|
||||
Self::PreferInstalledManaged | Self::PreferManaged | Self::PreferSystem => {
|
||||
f.write_str("managed or system toolchains")
|
||||
let s = match self {
|
||||
Self::OnlyManaged => "managed toolchains",
|
||||
Self::PreferManaged | Self::PreferInstalledManaged | Self::PreferSystem => {
|
||||
if cfg!(windows) {
|
||||
"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 {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NoPythonInstallation(sources, None | Some(VersionRequest::Any)) => {
|
||||
write!(f, "No Python interpreters found in {sources}")
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let sources = match self.environment_preference {
|
||||
EnvironmentPreference::Any => {
|
||||
format!("virtual environments or {}", self.toolchain_preference)
|
||||
}
|
||||
Self::NoPythonInstallation(sources, Some(version)) => {
|
||||
write!(f, "No Python {version} interpreters found in {sources}")
|
||||
EnvironmentPreference::ExplicitSystem => {
|
||||
if self.request.is_explicit_system() {
|
||||
"virtual or system environment".to_string()
|
||||
} else {
|
||||
"virtual environment".to_string()
|
||||
}
|
||||
}
|
||||
Self::NoMatchingVersion(sources, VersionRequest::Any) => {
|
||||
write!(f, "No Python interpreter found in {sources}")
|
||||
EnvironmentPreference::OnlySystem => self.toolchain_preference.to_string(),
|
||||
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}")
|
||||
}
|
||||
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()
|
||||
)
|
||||
_ => {
|
||||
write!(f, "No interpreter found for {} in {sources}", self.request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -10,8 +11,8 @@ use crate::discovery::find_toolchain;
|
|||
use crate::toolchain::Toolchain;
|
||||
use crate::virtualenv::{virtualenv_python_executable, PyVenvConfiguration};
|
||||
use crate::{
|
||||
EnvironmentPreference, Error, Interpreter, Prefix, Target, ToolchainPreference,
|
||||
ToolchainRequest,
|
||||
EnvironmentPreference, Error, Interpreter, Prefix, Target, ToolchainNotFound,
|
||||
ToolchainPreference, ToolchainRequest,
|
||||
};
|
||||
|
||||
/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
|
||||
|
@ -24,6 +25,50 @@ struct PythonEnvironmentShared {
|
|||
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 {
|
||||
/// Find a [`PythonEnvironment`] matching the given request and preference.
|
||||
///
|
||||
|
@ -34,13 +79,16 @@ impl PythonEnvironment {
|
|||
preference: EnvironmentPreference,
|
||||
cache: &Cache,
|
||||
) -> Result<Self, Error> {
|
||||
let toolchain = find_toolchain(
|
||||
let toolchain = match find_toolchain(
|
||||
request,
|
||||
preference,
|
||||
// Ignore managed toolchains when looking for environments
|
||||
ToolchainPreference::OnlySystem,
|
||||
cache,
|
||||
)??;
|
||||
)? {
|
||||
Ok(toolchain) => toolchain,
|
||||
Err(err) => return Err(EnvironmentNotFound::from(err).into()),
|
||||
};
|
||||
Ok(Self::from_toolchain(toolchain))
|
||||
}
|
||||
|
||||
|
@ -49,9 +97,10 @@ impl PythonEnvironment {
|
|||
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::ToolchainNotFound::DirectoryNotFound(root.as_ref().to_path_buf()),
|
||||
));
|
||||
return Err(Error::MissingEnvironment(EnvironmentNotFound {
|
||||
preference: EnvironmentPreference::Any,
|
||||
request: ToolchainRequest::Directory(root.as_ref().to_owned()),
|
||||
}));
|
||||
}
|
||||
Err(err) => return Err(Error::Discovery(err.into())),
|
||||
};
|
||||
|
|
|
@ -70,7 +70,10 @@ pub enum Error {
|
|||
KeyError(#[from] toolchain::ToolchainKeyError),
|
||||
|
||||
#[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
|
||||
|
@ -99,7 +102,7 @@ mod tests {
|
|||
use crate::{
|
||||
implementation::ImplementationName, managed::InstalledToolchains, toolchain::Toolchain,
|
||||
virtualenv::virtualenv_python_executable, PythonVersion, ToolchainNotFound,
|
||||
ToolchainRequest, ToolchainSource, VersionRequest,
|
||||
ToolchainRequest, ToolchainSource,
|
||||
};
|
||||
|
||||
struct TestContext {
|
||||
|
@ -410,7 +413,7 @@ mod tests {
|
|||
)
|
||||
});
|
||||
assert!(
|
||||
matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))),
|
||||
matches!(result, Ok(Err(ToolchainNotFound { .. }))),
|
||||
"With an empty path, no Python installation should be detected got {result:?}"
|
||||
);
|
||||
|
||||
|
@ -424,7 +427,7 @@ mod tests {
|
|||
)
|
||||
});
|
||||
assert!(
|
||||
matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))),
|
||||
matches!(result, Ok(Err(ToolchainNotFound { .. }))),
|
||||
"With an unset path, no Python installation should be detected got {result:?}"
|
||||
);
|
||||
|
||||
|
@ -450,7 +453,7 @@ mod tests {
|
|||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))
|
||||
Ok(Err(ToolchainNotFound { .. }))
|
||||
),
|
||||
"With an non-executable Python, no Python installation should be detected; got {result:?}"
|
||||
);
|
||||
|
@ -563,7 +566,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
// 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:?}"
|
||||
);
|
||||
|
@ -789,13 +792,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(ToolchainNotFound::NoMatchingVersion(
|
||||
_,
|
||||
VersionRequest::MajorMinor(3, 9)
|
||||
))
|
||||
),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find a toolchain; got {result:?}"
|
||||
);
|
||||
|
||||
|
@ -816,13 +813,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(ToolchainNotFound::NoMatchingVersion(
|
||||
_,
|
||||
VersionRequest::MajorMinorPatch(3, 11, 9)
|
||||
))
|
||||
),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find a toolchain; got {result:?}"
|
||||
);
|
||||
|
||||
|
@ -1298,14 +1289,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(ToolchainNotFound::NoPythonInstallation(
|
||||
// TODO(zanieb): We need the environment preference in the error
|
||||
ToolchainPreference::OnlySystem,
|
||||
None
|
||||
))
|
||||
),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find an toolchain; got {result:?}"
|
||||
);
|
||||
|
||||
|
@ -1322,13 +1306,7 @@ mod tests {
|
|||
},
|
||||
)?;
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(ToolchainNotFound::NoMatchingVersion(
|
||||
ToolchainPreference::OnlySystem,
|
||||
VersionRequest::MajorMinorPatch(3, 12, 3)
|
||||
))
|
||||
),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find an toolchain; got {result:?}"
|
||||
);
|
||||
Ok(())
|
||||
|
@ -1362,7 +1340,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find it without a specific request"
|
||||
);
|
||||
|
||||
|
@ -1375,7 +1353,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(result, Err(ToolchainNotFound::NoMatchingVersion(..))),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find it via a matching version request"
|
||||
);
|
||||
|
||||
|
@ -1569,7 +1547,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
toolchain.interpreter().python_full_version().to_string(),
|
||||
"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(())
|
||||
|
@ -1589,7 +1567,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(result, Err(ToolchainNotFound::FileNotFound(_))),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not find the file; got {result:?}"
|
||||
);
|
||||
|
||||
|
@ -1639,7 +1617,7 @@ mod tests {
|
|||
)
|
||||
})?;
|
||||
assert!(
|
||||
matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))),
|
||||
matches!(result, Err(ToolchainNotFound { .. })),
|
||||
"We should not the pypy interpreter if not named `python` or requested; got {result:?}"
|
||||
);
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ impl Toolchain {
|
|||
// Perform a find first
|
||||
match Self::find(&request, environments, preference, cache) {
|
||||
Ok(venv) => Ok(venv),
|
||||
Err(Error::NotFound(_))
|
||||
Err(Error::MissingToolchain(_))
|
||||
if preference.allows_managed() && client_builder.connectivity.is_online() =>
|
||||
{
|
||||
debug!("Requested Python not found, checking for available download...");
|
||||
|
|
|
@ -173,7 +173,7 @@ pub(crate) async fn find_interpreter(
|
|||
return Ok(venv.into_interpreter());
|
||||
}
|
||||
}
|
||||
Err(uv_toolchain::Error::NotFound(_)) => {}
|
||||
Err(uv_toolchain::Error::MissingEnvironment(_)) => {}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
|
@ -245,7 +245,7 @@ pub(crate) async fn init_environment(
|
|||
fs_err::remove_dir_all(venv.root())
|
||||
.context("Failed to remove existing virtual environment")?;
|
||||
}
|
||||
Err(uv_toolchain::Error::NotFound(_)) => {}
|
||||
Err(uv_toolchain::Error::MissingEnvironment(_)) => {}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
|
|
|
@ -2034,6 +2034,8 @@ fn lock_requires_python() -> Result<()> {
|
|||
.filters()
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
// Platform independent message for the missing toolchain
|
||||
.chain([(" or `py` launcher", "")])
|
||||
.collect();
|
||||
|
||||
// Install from the lockfile.
|
||||
|
@ -2046,7 +2048,7 @@ fn lock_requires_python() -> Result<()> {
|
|||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
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(())
|
||||
|
|
|
@ -66,7 +66,7 @@ fn missing_venv() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No Python interpreters found in system toolchains
|
||||
error: No virtual environment found
|
||||
"###);
|
||||
|
||||
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"]);
|
||||
|
||||
// No interpreters on the path
|
||||
uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
if cfg!(windows) {
|
||||
uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No Python interpreters found in system toolchains
|
||||
"###);
|
||||
----- stderr -----
|
||||
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
|
||||
uv_snapshot!(context.filters(), context.toolchain_find(), @r###"
|
||||
|
@ -94,15 +105,26 @@ fn toolchain_find() {
|
|||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Request PyPy
|
||||
uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
// Request PyPy (which should be missing)
|
||||
if cfg!(windows) {
|
||||
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 toolchains
|
||||
"###);
|
||||
----- stderr -----
|
||||
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
|
||||
context.python_versions.reverse();
|
||||
|
|
|
@ -264,7 +264,7 @@ fn create_venv_unknown_python_minor() {
|
|||
----- stdout -----
|
||||
|
||||
----- 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 {
|
||||
|
@ -274,7 +274,7 @@ fn create_venv_unknown_python_minor() {
|
|||
----- stdout -----
|
||||
|
||||
----- 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 -----
|
||||
|
||||
----- 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 {
|
||||
|
@ -312,7 +312,7 @@ fn create_venv_unknown_python_patch() {
|
|||
----- stdout -----
|
||||
|
||||
----- 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