mirror of https://github.com/astral-sh/uv
Add resolver error context to `run` and `tool run` (#5991)
## Summary Closes https://github.com/astral-sh/uv/issues/5530.
This commit is contained in:
parent
f10c28225c
commit
2822dde8cb
|
|
@ -128,17 +128,7 @@ pub struct NoSolutionError {
|
|||
}
|
||||
|
||||
impl NoSolutionError {
|
||||
pub fn header(&self) -> String {
|
||||
match &self.markers {
|
||||
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => {
|
||||
"No solution found when resolving dependencies:".to_string()
|
||||
}
|
||||
ResolverMarkers::Fork(markers) => {
|
||||
format!("No solution found when resolving dependencies for split ({markers:?}):")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`NoSolutionError`] from a [`pubgrub::NoSolutionError`].
|
||||
pub(crate) fn new(
|
||||
error: pubgrub::NoSolutionError<UvDependencyProvider>,
|
||||
available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>,
|
||||
|
|
@ -206,6 +196,11 @@ impl NoSolutionError {
|
|||
collapse(derivation_tree)
|
||||
.expect("derivation tree should contain at least one external term")
|
||||
}
|
||||
|
||||
/// Initialize a [`NoSolutionHeader`] for this error.
|
||||
pub fn header(&self) -> NoSolutionHeader {
|
||||
NoSolutionHeader::new(self.markers.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NoSolutionError {}
|
||||
|
|
@ -236,3 +231,58 @@ impl std::fmt::Display for NoSolutionError {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoSolutionHeader {
|
||||
/// The [`ResolverMarkers`] that caused the failure.
|
||||
markers: ResolverMarkers,
|
||||
/// The additional context for the resolution failure.
|
||||
context: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl NoSolutionHeader {
|
||||
/// Create a new [`NoSolutionHeader`] with the given [`ResolverMarkers`].
|
||||
pub fn new(markers: ResolverMarkers) -> Self {
|
||||
Self {
|
||||
markers,
|
||||
context: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the context for the resolution failure.
|
||||
#[must_use]
|
||||
pub fn with_context(mut self, context: &'static str) -> Self {
|
||||
self.context = Some(context);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NoSolutionHeader {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.markers {
|
||||
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal { .. } => {
|
||||
if let Some(context) = self.context {
|
||||
write!(
|
||||
f,
|
||||
"No solution found when resolving {context} dependencies:"
|
||||
)
|
||||
} else {
|
||||
write!(f, "No solution found when resolving dependencies:")
|
||||
}
|
||||
}
|
||||
ResolverMarkers::Fork(markers) => {
|
||||
if let Some(context) = self.context {
|
||||
write!(
|
||||
f,
|
||||
"No solution found when resolving {context} dependencies for split ({markers:?}):",
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"No solution found when resolving dependencies for split ({markers:?}):",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
pub use dependency_mode::DependencyMode;
|
||||
pub use error::{NoSolutionError, ResolveError};
|
||||
pub use error::{NoSolutionError, NoSolutionHeader, ResolveError};
|
||||
pub use exclude_newer::ExcludeNewer;
|
||||
pub use exclusions::Exclusions;
|
||||
pub use flat_index::FlatIndex;
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ struct DependencyEdit<'a> {
|
|||
#[diagnostic()]
|
||||
struct WithHelp {
|
||||
/// The header to render in the error message.
|
||||
header: String,
|
||||
header: uv_resolver::NoSolutionHeader,
|
||||
|
||||
/// The underlying error.
|
||||
#[source]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
use tracing::debug;
|
||||
|
||||
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
|
||||
use crate::commands::project::{resolve_environment, sync_environment};
|
||||
use crate::commands::SharedState;
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::ResolverInstallerSettings;
|
||||
use cache_key::{cache_digest, hash_digest};
|
||||
use distribution_types::Resolution;
|
||||
use uv_cache::{Cache, CacheBucket};
|
||||
|
|
@ -13,6 +8,12 @@ use uv_configuration::{Concurrency, PreviewMode};
|
|||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
use uv_requirements::RequirementsSpecification;
|
||||
|
||||
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
|
||||
use crate::commands::project::{resolve_environment, sync_environment, ProjectError};
|
||||
use crate::commands::SharedState;
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::ResolverInstallerSettings;
|
||||
|
||||
/// A [`PythonEnvironment`] stored in the cache.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CachedEnvironment(PythonEnvironment);
|
||||
|
|
@ -39,7 +40,7 @@ impl CachedEnvironment {
|
|||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Result<Self, ProjectError> {
|
||||
// When caching, always use the base interpreter, rather than that of the virtual
|
||||
// environment.
|
||||
let interpreter = if let Some(interpreter) = interpreter.to_base_interpreter(cache)? {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,18 @@ pub(crate) enum ProjectError {
|
|||
#[error(transparent)]
|
||||
Lock(#[from] uv_resolver::LockError),
|
||||
|
||||
#[error(transparent)]
|
||||
Operation(#[from] pip::operations::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
RequiresPython(#[from] uv_resolver::RequiresPythonError),
|
||||
|
||||
#[error(transparent)]
|
||||
Interpreter(#[from] uv_python::InterpreterError),
|
||||
|
||||
#[error(transparent)]
|
||||
Tool(#[from] uv_tool::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Fmt(#[from] std::fmt::Error),
|
||||
|
||||
|
|
@ -98,12 +110,6 @@ pub(crate) enum ProjectError {
|
|||
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Operation(#[from] pip::operations::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
RequiresPython(#[from] uv_resolver::RequiresPythonError),
|
||||
}
|
||||
|
||||
/// Compute the `Requires-Python` bound for the [`Workspace`].
|
||||
|
|
@ -500,7 +506,7 @@ pub(crate) async fn resolve_environment<'a>(
|
|||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> anyhow::Result<ResolutionGraph> {
|
||||
) -> Result<ResolutionGraph, ProjectError> {
|
||||
warn_on_requirements_txt_setting(&spec, settings);
|
||||
|
||||
let ResolverSettingsRef {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use std::ffi::OsString;
|
|||
use std::fmt::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use anstream::eprint;
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use tokio::process::Command;
|
||||
|
|
@ -23,13 +24,14 @@ use uv_python::{
|
|||
PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, VersionRequest,
|
||||
};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_scripts::Pep723Script;
|
||||
use uv_scripts::{Pep723Error, Pep723Script};
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceError};
|
||||
|
||||
use crate::commands::pip::loggers::{
|
||||
DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger,
|
||||
};
|
||||
use crate::commands::pip::operations;
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::project::environment::CachedEnvironment;
|
||||
use crate::commands::project::{ProjectError, WorkspacePython};
|
||||
|
|
@ -62,7 +64,7 @@ pub(crate) async fn run(
|
|||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
) -> anyhow::Result<ExitStatus> {
|
||||
if preview.is_disabled() {
|
||||
warn_user_once!("`uv run` is experimental and may change without warning");
|
||||
}
|
||||
|
|
@ -162,7 +164,7 @@ pub(crate) async fn run(
|
|||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
let spec = RequirementsSpecification::from_requirements(requirements);
|
||||
let environment = CachedEnvironment::get_or_create(
|
||||
let result = CachedEnvironment::get_or_create(
|
||||
spec,
|
||||
interpreter,
|
||||
&settings,
|
||||
|
|
@ -184,7 +186,20 @@ pub(crate) async fn run(
|
|||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
let environment = match result {
|
||||
Ok(resolution) => resolution,
|
||||
Err(ProjectError::Operation(operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
let report = miette::Report::msg(format!("{err}"))
|
||||
.context(err.header().with_context("script"));
|
||||
eprint!("{report:?}");
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
Some(environment.into_interpreter())
|
||||
} else {
|
||||
|
|
@ -515,7 +530,7 @@ pub(crate) async fn run(
|
|||
Some(spec) => {
|
||||
debug!("Syncing ephemeral requirements");
|
||||
|
||||
CachedEnvironment::get_or_create(
|
||||
let result = CachedEnvironment::get_or_create(
|
||||
spec,
|
||||
base_interpreter.clone(),
|
||||
&settings,
|
||||
|
|
@ -537,8 +552,22 @@ pub(crate) async fn run(
|
|||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?
|
||||
.into()
|
||||
.await;
|
||||
|
||||
let environment = match result {
|
||||
Ok(resolution) => resolution,
|
||||
Err(ProjectError::Operation(operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
let report = miette::Report::msg(format!("{err}"))
|
||||
.context(err.header().with_context("`--with`"));
|
||||
eprint!("{report:?}");
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
environment.into()
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
@ -612,7 +641,9 @@ pub(crate) async fn run(
|
|||
}
|
||||
|
||||
/// Read a [`Pep723Script`] from the given command.
|
||||
pub(crate) async fn parse_script(command: &ExternalCommand) -> Result<Option<Pep723Script>> {
|
||||
pub(crate) async fn parse_script(
|
||||
command: &ExternalCommand,
|
||||
) -> Result<Option<Pep723Script>, Pep723Error> {
|
||||
// Parse the input command.
|
||||
let command = RunCommand::from(command);
|
||||
|
||||
|
|
@ -621,7 +652,7 @@ pub(crate) async fn parse_script(command: &ExternalCommand) -> Result<Option<Pep
|
|||
};
|
||||
|
||||
// Read the PEP 723 `script` metadata from the target script.
|
||||
Ok(Pep723Script::read(&target).await?)
|
||||
Pep723Script::read(&target).await
|
||||
}
|
||||
|
||||
/// Returns `true` if we can skip creating an additional ephemeral environment in `uv run`.
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ use std::path::PathBuf;
|
|||
use std::str::FromStr;
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anstream::eprint;
|
||||
use anyhow::{bail, Context};
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use pypi_types::Requirement;
|
||||
use tokio::process::Command;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use distribution_types::{Name, UnresolvedRequirementSpecification};
|
||||
use pep440_rs::Version;
|
||||
use pypi_types::Requirement;
|
||||
use uv_cache::Cache;
|
||||
use uv_cli::ExternalCommand;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
|
|
@ -27,12 +28,12 @@ use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
|||
use uv_tool::{entrypoint_paths, InstalledTools};
|
||||
use uv_warnings::{warn_user, warn_user_once};
|
||||
|
||||
use crate::commands::reporters::PythonDownloadReporter;
|
||||
|
||||
use crate::commands::pip::loggers::{
|
||||
DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger,
|
||||
};
|
||||
use crate::commands::project::resolve_names;
|
||||
use crate::commands::pip::operations;
|
||||
use crate::commands::project::{resolve_names, ProjectError};
|
||||
use crate::commands::reporters::PythonDownloadReporter;
|
||||
use crate::commands::{
|
||||
project::environment::CachedEnvironment, tool::common::matching_packages, tool_list,
|
||||
};
|
||||
|
|
@ -76,7 +77,7 @@ pub(crate) async fn run(
|
|||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
) -> anyhow::Result<ExitStatus> {
|
||||
if preview.is_disabled() {
|
||||
warn_user_once!("`{invocation_source}` is experimental and may change without warning");
|
||||
}
|
||||
|
|
@ -98,7 +99,7 @@ pub(crate) async fn run(
|
|||
};
|
||||
|
||||
// Get or create a compatible environment in which to execute the tool.
|
||||
let (from, environment) = get_or_create_environment(
|
||||
let result = get_or_create_environment(
|
||||
&from,
|
||||
with,
|
||||
show_resolution,
|
||||
|
|
@ -114,7 +115,20 @@ pub(crate) async fn run(
|
|||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
let (from, environment) = match result {
|
||||
Ok(resolution) => resolution,
|
||||
Err(ProjectError::Operation(operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
let report =
|
||||
miette::Report::msg(format!("{err}")).context(err.header().with_context("tool"));
|
||||
eprint!("{report:?}");
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// TODO(zanieb): Determine the executable command via the package entry points
|
||||
let executable = target;
|
||||
|
|
@ -218,7 +232,7 @@ pub(crate) async fn run(
|
|||
fn get_entrypoints(
|
||||
from: &PackageName,
|
||||
site_packages: &SitePackages,
|
||||
) -> Result<Vec<(String, PathBuf)>> {
|
||||
) -> anyhow::Result<Vec<(String, PathBuf)>> {
|
||||
let installed = site_packages.get_packages(from);
|
||||
let Some(installed_dist) = installed.first().copied() else {
|
||||
bail!("Expected at least one requirement")
|
||||
|
|
@ -280,6 +294,42 @@ fn warn_executable_not_provided_by_package(
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse a target into a command name and a requirement.
|
||||
fn parse_target(target: &OsString) -> anyhow::Result<(Cow<OsString>, Cow<str>)> {
|
||||
let Some(target_str) = target.to_str() else {
|
||||
return Err(anyhow::anyhow!("Tool command could not be parsed as UTF-8 string. Use `--from` to specify the package name."));
|
||||
};
|
||||
|
||||
// e.g. uv, no special handling
|
||||
let Some((name, version)) = target_str.split_once('@') else {
|
||||
return Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)));
|
||||
};
|
||||
|
||||
// e.g. `uv@`, warn and treat the whole thing as the command
|
||||
if version.is_empty() {
|
||||
debug!("Ignoring empty version request in command");
|
||||
return Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)));
|
||||
}
|
||||
|
||||
// e.g. ignore `git+https://github.com/uv/uv.git@main`
|
||||
if PackageName::from_str(name).is_err() {
|
||||
debug!("Ignoring non-package name `{name}` in command");
|
||||
return Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)));
|
||||
}
|
||||
|
||||
// e.g. `uv@0.1.0`, convert to `uv==0.1.0`
|
||||
if let Ok(version) = Version::from_str(version) {
|
||||
return Ok((
|
||||
Cow::Owned(OsString::from(name)),
|
||||
Cow::Owned(format!("{name}=={version}")),
|
||||
));
|
||||
}
|
||||
|
||||
// e.g. `uv@invalid`, warn and treat the whole thing as the command
|
||||
debug!("Ignoring invalid version request `{version}` in command");
|
||||
Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)))
|
||||
}
|
||||
|
||||
/// Get or create a [`PythonEnvironment`] in which to run the specified tools.
|
||||
///
|
||||
/// If the target tool is already installed in a compatible environment, returns that
|
||||
|
|
@ -299,7 +349,7 @@ async fn get_or_create_environment(
|
|||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<(Requirement, PythonEnvironment)> {
|
||||
) -> Result<(Requirement, PythonEnvironment), ProjectError> {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls);
|
||||
|
|
@ -445,39 +495,3 @@ async fn get_or_create_environment(
|
|||
|
||||
Ok((from, environment.into()))
|
||||
}
|
||||
|
||||
/// Parse a target into a command name and a requirement.
|
||||
fn parse_target(target: &OsString) -> Result<(Cow<OsString>, Cow<str>)> {
|
||||
let Some(target_str) = target.to_str() else {
|
||||
return Err(anyhow::anyhow!("Tool command could not be parsed as UTF-8 string. Use `--from` to specify the package name."));
|
||||
};
|
||||
|
||||
// e.g. uv, no special handling
|
||||
let Some((name, version)) = target_str.split_once('@') else {
|
||||
return Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)));
|
||||
};
|
||||
|
||||
// e.g. `uv@`, warn and treat the whole thing as the command
|
||||
if version.is_empty() {
|
||||
debug!("Ignoring empty version request in command");
|
||||
return Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)));
|
||||
}
|
||||
|
||||
// e.g. ignore `git+https://github.com/uv/uv.git@main`
|
||||
if PackageName::from_str(name).is_err() {
|
||||
debug!("Ignoring non-package name `{name}` in command");
|
||||
return Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)));
|
||||
}
|
||||
|
||||
// e.g. `uv@0.1.0`, convert to `uv==0.1.0`
|
||||
if let Ok(version) = Version::from_str(version) {
|
||||
return Ok((
|
||||
Cow::Owned(OsString::from(name)),
|
||||
Cow::Owned(format!("{name}=={version}")),
|
||||
));
|
||||
}
|
||||
|
||||
// e.g. `uv@invalid`, warn and treat the whole thing as the command
|
||||
debug!("Ignoring invalid version request `{version}` in command");
|
||||
Ok((Cow::Borrowed(target), Cow::Borrowed(target_str)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,6 +332,29 @@ fn run_pep723_script() -> Result<()> {
|
|||
warning: `--no-project` is a no-op for Python scripts with inline metadata, which always run in isolation
|
||||
"###);
|
||||
|
||||
// If the script can't be resolved, we should reference the script.
|
||||
let test_script = context.temp_dir.child("main.py");
|
||||
test_script.write_str(indoc! { r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "add",
|
||||
# ]
|
||||
# ///
|
||||
"#
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("--preview").arg("--no-project").arg("main.py"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Reading inline script metadata from: main.py
|
||||
× No solution found when resolving script dependencies:
|
||||
╰─▶ Because there are no versions of add and you require add, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -505,6 +528,20 @@ fn run_with() -> Result<()> {
|
|||
+ sniffio==1.3.0
|
||||
"###);
|
||||
|
||||
// If the dependencies can't be resolved, we should reference `--with`.
|
||||
uv_snapshot!(context.filters(), context.run().arg("--with").arg("add").arg("main.py"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv run` is experimental and may change without warning
|
||||
Resolved 6 packages in [TIME]
|
||||
Audited 4 packages in [TIME]
|
||||
× No solution found when resolving `--with` dependencies:
|
||||
╰─▶ Because there are no versions of add and you require add, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -902,3 +902,25 @@ fn warn_no_executables_found() {
|
|||
warning: Package `requests` does not provide any executables.
|
||||
"###);
|
||||
}
|
||||
|
||||
/// If we fail to resolve the tool, we should include "tool" in the error message.
|
||||
#[test]
|
||||
fn tool_run_resolution_error() {
|
||||
let context = TestContext::new("3.12").with_filtered_counts();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
uv_snapshot!(context.filters(), context.tool_run()
|
||||
.arg("add")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv tool run` is experimental and may change without warning
|
||||
× No solution found when resolving tool dependencies:
|
||||
╰─▶ Because there are no versions of add and you require add, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue