Add graphviz output to puffin-dev resolve-cli (#443)

I added output in graphviz DOT format to `puffin-dev resolve-cli` to
help with debugging resolutions. This requires tracking the requested
ranges in the graph. I also fixed the direction of the graph.

 Output for `black`:

```dot
digraph {
    0 [ label="click\n8.1.7"]
    1 [ label="black\n23.11.0"]
    2 [ label="packaging\n23.2"]
    3 [ label="mypy-extensions\n1.0.0"]
    4 [ label="tomli\n2.0.1"]
    5 [ label="pathspec\n0.11.2"]
    6 [ label="typing-extensions\n4.8.0"]
    7 [ label="platformdirs\n4.0.0"]
    1 -> 0 [ label=">=8.0.0"]
    1 -> 3 [ label=">=0.4.3"]
    1 -> 5 [ label=">=0.9.0"]
    1 -> 4 [ label=">=1.1.0"]
    1 -> 6 [ label=">=4.0.1"]
    1 -> 2 [ label=">=22.0"]
    1 -> 7 [ label=">=2"]
}
```


![image](4a440fcd-6248-4349-8e1a-c3e0363e42b1)

transformers:


![image](a13a693c-a8c0-4a4f-95d9-3458431c678a)

jupyter:


![graphviz](ef730033-6fd9-4ea9-ac93-8c874c19a101)
This commit is contained in:
konsti 2023-11-17 19:16:24 +01:00 committed by GitHub
parent d39e9b3499
commit bf71e7adcf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 10 deletions

View file

@ -21,6 +21,7 @@ puffin-cache = { path = "../puffin-cache", features = ["clap"] }
puffin-client = { path = "../puffin-client" }
puffin-dispatch = { path = "../puffin-dispatch" }
puffin-interpreter = { path = "../puffin-interpreter" }
puffin-resolver = { path = "../puffin-resolver" }
pypi-types = { path = "../pypi-types" }
puffin-traits = { path = "../puffin-traits" }
@ -32,6 +33,7 @@ fs-err = { workspace = true }
futures = { workspace = true }
indicatif = { workspace = true }
itertools = { workspace = true }
petgraph = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

View file

@ -1,22 +1,29 @@
use std::fs;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use anstream::println;
use anyhow::Context;
use clap::Parser;
use fs_err::File;
use itertools::Itertools;
use petgraph::dot::{Config as DotConfig, Dot};
use pep508_rs::Requirement;
use platform_host::Platform;
use platform_tags::Tags;
use puffin_cache::{CacheArgs, CacheDir};
use puffin_client::RegistryClientBuilder;
use puffin_dispatch::BuildDispatch;
use puffin_interpreter::Virtualenv;
use puffin_traits::BuildContext;
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, Resolver};
#[derive(Parser)]
pub(crate) struct ResolveCliArgs {
requirements: Vec<Requirement>,
/// Write debug output in DOT format for graphviz to this file
#[clap(long)]
limit: Option<usize>,
graphviz: Option<PathBuf>,
/// Don't build source distributions. This means resolving will not run arbitrary code. The
/// cached wheels of already built source distributions will be reused.
#[clap(long)]
@ -30,15 +37,61 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> anyhow::Result<()> {
let platform = Platform::current()?;
let venv = Virtualenv::from_env(platform, Some(cache_dir.path()))?;
let client = RegistryClientBuilder::new(cache_dir.path().clone()).build();
let build_dispatch = BuildDispatch::new(
RegistryClientBuilder::new(cache_dir.path().clone()).build(),
client.clone(),
cache_dir.path().clone(),
venv.interpreter_info().clone(),
fs::canonicalize(venv.python_executable())?,
args.no_build,
);
let mut resolution = build_dispatch.resolve(&args.requirements).await?;
// Copied from `BuildDispatch`
let tags = Tags::from_env(
venv.interpreter_info().platform(),
venv.interpreter_info().simple_version(),
)?;
let resolver = Resolver::new(
// TODO(konstin): Split settings (for all resolutions) and inputs (only for this
// resolution) and attach the former to Self.
Manifest::new(
args.requirements.clone(),
Vec::default(),
Vec::default(),
ResolutionMode::default(),
PreReleaseMode::default(),
None, // TODO(zanieb): We may want to provide a project name here
None,
),
venv.interpreter_info().markers(),
&tags,
&client,
&build_dispatch,
);
let resolution_graph = resolver.resolve().await.with_context(|| {
format!(
"No solution found when resolving: {}",
args.requirements.iter().map(ToString::to_string).join(", "),
)
})?;
if let Some(graphviz) = args.graphviz {
let mut writer = BufWriter::new(File::create(graphviz)?);
let graphviz = Dot::with_attr_getters(
resolution_graph.petgraph(),
&[DotConfig::NodeNoLabel, DotConfig::EdgeNoLabel],
&|_graph, edge_ref| format!("label={:?}", edge_ref.weight().to_string()),
&|_graph, (_node_index, dist)| {
format!(
"label={:?}",
dist.to_string().replace("==", "\n").to_string()
)
},
);
write!(&mut writer, "{graphviz:?}")?;
}
let mut resolution = resolution_graph.requirements();
resolution.sort_unstable_by(|a, b| a.name.cmp(&b.name));
// Concise format for dev