From d7abe827d6bcd528e043c3c281fc73b2af8e0c9a Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 16 Aug 2024 09:25:26 -0500 Subject: [PATCH] Allow displaying the derivation tree (#6124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I need this for debugging error messages. I used an environment variable instead of a trace log so you can do `UV_INTERNAL__SHOW_DERIVATION_TREE=1` and run a test to see the tree in the test snapshot without further changes. e.g. ```rust // Resolving should fail. uv_snapshot!(context.filters(), context.lock().arg("--preview").current_dir(&workspace), @r###" success: false exit_code: 1 ----- stdout ----- UV_INTERNAL__SHOW_DERIVATION_TREE root==0a0.dev0 depends on foo* root==0a0.dev0 depends on bar[some-extra]* foo==0.1.0 depends on anyio==4.1.0 bar[some-extra]==0.1.0 depends on anyio==4.2.0 no versions of bar[some-extra]<0.1.0 | >0.1.0 ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: ╰─▶ Because only bar[some-extra]==0.1.0 is available and bar[some-extra] depends on anyio==4.2.0, we can conclude that all versions of bar[some-extra] depend on anyio==4.2.0. And because foo depends on anyio==4.1.0, we can conclude that foo and all versions of bar[some-extra] are incompatible. And because your workspace requires bar[some-extra] and foo, we can conclude that your workspace's requirements are unsatisfiable. "### ); ``` --- crates/uv-resolver/src/error.rs | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index e93d170b3..3f1f8c825 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -8,6 +8,7 @@ use rustc_hash::FxHashMap; use distribution_types::{BuiltDist, IndexLocations, InstalledDist, SourceDist}; use pep440_rs::Version; use pep508_rs::MarkerTree; +use tracing::trace; use uv_normalize::PackageName; use crate::candidate_selector::CandidateSelector; @@ -228,6 +229,13 @@ 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); + } + let report = DefaultStringReporter::report_with_formatter(&tree, &formatter); write!(f, "{report}")?; @@ -248,6 +256,56 @@ impl std::fmt::Display for NoSolutionError { } } +#[allow(clippy::print_stderr)] +fn display_tree(error: &DerivationTree, UnavailableReason>) { + 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")); + } else { + trace!("Resolver error derivation tree\n{}", lines.join("\n")); + } +} + +fn display_tree_inner( + error: &DerivationTree, UnavailableReason>, + lines: &mut Vec, + depth: usize, +) { + match error { + DerivationTree::Derived(derived) => { + display_tree_inner(&derived.cause1, lines, depth + 1); + display_tree_inner(&derived.cause2, lines, depth + 1); + } + DerivationTree::External(external) => { + let prefix = " ".repeat(depth).to_string(); + match external { + External::FromDependencyOf(package, version, dependency, dependency_version) => { + lines.push(format!( + "{prefix}{package}{version} depends on {dependency}{dependency_version}" + )); + } + External::Custom(package, versions, reason) => match reason { + UnavailableReason::Package(_) => { + lines.push(format!("{prefix}{package} {reason}")); + } + UnavailableReason::Version(_) => { + lines.push(format!("{prefix}{package}{versions} {reason}")); + } + }, + External::NoVersions(package, versions) => { + lines.push(format!("{prefix}no versions of {package}{versions}")); + } + External::NotRoot(package, versions) => { + lines.push(format!("{prefix}not root {package}{versions}")); + } + } + } + } +} + /// Given a [`DerivationTree`], collapse any `NoVersion` incompatibilities for workspace members /// to avoid saying things like "only ==0.1.0 is available". fn collapse_no_versions_of_workspace_members(