mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 02:48:17 +00:00
Improve display of resolution errors for workspace member conflicts with optional dependencies (#6123)
We have bad error messages for optional (extra) dependencies and development dependencies in workspaces: 1. We weren't showing the full package, so we'd drop `:dev` and `[extra]` by accident 2. We didn't include derived packages, e.g., `member[extra]` in tree processing collapse operation, so we'd include extra clauses like the ones we removed in #6092 Also - Revertsf0de4f71f2
— it turns out it wasn't quite correct and it didn't seem worth using the custom incompatibility anymore. - Fixes a bug in the display of `package:dev` which was not showing `:dev` for some variants (see94d8020b58
)
This commit is contained in:
parent
e6ddce0246
commit
89efe2491b
6 changed files with 71 additions and 62 deletions
|
@ -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 <workspace-member>==0.1.0 is available".
|
||||
fn collapse_unavailable_workspace_members(
|
||||
fn collapse_no_versions_of_workspace_members(
|
||||
tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
workspace_members: &BTreeSet<PackageName>,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> {
|
||||
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 => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -442,21 +442,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.expect("a package was chosen but we don't have a term");
|
||||
|
||||
if let PubGrubPackageInner::Package { ref name, .. } = &*state.next {
|
||||
// Check if the decision was due to the package being a
|
||||
// workspace member
|
||||
if self.workspace_members.contains(name) {
|
||||
state
|
||||
.pubgrub
|
||||
.add_incompatibility(Incompatibility::custom_term(
|
||||
state.next.clone(),
|
||||
term_intersection.clone(),
|
||||
UnavailableReason::Package(
|
||||
UnavailablePackage::WorkspaceMember,
|
||||
),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the decision was due to the package being unavailable
|
||||
if let Some(entry) = self.unavailable_packages.get(name) {
|
||||
state
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue