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 759f332
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 95 deletions.
124 changes: 116 additions & 8 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,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.
///
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 759f332

Please sign in to comment.