use std::path::Path;
use anyhow::{Context, Result};
use cargo_metadata::MetadataCommand;
use super::{Edge, EdgeKind, Graph};
pub fn extract(repo_root: &Path) -> Result<Graph> {
let meta = MetadataCommand::new()
.current_dir(repo_root)
.no_deps()
.exec()
.context("cargo_metadata::MetadataCommand::exec")?;
let mut g = Graph::default();
for p in &meta.packages {
g.nodes.push(p.name.to_string());
for d in &p.dependencies {
g.edges.push(Edge {
from: p.name.to_string(),
to: d.name.clone(),
kind: EdgeKind::DependsOn,
});
}
}
Ok(g)
}
pub fn extract_with_path_deps(repo_root: &Path, scan_root: &Path) -> Result<Graph> {
let (cloned, linked) =
crate::security::prepare_path_deps(repo_root, scan_root, &|_| None);
if cloned + linked > 0 {
eprintln!(
"nornir-docs: prepared path-deps for {} ({cloned} cloned, {linked} linked)",
repo_root.display()
);
}
extract(repo_root).map_err(|e| {
let unresolved = crate::security::unresolved_path_dep_siblings(repo_root, scan_root);
if unresolved.is_empty() {
return e;
}
let names: Vec<String> = unresolved
.iter()
.map(|(dep, path)| format!("{dep} ({path})"))
.collect();
anyhow::anyhow!(
"docs render for {repo}: unresolved path-dep sibling(s) {sibs} — \
their Cargo.toml is not on disk beside the repo. Check the sibling \
repo(s) out under {root}, or build nornir-server with the `net-scan` \
feature so it clones them automatically (set NORNIR_DEP_CLONE_BASE \
for a non-default git host).",
repo = repo_root.display(),
sibs = names.join(", "),
root = scan_root.display(),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_with_path_deps_resolves_sibling_under_scan_root() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let skade = root.join("skade");
std::fs::create_dir_all(&skade).unwrap();
std::fs::write(
skade.join("Cargo.toml"),
"[package]\nname = \"skade\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.unwrap();
std::fs::create_dir_all(skade.join("src")).unwrap();
std::fs::write(skade.join("src/lib.rs"), "").unwrap();
let nornir = root.join("nornir");
std::fs::create_dir_all(nornir.join("src")).unwrap();
std::fs::write(
nornir.join("Cargo.toml"),
"[package]\nname = \"nornir\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n\
[dependencies]\nskade = { path = \"vendor/skade\" }\n",
)
.unwrap();
std::fs::write(nornir.join("src/lib.rs"), "").unwrap();
assert!(!nornir.join("vendor/skade/Cargo.toml").exists());
let g = extract_with_path_deps(&nornir, root).expect("metadata resolves after prep");
assert!(nornir.join("vendor/skade/Cargo.toml").exists(), "sibling linked in");
assert!(g.nodes.iter().any(|n| n == "nornir"), "graph names the repo: {:?}", g.nodes);
assert!(
g.edges.iter().any(|e| e.from == "nornir" && e.to == "skade"),
"graph has the nornir→skade path-dep edge: {:?}",
g.edges
);
}
#[cfg(not(feature = "net-scan"))]
#[test]
fn extract_with_path_deps_surfaces_clear_error_for_unresolved_sibling() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let nornir = root.join("nornir");
std::fs::create_dir_all(nornir.join("src")).unwrap();
std::fs::write(
nornir.join("Cargo.toml"),
"[workspace]\nmembers = [\".\"]\n\n\
[package]\nname = \"nornir\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n\
[dependencies]\nskade = { version = \"0.4.9\", path = \"../skade/skade\" }\n",
)
.unwrap();
std::fs::write(nornir.join("src/lib.rs"), "").unwrap();
let err = extract_with_path_deps(&nornir, root)
.expect_err("must fail when the sibling is unresolvable");
let msg = format!("{err:#}");
assert!(msg.contains("unresolved path-dep sibling"), "DR2 wording: {msg}");
assert!(msg.contains("skade"), "DR2 must name the unresolved dep: {msg}");
assert!(
!msg.contains("MetadataCommand::exec"),
"DR2 must NOT surface the opaque cargo-metadata error: {msg}"
);
}
}