mirror of https://github.com/astral-sh/uv
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"]
}
```

transformers:

jupyter:

This commit is contained in:
parent
d39e9b3499
commit
bf71e7adcf
|
|
@ -2458,6 +2458,7 @@ dependencies = [
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"pep508_rs",
|
"pep508_rs",
|
||||||
|
"petgraph",
|
||||||
"platform-host",
|
"platform-host",
|
||||||
"platform-tags",
|
"platform-tags",
|
||||||
"puffin-build",
|
"puffin-build",
|
||||||
|
|
@ -2465,6 +2466,7 @@ dependencies = [
|
||||||
"puffin-client",
|
"puffin-client",
|
||||||
"puffin-dispatch",
|
"puffin-dispatch",
|
||||||
"puffin-interpreter",
|
"puffin-interpreter",
|
||||||
|
"puffin-resolver",
|
||||||
"puffin-traits",
|
"puffin-traits",
|
||||||
"pypi-types",
|
"pypi-types",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ puffin-cache = { path = "../puffin-cache", features = ["clap"] }
|
||||||
puffin-client = { path = "../puffin-client" }
|
puffin-client = { path = "../puffin-client" }
|
||||||
puffin-dispatch = { path = "../puffin-dispatch" }
|
puffin-dispatch = { path = "../puffin-dispatch" }
|
||||||
puffin-interpreter = { path = "../puffin-interpreter" }
|
puffin-interpreter = { path = "../puffin-interpreter" }
|
||||||
|
puffin-resolver = { path = "../puffin-resolver" }
|
||||||
pypi-types = { path = "../pypi-types" }
|
pypi-types = { path = "../pypi-types" }
|
||||||
puffin-traits = { path = "../puffin-traits" }
|
puffin-traits = { path = "../puffin-traits" }
|
||||||
|
|
||||||
|
|
@ -32,6 +33,7 @@ fs-err = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
indicatif = { workspace = true }
|
indicatif = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
petgraph = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io::{BufWriter, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anstream::println;
|
use anstream::println;
|
||||||
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use fs_err::File;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use petgraph::dot::{Config as DotConfig, Dot};
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use platform_host::Platform;
|
use platform_host::Platform;
|
||||||
|
use platform_tags::Tags;
|
||||||
use puffin_cache::{CacheArgs, CacheDir};
|
use puffin_cache::{CacheArgs, CacheDir};
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::RegistryClientBuilder;
|
||||||
use puffin_dispatch::BuildDispatch;
|
use puffin_dispatch::BuildDispatch;
|
||||||
use puffin_interpreter::Virtualenv;
|
use puffin_interpreter::Virtualenv;
|
||||||
use puffin_traits::BuildContext;
|
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, Resolver};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub(crate) struct ResolveCliArgs {
|
pub(crate) struct ResolveCliArgs {
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
|
/// Write debug output in DOT format for graphviz to this file
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
limit: Option<usize>,
|
graphviz: Option<PathBuf>,
|
||||||
/// Don't build source distributions. This means resolving will not run arbitrary code. The
|
/// Don't build source distributions. This means resolving will not run arbitrary code. The
|
||||||
/// cached wheels of already built source distributions will be reused.
|
/// cached wheels of already built source distributions will be reused.
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
|
|
@ -30,15 +37,61 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> anyhow::Result<()> {
|
||||||
|
|
||||||
let platform = Platform::current()?;
|
let platform = Platform::current()?;
|
||||||
let venv = Virtualenv::from_env(platform, Some(cache_dir.path()))?;
|
let venv = Virtualenv::from_env(platform, Some(cache_dir.path()))?;
|
||||||
|
let client = RegistryClientBuilder::new(cache_dir.path().clone()).build();
|
||||||
let build_dispatch = BuildDispatch::new(
|
let build_dispatch = BuildDispatch::new(
|
||||||
RegistryClientBuilder::new(cache_dir.path().clone()).build(),
|
client.clone(),
|
||||||
cache_dir.path().clone(),
|
cache_dir.path().clone(),
|
||||||
venv.interpreter_info().clone(),
|
venv.interpreter_info().clone(),
|
||||||
fs::canonicalize(venv.python_executable())?,
|
fs::canonicalize(venv.python_executable())?,
|
||||||
args.no_build,
|
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));
|
resolution.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
// Concise format for dev
|
// Concise format for dev
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::hash::BuildHasherDefault;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use petgraph::visit::EdgeRef;
|
use petgraph::visit::EdgeRef;
|
||||||
|
use petgraph::Direction;
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
use pubgrub::solver::{Kind, State};
|
use pubgrub::solver::{Kind, State};
|
||||||
use pubgrub::type_aliases::SelectedDependencies;
|
use pubgrub::type_aliases::SelectedDependencies;
|
||||||
|
|
@ -51,7 +52,7 @@ impl Resolution {
|
||||||
/// A complete resolution graph in which every node represents a pinned package and every edge
|
/// A complete resolution graph in which every node represents a pinned package and every edge
|
||||||
/// represents a dependency between two pinned packages.
|
/// represents a dependency between two pinned packages.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Graph(petgraph::graph::Graph<Dist, (), petgraph::Directed>);
|
pub struct Graph(petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed>);
|
||||||
|
|
||||||
impl Graph {
|
impl Graph {
|
||||||
/// Create a new graph from the resolved `PubGrub` state.
|
/// Create a new graph from the resolved `PubGrub` state.
|
||||||
|
|
@ -98,8 +99,12 @@ impl Graph {
|
||||||
// Add every edge to the graph.
|
// Add every edge to the graph.
|
||||||
for (package, version) in selection {
|
for (package, version) in selection {
|
||||||
for id in &state.incompatibilities[package] {
|
for id in &state.incompatibilities[package] {
|
||||||
if let Kind::FromDependencyOf(self_package, self_version, dependency_package, _) =
|
if let Kind::FromDependencyOf(
|
||||||
&state.incompatibility_store[*id].kind
|
self_package,
|
||||||
|
self_version,
|
||||||
|
dependency_package,
|
||||||
|
dependency_range,
|
||||||
|
) = &state.incompatibility_store[*id].kind
|
||||||
{
|
{
|
||||||
let PubGrubPackage::Package(self_package, None, _) = self_package else {
|
let PubGrubPackage::Package(self_package, None, _) = self_package else {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -112,7 +117,7 @@ impl Graph {
|
||||||
if self_version.contains(version) {
|
if self_version.contains(version) {
|
||||||
let self_index = &inverse[self_package];
|
let self_index = &inverse[self_package];
|
||||||
let dependency_index = &inverse[dependency_package];
|
let dependency_index = &inverse[dependency_package];
|
||||||
graph.update_edge(*dependency_index, *self_index, ());
|
graph.update_edge(*self_index, *dependency_index, dependency_range.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +184,13 @@ impl Graph {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the underlying graph.
|
||||||
|
pub fn petgraph(
|
||||||
|
&self,
|
||||||
|
) -> &petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
||||||
|
|
@ -198,8 +210,8 @@ impl std::fmt::Display for Graph {
|
||||||
|
|
||||||
let mut edges = self
|
let mut edges = self
|
||||||
.0
|
.0
|
||||||
.edges(index)
|
.edges_directed(index, Direction::Incoming)
|
||||||
.map(|edge| &self.0[edge.target()])
|
.map(|edge| &self.0[edge.source()])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
edges.sort_unstable_by_key(|package| package.name());
|
edges.sort_unstable_by_key(|package| package.name());
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue