Allow displaying the derivation tree (#6124)

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.
    "###
    );
```
This commit is contained in:
Zanie Blue 2024-08-16 09:25:26 -05:00 committed by GitHub
parent f2d6718038
commit d7abe827d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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<PubGrubPackage, Range<Version>, 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<PubGrubPackage, Range<Version>, UnavailableReason>,
lines: &mut Vec<String>,
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 <workspace-member>==0.1.0 is available".
fn collapse_no_versions_of_workspace_members(