Collapse unavailable packages in resolver errors (#6154)

Uses my expanding tree reduction knowledge from #6092 to improve the
long-standing issue of verbose messages for unavailable packages.

Implements https://github.com/pubgrub-rs/pubgrub/issues/232, but
post-resolution instead of during resolution.

Partially addresses https://github.com/astral-sh/uv/issues/5046
Closes https://github.com/astral-sh/uv/issues/2519
This commit is contained in:
Zanie Blue 2024-08-16 15:19:59 -05:00 committed by GitHub
parent d643e92d66
commit 05cceee523
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 164 additions and 95 deletions

View file

@ -222,6 +222,13 @@ impl std::fmt::Display for NoSolutionError {
// Transform the error tree for reporting // Transform the error tree for reporting
let mut tree = self.error.clone(); let mut tree = self.error.clone();
let should_display_tree = std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some()
|| tracing::enabled!(tracing::Level::TRACE);
if should_display_tree {
display_tree(&tree, "Resolver derivation tree before reduction");
}
collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members); collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
if self.workspace_members.len() == 1 { if self.workspace_members.len() == 1 {
@ -229,11 +236,10 @@ impl std::fmt::Display for NoSolutionError {
drop_root_dependency_on_project(&mut tree, project); drop_root_dependency_on_project(&mut tree, project);
} }
// Display the tree if enabled collapse_unavailable_versions(&mut tree);
if std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some()
|| tracing::enabled!(tracing::Level::TRACE) if should_display_tree {
{ display_tree(&tree, "Resolver derivation tree after reduction");
display_tree(&tree);
} }
let report = DefaultStringReporter::report_with_formatter(&tree, &formatter); let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
@ -257,15 +263,18 @@ impl std::fmt::Display for NoSolutionError {
} }
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
fn display_tree(error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>) { fn display_tree(
error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
name: &str,
) {
let mut lines = Vec::new(); let mut lines = Vec::new();
display_tree_inner(error, &mut lines, 0); display_tree_inner(error, &mut lines, 0);
lines.reverse(); lines.reverse();
if std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some() { if std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some() {
eprintln!("Resolver error derivation tree\n{}", lines.join("\n")); eprintln!("{name}\n{}", lines.join("\n"));
} else { } else {
trace!("Resolver error derivation tree\n{}", lines.join("\n")); trace!("{name}\n{}", lines.join("\n"));
} }
} }
@ -355,6 +364,105 @@ fn collapse_no_versions_of_workspace_members(
} }
} }
/// Given a [`DerivationTree`], collapse incompatibilities for versions of a package that are
/// unavailable for the same reason to avoid repeating the same message for every unavailable
/// version.
fn collapse_unavailable_versions(
tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
) {
match tree {
DerivationTree::External(_) => {}
DerivationTree::Derived(derived) => {
match (
Arc::make_mut(&mut derived.cause1),
Arc::make_mut(&mut derived.cause2),
) {
// If we have a node for unavailable package versions
(
DerivationTree::External(External::Custom(package, versions, reason)),
ref mut other,
)
| (
ref mut other,
DerivationTree::External(External::Custom(package, versions, reason)),
) => {
// First, recursively collapse the other side of the tree
collapse_unavailable_versions(other);
// If it's not a derived tree, nothing to do.
let DerivationTree::Derived(Derived {
terms,
shared_id,
cause1,
cause2,
}) = other
else {
return;
};
// If the other tree has an unavailable package...
match (&**cause1, &**cause2) {
// Note the following cases are the same, but we need two matches to retain
// the ordering of the causes
(
_,
DerivationTree::External(External::Custom(
other_package,
other_versions,
other_reason,
)),
) => {
// And the package and reason are the same...
if package == other_package && reason == other_reason {
// Collapse both into a new node, with a union of their ranges
*tree = DerivationTree::Derived(Derived {
terms: terms.clone(),
shared_id: *shared_id,
cause1: cause1.clone(),
cause2: Arc::new(DerivationTree::External(External::Custom(
package.clone(),
versions.union(other_versions),
reason.clone(),
))),
});
}
}
(
DerivationTree::External(External::Custom(
other_package,
other_versions,
other_reason,
)),
_,
) => {
// And the package and reason are the same...
if package == other_package && reason == other_reason {
// Collapse both into a new node, with a union of their ranges
*tree = DerivationTree::Derived(Derived {
terms: terms.clone(),
shared_id: *shared_id,
cause1: Arc::new(DerivationTree::External(External::Custom(
package.clone(),
versions.union(other_versions),
reason.clone(),
))),
cause2: cause2.clone(),
});
}
}
_ => {}
}
}
// If not, just recurse
_ => {
collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
}
}
}
}
}
/// Given a [`DerivationTree`], drop dependency incompatibilities from the root /// Given a [`DerivationTree`], drop dependency incompatibilities from the root
/// to the project. /// to the project.
/// ///

View file

@ -241,10 +241,15 @@ fn prune_unzipped() -> Result<()> {
Because only the following versions of iniconfig are available: Because only the following versions of iniconfig are available:
iniconfig<=0.1 iniconfig<=0.1
iniconfig>=1.0.0 iniconfig>=1.0.0
and iniconfig==0.1 network connectivity is disabled, but the metadata wasn't found in the cache, we can conclude that iniconfig<1.0.0 cannot be used. and any of:
And because iniconfig==1.0.0 network connectivity is disabled, but the metadata wasn't found in the cache and iniconfig==1.0.1 network connectivity is disabled, but the metadata wasn't found in the cache, we can conclude that iniconfig<1.1.0 cannot be used. iniconfig==0.1
And because iniconfig==1.1.0 network connectivity is disabled, but the metadata wasn't found in the cache and iniconfig==1.1.1 network connectivity is disabled, but the metadata wasn't found in the cache, we can conclude that iniconfig<2.0.0 cannot be used. iniconfig==1.0.0
And because iniconfig==2.0.0 network connectivity is disabled, but the metadata wasn't found in the cache and you require iniconfig, we can conclude that your requirements are unsatisfiable. iniconfig==1.0.1
iniconfig==1.1.0
iniconfig==1.1.1
iniconfig==2.0.0
network connectivity is disabled, but the metadata wasn't found in the cache, we can conclude that iniconfig<1.0.0 cannot be used.
And because you require iniconfig, we can conclude that your requirements are unsatisfiable.
hint: Pre-releases are available for iniconfig in the requested range (e.g., 0.2.dev0), but pre-releases weren't enabled (try: `--prerelease=allow`) hint: Pre-releases are available for iniconfig in the requested range (e.g., 0.2.dev0), but pre-releases weren't enabled (try: `--prerelease=allow`)

View file

@ -1925,95 +1925,51 @@ fn install_only_binary_all_and_no_binary_all() {
anyio>=3.0.0,<=3.6.2 anyio>=3.0.0,<=3.6.2
anyio>=3.7.0,<=3.7.1 anyio>=3.7.0,<=3.7.1
anyio>=4.0.0 anyio>=4.0.0
and anyio==1.0.0 has no usable wheels and building from source is disabled, we can conclude that any of: and any of:
anyio==1.0.0
anyio==1.1.0
anyio==1.2.0
anyio==1.2.1
anyio==1.2.2
anyio==1.2.3
anyio==1.3.0
anyio==1.3.1
anyio==1.4.0
anyio==2.0.0
anyio==2.0.1
anyio==2.0.2
anyio==2.1.0
anyio==2.2.0
anyio==3.0.0
anyio==3.0.1
anyio==3.1.0
anyio==3.2.0
anyio==3.2.1
anyio==3.3.0
anyio==3.3.1
anyio==3.3.2
anyio==3.3.3
anyio==3.3.4
anyio==3.4.0
anyio==3.5.0
anyio==3.6.0
anyio==3.6.1
anyio==3.6.2
anyio==3.7.0
anyio==3.7.1
anyio==4.0.0
anyio==4.1.0
anyio==4.2.0
anyio==4.3.0
anyio==4.4.0
has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<1.1.0 anyio<1.1.0
anyio>1.4.0,<2.0.0 anyio>1.4.0,<2.0.0
anyio>2.2.0,<3.0.0 anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0 anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0 anyio>3.7.1,<4.0.0
cannot be used. cannot be used.
And because anyio==1.1.0 has no usable wheels and building from source is disabled and anyio==1.2.0 has no usable wheels and building from source is disabled, we can conclude that any of: And because you require anyio, we can conclude that your requirements are unsatisfiable.
anyio<1.2.1
anyio>1.4.0,<2.0.0
anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==1.2.1 has no usable wheels and building from source is disabled and anyio==1.2.2 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<1.2.3
anyio>1.4.0,<2.0.0
anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==1.2.3 has no usable wheels and building from source is disabled and anyio==1.3.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<1.3.1
anyio>1.4.0,<2.0.0
anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==1.3.1 has no usable wheels and building from source is disabled and anyio==1.4.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<2.0.0
anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==2.0.0 has no usable wheels and building from source is disabled and anyio==2.0.1 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<2.0.2
anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==2.0.2 has no usable wheels and building from source is disabled and anyio==2.1.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<2.2.0
anyio>2.2.0,<3.0.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==2.2.0 has no usable wheels and building from source is disabled and anyio==3.0.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.0.1
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.0.1 has no usable wheels and building from source is disabled and anyio==3.1.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.2.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.2.0 has no usable wheels and building from source is disabled and anyio==3.2.1 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.3.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.3.0 has no usable wheels and building from source is disabled and anyio==3.3.1 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.3.2
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.3.2 has no usable wheels and building from source is disabled and anyio==3.3.3 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.3.4
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.3.4 has no usable wheels and building from source is disabled and anyio==3.4.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.5.0
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.5.0 has no usable wheels and building from source is disabled and anyio==3.6.0 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.6.1
anyio>3.6.2,<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.6.1 has no usable wheels and building from source is disabled and anyio==3.6.2 has no usable wheels and building from source is disabled, we can conclude that any of:
anyio<3.7.0
anyio>3.7.1,<4.0.0
cannot be used.
And because anyio==3.7.0 has no usable wheels and building from source is disabled and anyio==3.7.1 has no usable wheels and building from source is disabled, we can conclude that anyio<4.0.0 cannot be used.
And because anyio==4.0.0 has no usable wheels and building from source is disabled and anyio==4.1.0 has no usable wheels and building from source is disabled, we can conclude that anyio<4.2.0 cannot be used.
And because anyio==4.2.0 has no usable wheels and building from source is disabled and anyio==4.3.0 has no usable wheels and building from source is disabled, we can conclude that anyio<4.4.0 cannot be used.
And because anyio==4.4.0 has no usable wheels and building from source is disabled and you require anyio, we can conclude that your requirements are unsatisfiable.
hint: Pre-releases are available for anyio in the requested range (e.g., 4.0.0rc1), but pre-releases weren't enabled (try: `--prerelease=allow`) hint: Pre-releases are available for anyio in the requested range (e.g., 4.0.0rc1), but pre-releases weren't enabled (try: `--prerelease=allow`)
"### "###