Skip to content

Commit

Permalink
Collapse unavailable packages in resolver errors
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Aug 16, 2024
1 parent d7abe82 commit f2605de
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 98 deletions.
132 changes: 121 additions & 11 deletions crates/uv-resolver/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,18 +222,24 @@ 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 {
let project = self.workspace_members.iter().next().unwrap();
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);
Expand All @@ -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"));
}
}

Expand Down Expand Up @@ -355,6 +364,110 @@ 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;
};

// Then, if the other tree has an unavailable package...
match (&**cause1, &**cause2) {
// Note these cases are the same, but we need two matches to retain the
// ordering of the causes
(
other_cause,
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,
// TODO(zanieb): Avoid cloning inside the arc here
cause1: Arc::new(other_cause.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,
)),
other_cause,
) => {
// 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(),
))),
// TODO(zanieb): Avoid cloning inside the arc here
cause2: Arc::new(other_cause.clone()),
});
}
}
_ => {}
}

// Replace this node with the other tree
// *tree = other.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.
///
Expand Down Expand Up @@ -395,9 +508,6 @@ fn drop_root_dependency_on_project(
return;
}

// Recursively collapse the other side of the tree
drop_root_dependency_on_project(other, project);

// Then, replace this node with the other tree
*tree = other.clone();
}
Expand Down
13 changes: 9 additions & 4 deletions crates/uv/tests/cache_prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
122 changes: 39 additions & 83 deletions crates/uv/tests/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
"###
Expand Down

0 comments on commit f2605de

Please sign in to comment.