91 lines
2.6 KiB
Rust
91 lines
2.6 KiB
Rust
use notgraph_lib::{analysis, crate_graph, emit, module_graph, symbols};
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "notgraph", about = "Workspace module graph and symbol analysis")]
|
|
struct Cli {
|
|
#[arg(long, default_value = "docs/graph")]
|
|
output: PathBuf,
|
|
|
|
#[arg(long)]
|
|
fail_on_cycles: bool,
|
|
|
|
#[arg(long, default_value_t = 10)]
|
|
top: usize,
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
let manifest = find_workspace_manifest()?;
|
|
let workspace_root = manifest.parent().unwrap().to_path_buf();
|
|
|
|
let crate_g = crate_graph::build(&manifest)
|
|
.context("failed to build crate graph")?;
|
|
|
|
let meta = cargo_metadata::MetadataCommand::new()
|
|
.manifest_path(&manifest)
|
|
.no_deps()
|
|
.exec()?;
|
|
|
|
let mut module_graphs = Vec::new();
|
|
let mut symbol_tables = Vec::new();
|
|
|
|
for pkg in &meta.packages {
|
|
if !meta.workspace_members.contains(&pkg.id) {
|
|
continue;
|
|
}
|
|
let src_dir = PathBuf::from(pkg.manifest_path.as_str())
|
|
.parent().unwrap().join("src");
|
|
if !src_dir.exists() {
|
|
continue;
|
|
}
|
|
module_graphs.push(
|
|
module_graph::build(pkg.name.clone(), &src_dir)
|
|
.with_context(|| format!("module_graph failed for {}", pkg.name))?
|
|
);
|
|
symbol_tables.push(
|
|
symbols::build(pkg.name.clone(), &src_dir)
|
|
.with_context(|| format!("symbols failed for {}", pkg.name))?
|
|
);
|
|
}
|
|
|
|
let stats = analysis::analyse(&crate_g, &module_graphs, cli.top);
|
|
|
|
let output_dir = if cli.output.is_absolute() {
|
|
cli.output.clone()
|
|
} else {
|
|
workspace_root.join(&cli.output)
|
|
};
|
|
|
|
emit::write_all(&output_dir, &stats, &symbol_tables)
|
|
.context("failed to write reports")?;
|
|
|
|
println!("notgraph: reports written to {}", output_dir.display());
|
|
|
|
if cli.fail_on_cycles && !stats.cycles.is_empty() {
|
|
eprintln!("notgraph: {} cycle(s) detected", stats.cycles.len());
|
|
for cycle in &stats.cycles {
|
|
eprintln!(" {}", cycle.join(" -> "));
|
|
}
|
|
std::process::exit(1);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn find_workspace_manifest() -> Result<PathBuf> {
|
|
let mut dir = std::env::current_dir()?;
|
|
loop {
|
|
let candidate = dir.join("Cargo.toml");
|
|
if candidate.exists() {
|
|
let content = std::fs::read_to_string(&candidate)?;
|
|
if content.contains("[workspace]") {
|
|
return Ok(candidate);
|
|
}
|
|
}
|
|
anyhow::ensure!(dir.pop(), "could not find workspace Cargo.toml");
|
|
}
|
|
}
|