From 6196bfa8858c7f94d4b09fd1f3d574548ab037db Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 19:27:37 -0500 Subject: [PATCH 1/9] Propagate workspace information to no resolution errors --- crates/uv-resolver/src/error.rs | 4 ++++ crates/uv-resolver/src/manifest.rs | 7 +++++++ crates/uv-resolver/src/pubgrub/report.rs | 2 ++ crates/uv-resolver/src/resolver/mod.rs | 3 +++ crates/uv/src/commands/pip/compile.rs | 1 + crates/uv/src/commands/pip/install.rs | 1 + crates/uv/src/commands/pip/operations.rs | 3 +++ crates/uv/src/commands/pip/sync.rs | 1 + crates/uv/src/commands/project/lock.rs | 1 + crates/uv/src/commands/project/mod.rs | 2 ++ 10 files changed, 25 insertions(+) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 513b99731ea5..c007460bbf11 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -125,6 +125,7 @@ pub struct NoSolutionError { incomplete_packages: FxHashMap>, fork_urls: ForkUrls, markers: ResolverMarkers, + workspace_members: BTreeSet, } impl NoSolutionError { @@ -139,6 +140,7 @@ impl NoSolutionError { incomplete_packages: FxHashMap>, fork_urls: ForkUrls, markers: ResolverMarkers, + workspace_members: BTreeSet, ) -> Self { Self { error, @@ -150,6 +152,7 @@ impl NoSolutionError { incomplete_packages, fork_urls, markers, + workspace_members, } } @@ -211,6 +214,7 @@ impl std::fmt::Display for NoSolutionError { let formatter = PubGrubReportFormatter { available_versions: &self.available_versions, python_requirement: &self.python_requirement, + workspace_members: &self.workspace_members, }; let report = DefaultStringReporter::report_with_formatter(&self.error, &formatter); write!(f, "{report}")?; diff --git a/crates/uv-resolver/src/manifest.rs b/crates/uv-resolver/src/manifest.rs index 91a2d68e54ec..741ae98d0f13 100644 --- a/crates/uv-resolver/src/manifest.rs +++ b/crates/uv-resolver/src/manifest.rs @@ -1,5 +1,6 @@ use either::Either; use std::borrow::Cow; +use std::collections::BTreeSet; use pep508_rs::MarkerEnvironment; use pypi_types::Requirement; @@ -36,6 +37,9 @@ pub struct Manifest { /// The name of the project. pub(crate) project: Option, + /// Members of the project's workspace. + pub(crate) workspace_members: BTreeSet, + /// The installed packages to exclude from consideration during resolution. /// /// These typically represent packages that are being upgraded or reinstalled @@ -58,6 +62,7 @@ impl Manifest { dev: Vec, preferences: Preferences, project: Option, + workspace_members: Option>, exclusions: Exclusions, lookaheads: Vec, ) -> Self { @@ -68,6 +73,7 @@ impl Manifest { dev, preferences, project, + workspace_members: workspace_members.unwrap_or_default(), exclusions, lookaheads, } @@ -82,6 +88,7 @@ impl Manifest { preferences: Preferences::default(), project: None, exclusions: Exclusions::default(), + workspace_members: BTreeSet::new(), lookaheads: Vec::new(), } } diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 5f8606d2a085..8aa8d0dc6723 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -30,6 +30,8 @@ pub(crate) struct PubGrubReportFormatter<'a> { /// The versions that were available for each package pub(crate) python_requirement: &'a PythonRequirement, + + pub(crate) workspace_members: &'a BTreeSet, } impl ReportFormatter, UnavailableReason> diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 1963dae41048..5ea70ac15272 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -102,6 +102,7 @@ struct ResolverState { hasher: HashStrategy, markers: ResolverMarkers, python_requirement: PythonRequirement, + workspace_members: BTreeSet, /// This is derived from `PythonRequirement` once at initialization /// time. It's used in universal mode to filter our dependencies with /// a `python_version` marker expression that has no overlap with the @@ -225,6 +226,7 @@ impl ), groups: Groups::from_manifest(&manifest, markers.marker_environment()), project: manifest.project, + workspace_members: manifest.workspace_members, requirements: manifest.requirements, constraints: manifest.constraints, overrides: manifest.overrides, @@ -1988,6 +1990,7 @@ impl ResolverState( dev: Vec, source_trees: Vec, mut project: Option, + workspace_members: Option>, extras: &ExtrasSpecification, preferences: Vec, installed_packages: InstalledPackages, @@ -230,6 +232,7 @@ pub(crate) async fn resolve( dev, preferences, project, + workspace_members, exclusions, lookaheads, ); diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 32d58db418dc..89466fdaf4ec 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -288,6 +288,7 @@ pub(crate) async fn pip_sync( dev, source_trees, project, + None, &extras, preferences, site_packages.clone(), diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index b928cbad6e8b..0c64a2f6cc6f 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -596,6 +596,7 @@ async fn do_lock( dev, source_trees, None, + Some(workspace.packages().keys().cloned().collect()), &extras, preferences, EmptyInstalledPackages, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 52a434ef4489..4cf74c6b41cd 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -630,6 +630,7 @@ pub(crate) async fn resolve_environment<'a>( dev, source_trees, project, + None, &extras, preferences, EmptyInstalledPackages, @@ -952,6 +953,7 @@ pub(crate) async fn update_environment( dev, source_trees, project, + None, &extras, preferences, site_packages.clone(), From f0de4f71f257132388514cbeb96a77215cf90b3b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 10:54:46 -0500 Subject: [PATCH 2/9] Use a custom `UnavailablePackage` variant for workspace members instead of `NoVersions` --- crates/uv-resolver/src/pubgrub/report.rs | 2 +- crates/uv-resolver/src/resolver/availability.rs | 3 +++ crates/uv-resolver/src/resolver/mod.rs | 17 ++++++++++++++++- crates/uv/tests/workspace.rs | 10 +++++----- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 8aa8d0dc6723..4774abef3ffa 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -523,7 +523,7 @@ impl PubGrubReportFormatter<'_> { reason: reason.clone(), }); } - Some(UnavailablePackage::NotFound) => {} + Some(UnavailablePackage::NotFound | UnavailablePackage::WorkspaceMember) => {} None => {} } diff --git a/crates/uv-resolver/src/resolver/availability.rs b/crates/uv-resolver/src/resolver/availability.rs index 1b01cb25f5da..f40dfe499190 100644 --- a/crates/uv-resolver/src/resolver/availability.rs +++ b/crates/uv-resolver/src/resolver/availability.rs @@ -74,6 +74,8 @@ pub(crate) enum UnavailablePackage { InvalidMetadata(String), /// The package has an invalid structure. InvalidStructure(String), + /// No other versions of the package can be used because it is a workspace member + WorkspaceMember, } impl UnavailablePackage { @@ -85,6 +87,7 @@ impl UnavailablePackage { UnavailablePackage::MissingMetadata => "does not include a `METADATA` file", UnavailablePackage::InvalidMetadata(_) => "has invalid metadata", UnavailablePackage::InvalidStructure(_) => "has an invalid package format", + UnavailablePackage::WorkspaceMember => "is a workspace member", } } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 5ea70ac15272..52bdd861e404 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -451,8 +451,23 @@ impl ResolverState Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because only httpx<=9999 is available and leaf==0.1.0 depends on httpx>9999, we can conclude that leaf==0.1.0 cannot be used. - And because only leaf==0.1.0 is available and you require leaf, we can conclude that the requirements are unsatisfiable. + And because leaf is a workspace member and you require leaf, we can conclude that the requirements are unsatisfiable. "### ); @@ -1256,8 +1256,8 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only bar==0.1.0 is available and bar==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bar depend on anyio==4.2.0. - And because foo==0.1.0 depends on anyio==4.1.0 and only foo==0.1.0 is available, we can conclude that all versions of bar and all versions of foo are incompatible. + ╰─▶ Because bar is a workspace member and bar==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bar depend on anyio==4.2.0. + And because foo==0.1.0 depends on anyio==4.1.0 and foo is a workspace member, we can conclude that all versions of bar and all versions of foo are incompatible. And because you require bar and foo, we can conclude that the requirements are unsatisfiable. "### ); @@ -1324,8 +1324,8 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result< ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only bird==0.1.0 is available and bird==0.1.0 depends on anyio==4.3.0, we can conclude that all versions of bird depend on anyio==4.3.0. - And because knot==0.1.0 depends on anyio==4.2.0 and only knot==0.1.0 is available, we can conclude that all versions of bird and all versions of knot are incompatible. + ╰─▶ Because bird is a workspace member and bird==0.1.0 depends on anyio==4.3.0, we can conclude that all versions of bird depend on anyio==4.3.0. + And because knot==0.1.0 depends on anyio==4.2.0 and knot is a workspace member, we can conclude that all versions of bird and all versions of knot are incompatible. And because you require bird and knot, we can conclude that the requirements are unsatisfiable. "### ); From 5ac80b6b09a24d4a93f5c94de70945389db784fa Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 12:07:23 -0500 Subject: [PATCH 3/9] Collapse no version incompatibilities for workspace members before printing error --- crates/uv-resolver/src/error.rs | 84 ++++++++++++++++++++++++++++++- crates/uv/tests/edit.rs | 2 +- crates/uv/tests/lock.rs | 7 ++- crates/uv/tests/lock_scenarios.rs | 24 ++++----- crates/uv/tests/workspace.rs | 10 ++-- 5 files changed, 103 insertions(+), 24 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index c007460bbf11..16cd801119d9 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -216,7 +216,12 @@ impl std::fmt::Display for NoSolutionError { python_requirement: &self.python_requirement, workspace_members: &self.workspace_members, }; - let report = DefaultStringReporter::report_with_formatter(&self.error, &formatter); + + // Transform the error tree for reporting + let mut tree = self.error.clone(); + collapse_unavailable_workspace_members(&mut tree); + + let report = DefaultStringReporter::report_with_formatter(&tree, &formatter); write!(f, "{report}")?; // Include any additional hints. @@ -236,6 +241,83 @@ impl std::fmt::Display for NoSolutionError { } } +/// Given a [`DerivationTree`], collapse any [`UnavailablePackage::WorkspaceMember`] incompatibilities +/// to avoid saying things like "only ==0.1.0 is available". +fn collapse_unavailable_workspace_members( + tree: &mut DerivationTree, UnavailableReason>, +) { + match tree { + DerivationTree::External(_) => {} + DerivationTree::Derived(derived) => { + match ( + Arc::make_mut(&mut derived.cause1), + Arc::make_mut(&mut derived.cause2), + ) { + // If one node is an unavailable workspace member... + ( + DerivationTree::External(External::Custom( + _, + _, + UnavailableReason::Package(UnavailablePackage::WorkspaceMember), + )), + mut other, + ) + | ( + mut other, + DerivationTree::External(External::Custom( + _, + _, + UnavailableReason::Package(UnavailablePackage::WorkspaceMember), + )), + ) => { + // First, recursively collapse the other side of the tree + collapse_unavailable_workspace_members(&mut other); + + // Then, replace this node with the other tree + *tree = other.clone() + } + // If not, just recurse + _ => { + collapse_unavailable_workspace_members(Arc::make_mut(&mut derived.cause1)); + collapse_unavailable_workspace_members(Arc::make_mut(&mut derived.cause2)); + } + } + } + } +} + +fn iter_tree( + error: &DerivationTree, UnavailableReason>, + depth: usize, +) { + match error { + DerivationTree::Derived(derived) => { + iter_tree(&derived.cause1, depth + 1); + iter_tree(&derived.cause2, depth + 1); + } + DerivationTree::External(external) => { + print!("{}", " ".repeat(depth)); + match external { + External::FromDependencyOf(package, version, dependency, dependency_version) => { + println!("{package}{version} depends on {dependency}{dependency_version}"); + } + External::Custom(package, versions, reason) => match reason { + UnavailableReason::Package(_) => println!("{package} {reason}"), + UnavailableReason::Version(_) => { + println!("{package}{versions} {reason}"); + } + }, + External::NoVersions(package, versions) => { + println!("no versions of {package}{versions}"); + } + External::NotRoot(package, versions) => { + println!("not root {package}{versions}"); + } + } + } + } +} + #[derive(Debug)] pub struct NoSolutionHeader { /// The [`ResolverMarkers`] that caused the failure. diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 23ac75268a50..1eb2183154ba 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2579,7 +2579,7 @@ fn add_error() -> Result<()> { warning: `uv add` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because there are no versions of xyz and project==0.1.0 depends on xyz, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because you require project, we can conclude that the requirements are unsatisfiable. help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps. "###); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index 38cb17ea0a91..a3396f294600 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -2748,8 +2748,7 @@ fn lock_requires_python() -> Result<()> { pygls>=1.1.0,<1.3.0 pygls>1.3.0 cannot be used, we can conclude that pygls>=1.1.0 cannot be used. - And because project==0.1.0 depends on pygls>=1.1.0, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on pygls>=1.1.0 and you require project, we can conclude that the requirements are unsatisfiable. hint: The `requires-python` value (>=3.7) includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `requires-python` value (like >=3.7.9, <4). "###); @@ -5017,7 +5016,7 @@ fn lock_requires_python_no_wheels() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project==0.1.0 depends on dearpygui==1.9.1, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because you require project, we can conclude that the requirements are unsatisfiable. "###); Ok(()) @@ -8212,7 +8211,7 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (python_version > '3.10'): ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project==0.1.0 depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because you require project, we can conclude that the requirements are unsatisfiable. "###); Ok(()) diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index 558cefe16d7d..4077bfec1aaf 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -468,13 +468,13 @@ fn conflict_in_fork() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'darwin'): ╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 depends on package-d==1, we can conclude that all versions of package-b depend on package-d==1. - And because package-c==1.0.0 depends on package-d==2 and only package-c==1.0.0 is available, we can conclude that all versions of package-b and all versions of package-c are incompatible. - And because package-a{sys_platform == 'darwin'}==1.0.0 depends on package-b and package-c, we can conclude that package-a{sys_platform == 'darwin'}==1.0.0 cannot be used. - And because only the following versions of package-a{sys_platform == 'darwin'} are available: + And because package-c==1.0.0 depends on package-d==2, we can conclude that all versions of package-b and package-c==1.0.0 are incompatible. + And because only package-c==1.0.0 is available and package-a{sys_platform == 'darwin'}==1.0.0 depends on package-b, we can conclude that package-a{sys_platform == 'darwin'}==1.0.0 and all versions of package-c are incompatible. + And because package-a{sys_platform == 'darwin'}==1.0.0 depends on package-c and only the following versions of package-a{sys_platform == 'darwin'} are available: package-a{sys_platform == 'darwin'}==1.0.0 package-a{sys_platform == 'darwin'}>=2 - and project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + we can conclude that package-a{sys_platform == 'darwin'}<2 is incompatible. + And because project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2 and you require project, we can conclude that the requirements are unsatisfiable. "### ); @@ -538,7 +538,7 @@ fn fork_conflict_unsatisfiable() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because project==0.1.0 depends on package-a>=2 and package-a<2, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because you require project, we can conclude that the requirements are unsatisfiable. "### ); @@ -1263,7 +1263,7 @@ fn fork_marker_disjoint() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'linux'): ╰─▶ Because project==0.1.0 depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because you require project, we can conclude that the requirements are unsatisfiable. "### ); @@ -3024,8 +3024,8 @@ fn fork_non_local_fork_marker_direct() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. - And because project==0.1.0 depends on package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project==0.1.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. + And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and you require project, we can conclude that the requirements are unsatisfiable. "### ); @@ -3101,9 +3101,9 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> { And because only the following versions of package-c{sys_platform == 'linux'} are available: package-c{sys_platform == 'linux'}==1.0.0 package-c{sys_platform == 'linux'}>=2.0.0 - and package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible. - And because project==0.1.0 depends on package-a==1.0.0 and package-b==1.0.0, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + we can conclude that package-b==1.0.0 and package-c{sys_platform == 'linux'}<2.0.0 are incompatible. + And because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and project==0.1.0 depends on package-a==1.0.0, we can conclude that package-b==1.0.0 and project==0.1.0 are incompatible. + And because project==0.1.0 depends on package-b==1.0.0 and you require project, we can conclude that the requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index 81c4db0016f1..2107bd6be3af 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -1009,7 +1009,7 @@ fn workspace_inherit_sources() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because library was not found in the cache and leaf==0.1.0 depends on library, we can conclude that leaf==0.1.0 cannot be used. - And because only leaf==0.1.0 is available and you require leaf, we can conclude that the requirements are unsatisfiable. + And because you require leaf, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -1201,7 +1201,7 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because only httpx<=9999 is available and leaf==0.1.0 depends on httpx>9999, we can conclude that leaf==0.1.0 cannot be used. - And because leaf is a workspace member and you require leaf, we can conclude that the requirements are unsatisfiable. + And because you require leaf, we can conclude that the requirements are unsatisfiable. "### ); @@ -1256,8 +1256,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because bar is a workspace member and bar==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bar depend on anyio==4.2.0. - And because foo==0.1.0 depends on anyio==4.1.0 and foo is a workspace member, we can conclude that all versions of bar and all versions of foo are incompatible. + ╰─▶ Because bar==0.1.0 depends on anyio==4.2.0 and foo==0.1.0 depends on anyio==4.1.0, we can conclude that all versions of bar and foo==0.1.0 are incompatible. And because you require bar and foo, we can conclude that the requirements are unsatisfiable. "### ); @@ -1324,8 +1323,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result< ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because bird is a workspace member and bird==0.1.0 depends on anyio==4.3.0, we can conclude that all versions of bird depend on anyio==4.3.0. - And because knot==0.1.0 depends on anyio==4.2.0 and knot is a workspace member, we can conclude that all versions of bird and all versions of knot are incompatible. + ╰─▶ Because bird==0.1.0 depends on anyio==4.3.0 and knot==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bird and knot==0.1.0 are incompatible. And because you require bird and knot, we can conclude that the requirements are unsatisfiable. "### ); From fa6def3a629f03966d0fb51bb60c5ad07814735c Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 14:01:02 -0500 Subject: [PATCH 4/9] Refactor display of the root package into utility --- crates/uv-resolver/src/error.rs | 8 +- crates/uv-resolver/src/pubgrub/report.rs | 295 ++++++++++++++--------- 2 files changed, 187 insertions(+), 116 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 16cd801119d9..e4f5fa3c07f1 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -260,10 +260,10 @@ fn collapse_unavailable_workspace_members( _, UnavailableReason::Package(UnavailablePackage::WorkspaceMember), )), - mut other, + ref mut other, ) | ( - mut other, + ref mut other, DerivationTree::External(External::Custom( _, _, @@ -271,10 +271,10 @@ fn collapse_unavailable_workspace_members( )), ) => { // First, recursively collapse the other side of the tree - collapse_unavailable_workspace_members(&mut other); + collapse_unavailable_workspace_members(other); // Then, replace this node with the other tree - *tree = other.clone() + *tree = other.clone(); } // If not, just recurse _ => { diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 4774abef3ffa..03711e22f3a9 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -55,12 +55,12 @@ impl ReportFormatter, UnavailableReason> return if let Some(target) = self.python_requirement.target() { format!( "the requested {package} version ({target}) does not satisfy {}", - PackageRange::compatibility(package, set) + self.compatible_range(package, set) ) } else { format!( "the requested {package} version does not satisfy {}", - PackageRange::compatibility(package, set) + self.compatible_range(package, set) ) }; } @@ -71,7 +71,7 @@ impl ReportFormatter, UnavailableReason> return format!( "the current {package} version ({}) does not satisfy {}", self.python_requirement.installed(), - PackageRange::compatibility(package, set) + self.compatible_range(package, set) ); } @@ -88,57 +88,51 @@ impl ReportFormatter, UnavailableReason> if segments == 1 { format!( "only {} is available", - PackageRange::compatibility(package, &complement) + self.compatible_range(package, &complement) ) // Complex case, there are multiple ranges } else { format!( "only the following versions of {} {}", package, - PackageRange::available(package, &complement) + self.availability_range(package, &complement) ) } } } - External::Custom(package, set, reason) => match &**package { - PubGrubPackageInner::Root(Some(name)) => { - format!("{name} cannot be used because {reason}") - } - PubGrubPackageInner::Root(None) => { - format!("your requirements cannot be used because {reason}") - } - _ => match reason { - UnavailableReason::Package(reason) => { - // While there may be a term attached, this error applies to the entire - // package, so we show it for the entire package - format!("{}{reason}", Padded::new("", &package, " ")) - } - UnavailableReason::Version(reason) => { - format!( - "{}{reason}", - Padded::new("", &PackageRange::compatibility(package, set), " ") - ) + External::Custom(package, set, reason) => { + if let Some(root) = self.format_root(package) { + format!("{root} cannot be used because {reason}") + } else { + match reason { + UnavailableReason::Package(reason) => { + // While there may be a term attached, this error applies to the entire + // package, so we show it for the entire package + format!("{}{reason}", Padded::new("", &package, " ")) + } + UnavailableReason::Version(reason) => { + format!( + "{}{reason}", + Padded::new("", &self.compatible_range(package, set), " ") + ) + } } - }, - }, + } + } External::FromDependencyOf(package, package_set, dependency, dependency_set) => { let package_set = self.simplify_set(package_set, package); let dependency_set = self.simplify_set(dependency_set, dependency); - match &**package { - PubGrubPackageInner::Root(Some(name)) => format!( - "{name} depends on {}", - PackageRange::dependency(dependency, &dependency_set) - ), - PubGrubPackageInner::Root(None) => format!( - "you require {}", - PackageRange::dependency(dependency, &dependency_set) - ), - _ => format!( - "{}", - PackageRange::compatibility(package, &package_set) - .depends_on(dependency, &dependency_set), - ), + if let Some(root) = self.format_root_requires(package) { + return format!( + "{root} {}", + self.dependency_range(dependency, &dependency_set) + ); } + format!( + "{}", + self.compatible_range(package, &package_set) + .depends_on(dependency, &dependency_set), + ) } } } @@ -158,19 +152,13 @@ impl ReportFormatter, UnavailableReason> if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => { let range = self.simplify_set(range, package); - format!( - "{} cannot be used", - PackageRange::compatibility(package, &range) - ) + format!("{} cannot be used", self.compatible_range(package, &range)) } [(package, Term::Negative(range))] if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => { let range = self.simplify_set(range, package); - format!( - "{} must be used", - PackageRange::compatibility(package, &range) - ) + format!("{} must be used", self.compatible_range(package, &range)) } [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => self.format_external( &External::FromDependencyOf((*p1).clone(), r1.clone(), (*p2).clone(), r2.clone()), @@ -182,7 +170,7 @@ impl ReportFormatter, UnavailableReason> let mut result = String::new(); let str_terms: Vec<_> = slice .iter() - .map(|(p, t)| format!("{}", PackageTerm::new(p, t))) + .map(|(p, t)| format!("{}", PackageTerm::new(p, t, self))) .collect(); for (index, term) in str_terms.iter().enumerate() { result.push_str(term); @@ -197,7 +185,7 @@ impl ReportFormatter, UnavailableReason> } } if let [(p, t)] = slice { - if PackageTerm::new(p, t).plural() { + if PackageTerm::new(p, t, self).plural() { result.push_str(" are incompatible"); } else { result.push_str(" is incompatible"); @@ -330,6 +318,57 @@ impl ReportFormatter, UnavailableReason> } impl PubGrubReportFormatter<'_> { + /// Return the formatting for "the root package requires", if the given + /// package is the root package. + /// + /// If not given the root package, returns `None`. + fn format_root_requires(&self, package: &PubGrubPackage) -> Option { + match &**package { + PubGrubPackageInner::Root(Some(name)) => Some(format!("{name} depends on")), + PubGrubPackageInner::Root(None) => Some("you require".to_string()), + _ => None, + } + } + + /// Return the formatting for "the root package", if the given + /// package is the root package. + /// + /// If not given the root package, returns `None`. + fn format_root(&self, package: &PubGrubPackage) -> Option { + match &**package { + PubGrubPackageInner::Root(Some(name)) => Some(format!("{name}")), + PubGrubPackageInner::Root(None) => Some("your requirements".to_string()), + _ => None, + } + } + + /// Create a [`PackageRange::compatibility`] display with this formatter attached. + fn compatible_range<'a>( + &'a self, + package: &'a PubGrubPackage, + range: &'a Range, + ) -> PackageRange<'a> { + PackageRange::compatibility(package, range, Some(self)) + } + + /// Create a [`PackageRange::dependency`] display with this formatter attached. + fn dependency_range<'a>( + &'a self, + package: &'a PubGrubPackage, + range: &'a Range, + ) -> PackageRange<'a> { + PackageRange::dependency(package, range, Some(self)) + } + + /// Create a [`PackageRange::availability`] display with this formatter attached. + fn availability_range<'a>( + &'a self, + package: &'a PubGrubPackage, + range: &'a Range, + ) -> PackageRange<'a> { + PackageRange::availability(package, range, Some(self)) + } + /// Format two external incompatibilities, combining them if possible. fn format_both_external( &self, @@ -342,33 +381,26 @@ impl PubGrubReportFormatter<'_> { External::FromDependencyOf(package2, _, dependency2, dependency_set2), ) if package1 == package2 => { let dependency_set1 = self.simplify_set(dependency_set1, dependency1); - let dependency1 = PackageRange::dependency(dependency1, &dependency_set1); + let dependency1 = self.dependency_range(dependency1, &dependency_set1); let dependency_set2 = self.simplify_set(dependency_set2, dependency2); - let dependency2 = PackageRange::dependency(dependency2, &dependency_set2); + let dependency2 = self.dependency_range(dependency2, &dependency_set2); - match &**package1 { - PubGrubPackageInner::Root(Some(name)) => format!( - "{name} depends on {}and {}", - Padded::new("", &dependency1, " "), - dependency2, - ), - PubGrubPackageInner::Root(None) => format!( - "you require {}and {}", + if let Some(root) = self.format_root_requires(package1) { + return format!( + "{root} {}and {}", Padded::new("", &dependency1, " "), dependency2, - ), - _ => { - let package_set = self.simplify_set(package_set1, package1); - - format!( - "{}", - PackageRange::compatibility(package1, &package_set) - .depends_on(dependency1.package, &dependency_set1) - .and(dependency2.package, &dependency_set2), - ) - } + ); } + let package_set = self.simplify_set(package_set1, package1); + + format!( + "{}", + self.compatible_range(package1, &package_set) + .depends_on(dependency1.package, &dependency_set1) + .and(dependency2.package, &dependency_set2), + ) } _ => { let external1 = self.format_external(external1); @@ -718,7 +750,7 @@ impl std::fmt::Display for PubGrubHint { "hint".bold().cyan(), ":".bold(), package.bold(), - PackageRange::compatibility(package, range).bold() + PackageRange::compatibility(package, range, None).bold() ) } Self::NoIndex => { @@ -833,7 +865,7 @@ impl std::fmt::Display for PubGrubHint { "hint".bold().cyan(), ":".bold(), requires_python.bold(), - PackageRange::compatibility(package, package_set).bold(), + PackageRange::compatibility(package, package_set, None).bold(), package_requires_python.bold(), package_requires_python.bold(), ) @@ -846,12 +878,15 @@ impl std::fmt::Display for PubGrubHint { struct PackageTerm<'a> { package: &'a PubGrubPackage, term: &'a Term>, + formatter: &'a PubGrubReportFormatter<'a>, } impl std::fmt::Display for PackageTerm<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.term { - Term::Positive(set) => write!(f, "{}", PackageRange::compatibility(self.package, set)), + Term::Positive(set) => { + write!(f, "{}", self.formatter.compatible_range(self.package, set)) + } Term::Negative(set) => { if let Some(version) = set.as_singleton() { // Note we do not handle the "root" package here but we should never @@ -862,7 +897,8 @@ impl std::fmt::Display for PackageTerm<'_> { write!( f, "{}", - PackageRange::compatibility(self.package, &set.complement()) + self.formatter + .compatible_range(self.package, &set.complement()) ) } } @@ -872,19 +908,29 @@ impl std::fmt::Display for PackageTerm<'_> { impl PackageTerm<'_> { /// Create a new [`PackageTerm`] from a [`PubGrubPackage`] and a [`Term`]. - fn new<'a>(package: &'a PubGrubPackage, term: &'a Term>) -> PackageTerm<'a> { - PackageTerm { package, term } + fn new<'a>( + package: &'a PubGrubPackage, + term: &'a Term>, + formatter: &'a PubGrubReportFormatter<'a>, + ) -> PackageTerm<'a> { + PackageTerm { + package, + term, + formatter, + } } /// Returns `true` if the predicate following this package term should be singular or plural. fn plural(&self) -> bool { match self.term { - Term::Positive(set) => PackageRange::compatibility(self.package, set).plural(), + Term::Positive(set) => self.formatter.compatible_range(self.package, set).plural(), Term::Negative(set) => { if set.as_singleton().is_some() { false } else { - PackageRange::compatibility(self.package, &set.complement()).plural() + self.formatter + .compatible_range(self.package, &set.complement()) + .plural() } } } @@ -905,9 +951,49 @@ struct PackageRange<'a> { package: &'a PubGrubPackage, range: &'a Range, kind: PackageRangeKind, + formatter: Option<&'a PubGrubReportFormatter<'a>>, } impl PackageRange<'_> { + fn compatibility<'a>( + package: &'a PubGrubPackage, + range: &'a Range, + formatter: Option<&'a PubGrubReportFormatter<'a>>, + ) -> PackageRange<'a> { + PackageRange { + package, + range, + kind: PackageRangeKind::Compatibility, + formatter, + } + } + + fn dependency<'a>( + package: &'a PubGrubPackage, + range: &'a Range, + formatter: Option<&'a PubGrubReportFormatter<'a>>, + ) -> PackageRange<'a> { + PackageRange { + package, + range, + kind: PackageRangeKind::Dependency, + formatter, + } + } + + fn availability<'a>( + package: &'a PubGrubPackage, + range: &'a Range, + formatter: Option<&'a PubGrubReportFormatter<'a>>, + ) -> PackageRange<'a> { + PackageRange { + package, + range, + kind: PackageRangeKind::Available, + formatter, + } + } + /// Returns a boolean indicating if the predicate following this package range should /// be singular or plural e.g. if false use " depends on <...>" and /// if true use " depend on <...>" @@ -932,11 +1018,13 @@ impl PackageRange<'_> { impl std::fmt::Display for PackageRange<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Exit early for the root package — the range is not meaningful - let package = match &**self.package { - PubGrubPackageInner::Root(Some(name)) => return write!(f, "{name}"), - PubGrubPackageInner::Root(None) => return write!(f, "your requirements"), - _ => self.package, - }; + if let Some(root) = self + .formatter + .and_then(|formatter| formatter.format_root(self.package)) + { + return write!(f, "{root}"); + } + let package = self.package; if self.range.is_empty() { return write!(f, "{package} ∅"); @@ -984,33 +1072,6 @@ impl std::fmt::Display for PackageRange<'_> { } impl PackageRange<'_> { - fn compatibility<'a>( - package: &'a PubGrubPackage, - range: &'a Range, - ) -> PackageRange<'a> { - PackageRange { - package, - range, - kind: PackageRangeKind::Compatibility, - } - } - - fn dependency<'a>(package: &'a PubGrubPackage, range: &'a Range) -> PackageRange<'a> { - PackageRange { - package, - range, - kind: PackageRangeKind::Dependency, - } - } - - fn available<'a>(package: &'a PubGrubPackage, range: &'a Range) -> PackageRange<'a> { - PackageRange { - package, - range, - kind: PackageRangeKind::Available, - } - } - fn depends_on<'a>( &'a self, package: &'a PubGrubPackage, @@ -1018,7 +1079,12 @@ impl PackageRange<'_> { ) -> DependsOn<'a> { DependsOn { package: self, - dependency1: PackageRange::dependency(package, range), + dependency1: PackageRange { + package, + range, + kind: PackageRangeKind::Dependency, + formatter: self.formatter, + }, dependency2: None, } } @@ -1037,7 +1103,12 @@ impl<'a> DependsOn<'a> { /// /// Note this overwrites previous calls to `DependsOn::and`. fn and(mut self, package: &'a PubGrubPackage, range: &'a Range) -> DependsOn<'a> { - self.dependency2 = Some(PackageRange::dependency(package, range)); + self.dependency2 = Some(PackageRange { + package, + range, + kind: PackageRangeKind::Dependency, + formatter: self.package.formatter, + }); self } } From da737f2b95b0c8575c2158edd29ec45524181a77 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 14:15:58 -0500 Subject: [PATCH 5/9] Add special-case for workspaces when displaying requirements of the root package --- crates/uv-resolver/src/pubgrub/report.rs | 15 +++++++++++++++ crates/uv/tests/edit.rs | 2 +- crates/uv/tests/lock.rs | 6 +++--- crates/uv/tests/lock_scenarios.rs | 10 +++++----- crates/uv/tests/workspace.rs | 8 ++++---- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 03711e22f3a9..cf5e889deaec 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -323,6 +323,11 @@ impl PubGrubReportFormatter<'_> { /// /// If not given the root package, returns `None`. fn format_root_requires(&self, package: &PubGrubPackage) -> Option { + if self.is_workspace() { + if matches!(&**package, PubGrubPackageInner::Root(_)) { + return Some(format!("your workspace requires")); + } + } match &**package { PubGrubPackageInner::Root(Some(name)) => Some(format!("{name} depends on")), PubGrubPackageInner::Root(None) => Some("you require".to_string()), @@ -335,6 +340,11 @@ impl PubGrubReportFormatter<'_> { /// /// If not given the root package, returns `None`. fn format_root(&self, package: &PubGrubPackage) -> Option { + if self.is_workspace() { + if matches!(&**package, PubGrubPackageInner::Root(_)) { + return Some(format!("your workspace's requirements")); + } + } match &**package { PubGrubPackageInner::Root(Some(name)) => Some(format!("{name}")), PubGrubPackageInner::Root(None) => Some("your requirements".to_string()), @@ -342,6 +352,11 @@ impl PubGrubReportFormatter<'_> { } } + /// Whether the resolution error is for a workspace + fn is_workspace(&self) -> bool { + !self.workspace_members.is_empty() + } + /// Create a [`PackageRange::compatibility`] display with this formatter attached. fn compatible_range<'a>( &'a self, diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 1eb2183154ba..5ce5bd464110 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2579,7 +2579,7 @@ fn add_error() -> Result<()> { warning: `uv add` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because there are no versions of xyz and project==0.1.0 depends on xyz, we can conclude that project==0.1.0 cannot be used. - And because you require project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that the requirements are unsatisfiable. help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps. "###); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index a3396f294600..afd00a6102f5 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -2748,7 +2748,7 @@ fn lock_requires_python() -> Result<()> { pygls>=1.1.0,<1.3.0 pygls>1.3.0 cannot be used, we can conclude that pygls>=1.1.0 cannot be used. - And because project==0.1.0 depends on pygls>=1.1.0 and you require project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on pygls>=1.1.0 and your workspace requires project, we can conclude that the requirements are unsatisfiable. hint: The `requires-python` value (>=3.7) includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `requires-python` value (like >=3.7.9, <4). "###); @@ -5016,7 +5016,7 @@ fn lock_requires_python_no_wheels() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project==0.1.0 depends on dearpygui==1.9.1, we can conclude that project==0.1.0 cannot be used. - And because you require project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that the requirements are unsatisfiable. "###); Ok(()) @@ -8211,7 +8211,7 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (python_version > '3.10'): ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project==0.1.0 depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project==0.1.0 cannot be used. - And because you require project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that the requirements are unsatisfiable. "###); Ok(()) diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index 4077bfec1aaf..35f5edb102e9 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -474,7 +474,7 @@ fn conflict_in_fork() -> Result<()> { package-a{sys_platform == 'darwin'}==1.0.0 package-a{sys_platform == 'darwin'}>=2 we can conclude that package-a{sys_platform == 'darwin'}<2 is incompatible. - And because project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2 and you require project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2 and your workspace requires project, we can conclude that the requirements are unsatisfiable. "### ); @@ -538,7 +538,7 @@ fn fork_conflict_unsatisfiable() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because project==0.1.0 depends on package-a>=2 and package-a<2, we can conclude that project==0.1.0 cannot be used. - And because you require project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that the requirements are unsatisfiable. "### ); @@ -1263,7 +1263,7 @@ fn fork_marker_disjoint() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'linux'): ╰─▶ Because project==0.1.0 depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project==0.1.0 cannot be used. - And because you require project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that the requirements are unsatisfiable. "### ); @@ -3025,7 +3025,7 @@ fn fork_non_local_fork_marker_direct() -> Result<()> { × No solution found when resolving dependencies: ╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. And because project==0.1.0 depends on package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project==0.1.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. - And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and you require project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and your workspace requires project, we can conclude that the requirements are unsatisfiable. "### ); @@ -3103,7 +3103,7 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> { package-c{sys_platform == 'linux'}>=2.0.0 we can conclude that package-b==1.0.0 and package-c{sys_platform == 'linux'}<2.0.0 are incompatible. And because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and project==0.1.0 depends on package-a==1.0.0, we can conclude that package-b==1.0.0 and project==0.1.0 are incompatible. - And because project==0.1.0 depends on package-b==1.0.0 and you require project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-b==1.0.0 and your workspace requires project, we can conclude that the requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index 2107bd6be3af..87858c4f764c 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -1009,7 +1009,7 @@ fn workspace_inherit_sources() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because library was not found in the cache and leaf==0.1.0 depends on library, we can conclude that leaf==0.1.0 cannot be used. - And because you require leaf, we can conclude that the requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -1201,7 +1201,7 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because only httpx<=9999 is available and leaf==0.1.0 depends on httpx>9999, we can conclude that leaf==0.1.0 cannot be used. - And because you require leaf, we can conclude that the requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that the requirements are unsatisfiable. "### ); @@ -1257,7 +1257,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because bar==0.1.0 depends on anyio==4.2.0 and foo==0.1.0 depends on anyio==4.1.0, we can conclude that all versions of bar and foo==0.1.0 are incompatible. - And because you require bar and foo, we can conclude that the requirements are unsatisfiable. + And because your workspace requires bar and foo, we can conclude that the requirements are unsatisfiable. "### ); @@ -1324,7 +1324,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result< Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because bird==0.1.0 depends on anyio==4.3.0 and knot==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bird and knot==0.1.0 are incompatible. - And because you require bird and knot, we can conclude that the requirements are unsatisfiable. + And because your workspace requires bird and knot, we can conclude that the requirements are unsatisfiable. "### ); From 87264c0d770279084e6f9961c61e7d72a2fbb2e9 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 14:27:53 -0500 Subject: [PATCH 6/9] Use root formatting utility in conclusions --- crates/uv-resolver/src/pubgrub/report.rs | 7 ++++--- crates/uv/tests/edit.rs | 2 +- crates/uv/tests/lock.rs | 6 +++--- crates/uv/tests/lock_scenarios.rs | 10 +++++----- crates/uv/tests/pip_install.rs | 2 +- crates/uv/tests/workspace.rs | 8 ++++---- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index cf5e889deaec..d7b0ff43d5ef 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -146,7 +146,8 @@ impl ReportFormatter, UnavailableReason> match terms_vec.as_slice() { [] => "the requirements are unsatisfiable".into(), [(root, _)] if matches!(&**(*root), PubGrubPackageInner::Root(_)) => { - "the requirements are unsatisfiable".into() + let root = self.format_root(root).unwrap(); + format!("{root} are unsatisfiable") } [(package, Term::Positive(range))] if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => @@ -346,8 +347,8 @@ impl PubGrubReportFormatter<'_> { } } match &**package { - PubGrubPackageInner::Root(Some(name)) => Some(format!("{name}")), - PubGrubPackageInner::Root(None) => Some("your requirements".to_string()), + PubGrubPackageInner::Root(Some(_)) => Some(format!("the requirements")), + PubGrubPackageInner::Root(None) => Some("the requirements".to_string()), _ => None, } } diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 5ce5bd464110..f5431511db2c 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2579,7 +2579,7 @@ fn add_error() -> Result<()> { warning: `uv add` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because there are no versions of xyz and project==0.1.0 depends on xyz, we can conclude that project==0.1.0 cannot be used. - And because your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps. "###); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index afd00a6102f5..8aafe8ac247a 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -2748,7 +2748,7 @@ fn lock_requires_python() -> Result<()> { pygls>=1.1.0,<1.3.0 pygls>1.3.0 cannot be used, we can conclude that pygls>=1.1.0 cannot be used. - And because project==0.1.0 depends on pygls>=1.1.0 and your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on pygls>=1.1.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. hint: The `requires-python` value (>=3.7) includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `requires-python` value (like >=3.7.9, <4). "###); @@ -5016,7 +5016,7 @@ fn lock_requires_python_no_wheels() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project==0.1.0 depends on dearpygui==1.9.1, we can conclude that project==0.1.0 cannot be used. - And because your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); Ok(()) @@ -8211,7 +8211,7 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (python_version > '3.10'): ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project==0.1.0 depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project==0.1.0 cannot be used. - And because your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); Ok(()) diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index 35f5edb102e9..f98726a4751c 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -474,7 +474,7 @@ fn conflict_in_fork() -> Result<()> { package-a{sys_platform == 'darwin'}==1.0.0 package-a{sys_platform == 'darwin'}>=2 we can conclude that package-a{sys_platform == 'darwin'}<2 is incompatible. - And because project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2 and your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -538,7 +538,7 @@ fn fork_conflict_unsatisfiable() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because project==0.1.0 depends on package-a>=2 and package-a<2, we can conclude that project==0.1.0 cannot be used. - And because your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1263,7 +1263,7 @@ fn fork_marker_disjoint() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'linux'): ╰─▶ Because project==0.1.0 depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project==0.1.0 cannot be used. - And because your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -3025,7 +3025,7 @@ fn fork_non_local_fork_marker_direct() -> Result<()> { × No solution found when resolving dependencies: ╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. And because project==0.1.0 depends on package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project==0.1.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. - And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -3103,7 +3103,7 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> { package-c{sys_platform == 'linux'}>=2.0.0 we can conclude that package-b==1.0.0 and package-c{sys_platform == 'linux'}<2.0.0 are incompatible. And because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and project==0.1.0 depends on package-a==1.0.0, we can conclude that package-b==1.0.0 and project==0.1.0 are incompatible. - And because project==0.1.0 depends on package-b==1.0.0 and your workspace requires project, we can conclude that the requirements are unsatisfiable. + And because project==0.1.0 depends on package-b==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 78351b7c571b..23c1fe60b000 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -387,7 +387,7 @@ werkzeug==3.0.1 ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because flask==3.0.2 depends on click>=8.1.3 and you require click==7.0.0, we can conclude that your requirements and flask==3.0.2 are incompatible. + ╰─▶ Because flask==3.0.2 depends on click>=8.1.3 and you require click==7.0.0, we can conclude that the requirements and flask==3.0.2 are incompatible. And because you require flask==3.0.2, we can conclude that the requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index 87858c4f764c..b267b507fadf 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -1009,7 +1009,7 @@ fn workspace_inherit_sources() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because library was not found in the cache and leaf==0.1.0 depends on library, we can conclude that leaf==0.1.0 cannot be used. - And because your workspace requires leaf, we can conclude that the requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -1201,7 +1201,7 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because only httpx<=9999 is available and leaf==0.1.0 depends on httpx>9999, we can conclude that leaf==0.1.0 cannot be used. - And because your workspace requires leaf, we can conclude that the requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1257,7 +1257,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because bar==0.1.0 depends on anyio==4.2.0 and foo==0.1.0 depends on anyio==4.1.0, we can conclude that all versions of bar and foo==0.1.0 are incompatible. - And because your workspace requires bar and foo, we can conclude that the requirements are unsatisfiable. + And because your workspace requires bar and foo, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1324,7 +1324,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result< Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because bird==0.1.0 depends on anyio==4.3.0 and knot==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bird and knot==0.1.0 are incompatible. - And because your workspace requires bird and knot, we can conclude that the requirements are unsatisfiable. + And because your workspace requires bird and knot, we can conclude that your workspace's requirements are unsatisfiable. "### ); From d0a0142b82176358d6f19d87a864b87c43016a87 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 14:46:17 -0500 Subject: [PATCH 7/9] Drop derivation tree display utility --- crates/uv-resolver/src/error.rs | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index e4f5fa3c07f1..32436a2cec67 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -286,38 +286,6 @@ fn collapse_unavailable_workspace_members( } } -fn iter_tree( - error: &DerivationTree, UnavailableReason>, - depth: usize, -) { - match error { - DerivationTree::Derived(derived) => { - iter_tree(&derived.cause1, depth + 1); - iter_tree(&derived.cause2, depth + 1); - } - DerivationTree::External(external) => { - print!("{}", " ".repeat(depth)); - match external { - External::FromDependencyOf(package, version, dependency, dependency_version) => { - println!("{package}{version} depends on {dependency}{dependency_version}"); - } - External::Custom(package, versions, reason) => match reason { - UnavailableReason::Package(_) => println!("{package} {reason}"), - UnavailableReason::Version(_) => { - println!("{package}{versions} {reason}"); - } - }, - External::NoVersions(package, versions) => { - println!("no versions of {package}{versions}"); - } - External::NotRoot(package, versions) => { - println!("not root {package}{versions}"); - } - } - } - } -} - #[derive(Debug)] pub struct NoSolutionHeader { /// The [`ResolverMarkers`] that caused the failure. From 139c76ef7d9f9b6c75c4e1529920f85b3c054694 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 15:00:30 -0500 Subject: [PATCH 8/9] Avoid showing ranges for workspace member packages --- crates/uv-resolver/src/pubgrub/report.rs | 31 +++++++++++++++++++++--- crates/uv/tests/edit.rs | 2 +- crates/uv/tests/lock.rs | 6 ++--- crates/uv/tests/lock_scenarios.rs | 14 +++++------ crates/uv/tests/workspace.rs | 8 +++--- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index d7b0ff43d5ef..df2bb5778947 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -326,7 +326,7 @@ impl PubGrubReportFormatter<'_> { fn format_root_requires(&self, package: &PubGrubPackage) -> Option { if self.is_workspace() { if matches!(&**package, PubGrubPackageInner::Root(_)) { - return Some(format!("your workspace requires")); + return Some("your workspace requires".to_string()); } } match &**package { @@ -343,21 +343,37 @@ impl PubGrubReportFormatter<'_> { fn format_root(&self, package: &PubGrubPackage) -> Option { if self.is_workspace() { if matches!(&**package, PubGrubPackageInner::Root(_)) { - return Some(format!("your workspace's requirements")); + return Some("your workspace's requirements".to_string()); } } match &**package { - PubGrubPackageInner::Root(Some(_)) => Some(format!("the requirements")), + PubGrubPackageInner::Root(Some(_)) => Some("the requirements".to_string()), PubGrubPackageInner::Root(None) => Some("the requirements".to_string()), _ => None, } } - /// Whether the resolution error is for a workspace + /// Whether the resolution error is for a workspace. fn is_workspace(&self) -> bool { !self.workspace_members.is_empty() } + /// Return a display name for the package if it is a workspace member. + fn format_workspace_member(&self, package: &PubGrubPackage) -> Option { + match &**package { + PubGrubPackageInner::Package { name, .. } + | PubGrubPackageInner::Extra { name, .. } + | PubGrubPackageInner::Dev { name, .. } => { + if self.workspace_members.contains(name) { + Some(format!("{name}")) + } else { + None + } + } + _ => None, + } + } + /// Create a [`PackageRange::compatibility`] display with this formatter attached. fn compatible_range<'a>( &'a self, @@ -1040,6 +1056,13 @@ impl std::fmt::Display for PackageRange<'_> { { return write!(f, "{root}"); } + // Exit early for workspace members, only a single version is available + if let Some(member) = self + .formatter + .and_then(|formatter| formatter.format_workspace_member(self.package)) + { + return write!(f, "{member}"); + } let package = self.package; if self.range.is_empty() { diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index f5431511db2c..e0c243f7a635 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2578,7 +2578,7 @@ fn add_error() -> Result<()> { ----- stderr ----- warning: `uv add` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because there are no versions of xyz and project==0.1.0 depends on xyz, we can conclude that project==0.1.0 cannot be used. + ╰─▶ Because there are no versions of xyz and project depends on xyz, we can conclude that project cannot be used. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps. "###); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index 8aafe8ac247a..524a48bf89b9 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -2748,7 +2748,7 @@ fn lock_requires_python() -> Result<()> { pygls>=1.1.0,<1.3.0 pygls>1.3.0 cannot be used, we can conclude that pygls>=1.1.0 cannot be used. - And because project==0.1.0 depends on pygls>=1.1.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. + And because project depends on pygls>=1.1.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. hint: The `requires-python` value (>=3.7) includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `requires-python` value (like >=3.7.9, <4). "###); @@ -5015,7 +5015,7 @@ fn lock_requires_python_no_wheels() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project==0.1.0 depends on dearpygui==1.9.1, we can conclude that project==0.1.0 cannot be used. + ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project depends on dearpygui==1.9.1, we can conclude that project cannot be used. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); @@ -8210,7 +8210,7 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (python_version > '3.10'): - ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project==0.1.0 depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project==0.1.0 cannot be used. + ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project cannot be used. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index f98726a4751c..fd5263cf3447 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -474,7 +474,7 @@ fn conflict_in_fork() -> Result<()> { package-a{sys_platform == 'darwin'}==1.0.0 package-a{sys_platform == 'darwin'}>=2 we can conclude that package-a{sys_platform == 'darwin'}<2 is incompatible. - And because project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. + And because project depends on package-a{sys_platform == 'darwin'}<2 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -537,7 +537,7 @@ fn fork_conflict_unsatisfiable() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because project==0.1.0 depends on package-a>=2 and package-a<2, we can conclude that project==0.1.0 cannot be used. + ╰─▶ Because project depends on package-a>=2 and package-a<2, we can conclude that project cannot be used. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1262,7 +1262,7 @@ fn fork_marker_disjoint() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'linux'): - ╰─▶ Because project==0.1.0 depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project==0.1.0 cannot be used. + ╰─▶ Because project depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project cannot be used. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -3024,8 +3024,8 @@ fn fork_non_local_fork_marker_direct() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. - And because project==0.1.0 depends on package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project==0.1.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. - And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. + And because project depends on package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. + And because project depends on package-b{sys_platform == 'darwin'}==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -3102,8 +3102,8 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> { package-c{sys_platform == 'linux'}==1.0.0 package-c{sys_platform == 'linux'}>=2.0.0 we can conclude that package-b==1.0.0 and package-c{sys_platform == 'linux'}<2.0.0 are incompatible. - And because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and project==0.1.0 depends on package-a==1.0.0, we can conclude that package-b==1.0.0 and project==0.1.0 are incompatible. - And because project==0.1.0 depends on package-b==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. + And because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and project depends on package-a==1.0.0, we can conclude that package-b==1.0.0 and project are incompatible. + And because project depends on package-b==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index b267b507fadf..32fb6c3ed4d9 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -1008,7 +1008,7 @@ fn workspace_inherit_sources() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because library was not found in the cache and leaf==0.1.0 depends on library, we can conclude that leaf==0.1.0 cannot be used. + ╰─▶ Because library was not found in the cache and leaf depends on library, we can conclude that leaf cannot be used. And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled @@ -1200,7 +1200,7 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only httpx<=9999 is available and leaf==0.1.0 depends on httpx>9999, we can conclude that leaf==0.1.0 cannot be used. + ╰─▶ Because only httpx<=9999 is available and leaf depends on httpx>9999, we can conclude that leaf cannot be used. And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1256,7 +1256,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because bar==0.1.0 depends on anyio==4.2.0 and foo==0.1.0 depends on anyio==4.1.0, we can conclude that all versions of bar and foo==0.1.0 are incompatible. + ╰─▶ Because bar depends on anyio==4.2.0 and foo depends on anyio==4.1.0, we can conclude that bar and foo are incompatible. And because your workspace requires bar and foo, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1323,7 +1323,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result< ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because bird==0.1.0 depends on anyio==4.3.0 and knot==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bird and knot==0.1.0 are incompatible. + ╰─▶ Because bird depends on anyio==4.3.0 and knot depends on anyio==4.2.0, we can conclude that bird and knot are incompatible. And because your workspace requires bird and knot, we can conclude that your workspace's requirements are unsatisfiable. "### ); From b03445be56bd9d040ca8aa710e86b96a88fbc848 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 14 Aug 2024 15:18:32 -0500 Subject: [PATCH 9/9] Improve "cannot be used" for workspace members --- crates/uv-resolver/src/pubgrub/report.rs | 6 +++++- crates/uv/tests/edit.rs | 2 +- crates/uv/tests/lock.rs | 4 ++-- crates/uv/tests/lock_scenarios.rs | 4 ++-- crates/uv/tests/workspace.rs | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index df2bb5778947..32b235221706 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -153,7 +153,11 @@ impl ReportFormatter, UnavailableReason> if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => { let range = self.simplify_set(range, package); - format!("{} cannot be used", self.compatible_range(package, &range)) + if let Some(member) = self.format_workspace_member(package) { + format!("{member}'s requirements are unsatisfiable") + } else { + format!("{} cannot be used", self.compatible_range(package, &range)) + } } [(package, Term::Negative(range))] if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index e0c243f7a635..2cca891ce407 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2578,7 +2578,7 @@ fn add_error() -> Result<()> { ----- stderr ----- warning: `uv add` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because there are no versions of xyz and project depends on xyz, we can conclude that project cannot be used. + ╰─▶ Because there are no versions of xyz and project depends on xyz, we can conclude that project's requirements are unsatisfiable. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps. "###); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index 524a48bf89b9..33170b3f6461 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -5015,7 +5015,7 @@ fn lock_requires_python_no_wheels() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project depends on dearpygui==1.9.1, we can conclude that project cannot be used. + ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project depends on dearpygui==1.9.1, we can conclude that project's requirements are unsatisfiable. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); @@ -8210,7 +8210,7 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (python_version > '3.10'): - ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project cannot be used. + ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project's requirements are unsatisfiable. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index fd5263cf3447..4e76f13aac7e 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -537,7 +537,7 @@ fn fork_conflict_unsatisfiable() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because project depends on package-a>=2 and package-a<2, we can conclude that project cannot be used. + ╰─▶ Because project depends on package-a>=2 and package-a<2, we can conclude that project's requirements are unsatisfiable. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1262,7 +1262,7 @@ fn fork_marker_disjoint() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'linux'): - ╰─▶ Because project depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project cannot be used. + ╰─▶ Because project depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project's requirements are unsatisfiable. And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index 32fb6c3ed4d9..6c8a32fe0117 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -1008,7 +1008,7 @@ fn workspace_inherit_sources() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because library was not found in the cache and leaf depends on library, we can conclude that leaf cannot be used. + ╰─▶ Because library was not found in the cache and leaf depends on library, we can conclude that leaf's requirements are unsatisfiable. And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled @@ -1200,7 +1200,7 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only httpx<=9999 is available and leaf depends on httpx>9999, we can conclude that leaf cannot be used. + ╰─▶ Because only httpx<=9999 is available and leaf depends on httpx>9999, we can conclude that leaf's requirements are unsatisfiable. And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. "### );