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
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);
if self.workspace_members.len() == 1 {
@ -229,11 +236,10 @@ impl std::fmt::Display for NoSolutionError {
drop_root_dependency_on_project(&mut tree, project);
}
// Display the tree if enabled
if std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some()
|| tracing::enabled!(tracing::Level::TRACE)
{
display_tree(&tree);
collapse_unavailable_versions(&mut tree);
if should_display_tree {
display_tree(&tree, "Resolver derivation tree after reduction");
}
let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
@ -257,15 +263,18 @@ impl std::fmt::Display for NoSolutionError {
}
#[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();
display_tree_inner(error, &mut lines, 0);
lines.reverse();
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 {
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
/// to the project.
///

View file

@ -241,10 +241,15 @@ fn prune_unzipped() -> Result<()> {
Because only the following versions of iniconfig are available:
iniconfig<=0.1
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 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.
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.
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.
and any of:
iniconfig==0.1
iniconfig==1.0.0
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`)

View file

@ -1925,95 +1925,51 @@ fn install_only_binary_all_and_no_binary_all() {
anyio>=3.0.0,<=3.6.2
anyio>=3.7.0,<=3.7.1
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.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.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:
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.
And because 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`)
"###