mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 18:38:21 +00:00
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
4 changed files with 79 additions and 10 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2458,6 +2458,7 @@ dependencies = [
|
|||
"itertools 0.11.0",
|
||||
"mimalloc",
|
||||
"pep508_rs",
|
||||
"petgraph",
|
||||
"platform-host",
|
||||
"platform-tags",
|
||||
"puffin-build",
|
||||
|
@ -2465,6 +2466,7 @@ dependencies = [
|
|||
"puffin-client",
|
||||
"puffin-dispatch",
|
||||
"puffin-interpreter",
|
||||
"puffin-resolver",
|
||||
"puffin-traits",
|
||||
"pypi-types",
|
||||
"tempfile",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::hash::BuildHasherDefault;
|
|||
use colored::Colorize;
|
||||
use fxhash::FxHashMap;
|
||||
use petgraph::visit::EdgeRef;
|
||||
use petgraph::Direction;
|
||||
use pubgrub::range::Range;
|
||||
use pubgrub::solver::{Kind, State};
|
||||
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
|
||||
/// represents a dependency between two pinned packages.
|
||||
#[derive(Debug)]
|
||||
pub struct Graph(petgraph::graph::Graph<Dist, (), petgraph::Directed>);
|
||||
pub struct Graph(petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed>);
|
||||
|
||||
impl Graph {
|
||||
/// Create a new graph from the resolved `PubGrub` state.
|
||||
|
@ -98,8 +99,12 @@ impl Graph {
|
|||
// Add every edge to the graph.
|
||||
for (package, version) in selection {
|
||||
for id in &state.incompatibilities[package] {
|
||||
if let Kind::FromDependencyOf(self_package, self_version, dependency_package, _) =
|
||||
&state.incompatibility_store[*id].kind
|
||||
if let Kind::FromDependencyOf(
|
||||
self_package,
|
||||
self_version,
|
||||
dependency_package,
|
||||
dependency_range,
|
||||
) = &state.incompatibility_store[*id].kind
|
||||
{
|
||||
let PubGrubPackage::Package(self_package, None, _) = self_package else {
|
||||
continue;
|
||||
|
@ -112,7 +117,7 @@ impl Graph {
|
|||
if self_version.contains(version) {
|
||||
let self_index = &inverse[self_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()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -198,8 +210,8 @@ impl std::fmt::Display for Graph {
|
|||
|
||||
let mut edges = self
|
||||
.0
|
||||
.edges(index)
|
||||
.map(|edge| &self.0[edge.target()])
|
||||
.edges_directed(index, Direction::Incoming)
|
||||
.map(|edge| &self.0[edge.source()])
|
||||
.collect::<Vec<_>>();
|
||||
edges.sort_unstable_by_key(|package| package.name());
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue