diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 851000448..e93d170b3 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -221,7 +221,7 @@ impl std::fmt::Display for NoSolutionError { // Transform the error tree for reporting let mut tree = self.error.clone(); - collapse_unavailable_workspace_members(&mut tree); + collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members); if self.workspace_members.len() == 1 { let project = self.workspace_members.iter().next().unwrap(); @@ -248,10 +248,11 @@ impl std::fmt::Display for NoSolutionError { } } -/// Given a [`DerivationTree`], collapse any [`UnavailablePackage::WorkspaceMember`] incompatibilities +/// Given a [`DerivationTree`], collapse any `NoVersion` incompatibilities for workspace members /// to avoid saying things like "only ==0.1.0 is available". -fn collapse_unavailable_workspace_members( +fn collapse_no_versions_of_workspace_members( tree: &mut DerivationTree, UnavailableReason>, + workspace_members: &BTreeSet, ) { match tree { DerivationTree::External(_) => {} @@ -260,33 +261,36 @@ fn collapse_unavailable_workspace_members( 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), - )), - ref mut other, - ) - | ( - ref mut other, - DerivationTree::External(External::Custom( - _, - _, - UnavailableReason::Package(UnavailablePackage::WorkspaceMember), - )), - ) => { - // First, recursively collapse the other side of the tree - collapse_unavailable_workspace_members(other); + // If we have a node for a package with no versions... + (DerivationTree::External(External::NoVersions(package, _)), ref mut other) + | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => { + // First, always recursively visit the other side of the tree + collapse_no_versions_of_workspace_members(other, workspace_members); - // Then, replace this node with the other tree + // Then, if the package is a workspace member... + let (PubGrubPackageInner::Package { name, .. } + | PubGrubPackageInner::Extra { name, .. } + | PubGrubPackageInner::Dev { name, .. }) = &**package + else { + return; + }; + if !workspace_members.contains(name) { + return; + } + + // 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)); + collapse_no_versions_of_workspace_members( + Arc::make_mut(&mut derived.cause1), + workspace_members, + ); + collapse_no_versions_of_workspace_members( + Arc::make_mut(&mut derived.cause2), + workspace_members, + ); } } } diff --git a/crates/uv-resolver/src/pubgrub/package.rs b/crates/uv-resolver/src/pubgrub/package.rs index 5502948c2..c631d4403 100644 --- a/crates/uv-resolver/src/pubgrub/package.rs +++ b/crates/uv-resolver/src/pubgrub/package.rs @@ -196,13 +196,13 @@ impl std::fmt::Display for PubGrubPackageInner { name, extra: None, marker: None, - .. + dev: None, } => write!(f, "{name}"), Self::Package { name, extra: Some(extra), marker: None, - .. + dev: None, } => { write!(f, "{name}[{extra}]") } @@ -210,19 +210,40 @@ impl std::fmt::Display for PubGrubPackageInner { name, extra: None, marker: Some(marker), - .. + dev: None, } => write!(f, "{name}{{{marker}}}"), Self::Package { name, extra: Some(extra), marker: Some(marker), - .. + dev: None, } => { write!(f, "{name}[{extra}]{{{marker}}}") } + Self::Package { + name, + extra: None, + marker: None, + dev: Some(dev), + } => write!(f, "{name}:{dev}"), + Self::Package { + name, + extra: None, + marker: Some(marker), + dev: Some(dev), + } => { + write!(f, "{name}[{dev}]{{{marker}}}") + } Self::Marker { name, marker, .. } => write!(f, "{name}{{{marker}}}"), Self::Extra { name, extra, .. } => write!(f, "{name}[{extra}]"), Self::Dev { name, dev, .. } => write!(f, "{name}:{dev}"), + // It is guaranteed that `extra` and `dev` are never set at the same time. + Self::Package { + name: _, + extra: Some(_), + marker: _, + dev: Some(_), + } => unreachable!(), } } } diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 8a0f832aa..4aafa5ba5 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -385,19 +385,22 @@ impl PubGrubReportFormatter<'_> { /// 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) { - if self.is_single_project_workspace() { - Some("your project".to_string()) - } else { - Some(format!("{name}")) - } + // TODO(zanieb): Improve handling of dev and extra for single-project workspaces + PubGrubPackageInner::Package { + name, extra, dev, .. + } if self.workspace_members.contains(name) => { + if self.is_single_project_workspace() && extra.is_none() && dev.is_none() { + Some("your project".to_string()) } else { - None + Some(format!("{package}")) } } + PubGrubPackageInner::Extra { name, .. } if self.workspace_members.contains(name) => { + Some(format!("{package}")) + } + PubGrubPackageInner::Dev { name, .. } if self.workspace_members.contains(name) => { + Some(format!("{package}")) + } _ => None, } } @@ -615,7 +618,7 @@ impl PubGrubReportFormatter<'_> { reason: reason.clone(), }); } - Some(UnavailablePackage::NotFound | UnavailablePackage::WorkspaceMember) => {} + Some(UnavailablePackage::NotFound) => {} None => {} } diff --git a/crates/uv-resolver/src/resolver/availability.rs b/crates/uv-resolver/src/resolver/availability.rs index f40dfe499..1b01cb25f 100644 --- a/crates/uv-resolver/src/resolver/availability.rs +++ b/crates/uv-resolver/src/resolver/availability.rs @@ -74,8 +74,6 @@ 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 { @@ -87,7 +85,6 @@ 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 8e7c7e0da..3ceab47aa 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -442,21 +442,6 @@ impl ResolverState Result<()> ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only bar is available and bar depends on anyio==4.2.0, we can conclude that bar depends on anyio==4.2.0. - And because foo depends on anyio==4.1.0, we can conclude that foo and bar are incompatible. - And because your workspace requires bar and foo, we can conclude that your workspace's requirements are unsatisfiable. + ╰─▶ Because bar[some-extra] depends on anyio==4.2.0 and foo depends on anyio==4.1.0, we can conclude that foo and bar[some-extra] are incompatible. + And because your workspace requires bar[some-extra] and foo, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1440,7 +1439,7 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_dev() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because bar depends on bar and bar depends on anyio==4.2.0, we can conclude that bar depends on anyio==4.2.0. + ╰─▶ Because bar depends on bar:dev and bar:dev depends on anyio==4.2.0, we can conclude that bar depends on anyio==4.2.0. And because 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. "###