diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 3f1f8c825192..c7e7a5e58f40 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -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, UnavailableReason>) { +fn display_tree( + error: &DerivationTree, 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, 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. /// diff --git a/crates/uv/tests/cache_prune.rs b/crates/uv/tests/cache_prune.rs index 7e2ecf5b2f8b..c53cfde87b23 100644 --- a/crates/uv/tests/cache_prune.rs +++ b/crates/uv/tests/cache_prune.rs @@ -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`) diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index e8f74ec5f485..fbc3b6367625 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -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`) "###