use guppy::MetadataCommand;
use guppy::graph::PackageGraph;
use rustdoc_processor::CrateCollection;
use rustdoc_processor::cache::RustdocGlobalFsCache;
use rustdoc_processor::compute::NoProgress;
use std::path::PathBuf;
use crate::indexing::CheadergenIndexer;
pub const DOCS_TOOLCHAIN: &str = include_str!("../rust-docs-toolchain");
pub fn load_package_graph(
metadata_path: Option<&PathBuf>,
input: Option<&PathBuf>,
) -> anyhow::Result<PackageGraph> {
let raw_json = if let Some(metadata_path) = metadata_path {
fs_err::read_to_string(metadata_path)?
} else {
let mut cmd = MetadataCommand::new();
if let Some(input) = input {
cmd.current_dir(input);
}
let mut command = cmd.cargo_command();
let output = command.output().map_err(|e| {
anyhow::anyhow!("failed to invoke `cargo metadata`: {e}")
})?;
if !output.status.success() {
anyhow::bail!(
"`cargo metadata` failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
String::from_utf8(output.stdout)
.map_err(|e| anyhow::anyhow!("`cargo metadata` produced non-UTF8 output: {e}"))?
};
let patched = strip_self_dev_dep_edges(&raw_json)?;
let metadata = guppy::CargoMetadata::parse_json(&patched)?;
Ok(metadata.build_graph()?)
}
fn strip_self_dev_dep_edges(raw: &str) -> anyhow::Result<String> {
let mut value: serde_json::Value = serde_json::from_str(raw)?;
if let Some(nodes) = value
.get_mut("resolve")
.and_then(|r| r.get_mut("nodes"))
.and_then(|n| n.as_array_mut())
{
for node in nodes {
let Some(id) = node.get("id").and_then(|v| v.as_str()).map(str::to_owned) else {
continue;
};
if let Some(deps) = node.get_mut("deps").and_then(|v| v.as_array_mut()) {
deps.retain(|d| d.get("pkg").and_then(|v| v.as_str()) != Some(&id));
}
}
}
Ok(serde_json::to_string(&value)?)
}
pub fn cache_dir() -> anyhow::Result<PathBuf> {
Ok(xdg_home::home_dir()
.ok_or_else(|| anyhow::anyhow!("Failed to get the user's home directory"))?
.join(".cheadergen/cache"))
}
pub fn create_collection(package_graph: PackageGraph) -> anyhow::Result<crate::Collection> {
let toolchain =
std::env::var("CHEADERGEN_DOCS_TOOLCHAIN").unwrap_or_else(|_| DOCS_TOOLCHAIN.to_string());
let project_fingerprint = package_graph.workspace().root().to_string();
let cache_dir = cache_dir()?;
let disk_cache = RustdocGlobalFsCache::new(
rustdoc_processor::CRATE_VERSION,
&toolchain,
false,
&package_graph,
&cache_dir,
)?;
let collection = CrateCollection::new(
CheadergenIndexer,
toolchain,
package_graph,
project_fingerprint,
disk_cache,
Box::new(NoProgress),
);
collection
.bootstrap(std::iter::empty())
.expect("Failed to bootstrap the crate collection");
Ok(collection)
}
#[cfg(test)]
mod tests {
use super::*;
const RAW_METADATA: &str = r#"{
"packages": [
{
"name": "base",
"version": "0.1.0",
"id": "base 0.1.0 (path+file:///x/base)",
"dependencies": [],
"manifest_path": "/x/base/Cargo.toml"
}
],
"resolve": {
"nodes": [
{
"id": "base 0.1.0 (path+file:///x/base)",
"deps": [
{
"name": "base",
"pkg": "base 0.1.0 (path+file:///x/base)",
"dep_kinds": [{"kind": "dev", "target": null}]
},
{
"name": "helper",
"pkg": "helper 0.1.0 (path+file:///x/helper)",
"dep_kinds": [{"kind": null, "target": null}]
}
]
}
],
"root": null
}
}"#;
#[test]
fn strip_self_dev_dep_edges_removes_self_loop() {
let patched = strip_self_dev_dep_edges(RAW_METADATA).expect("valid JSON");
let value: serde_json::Value = serde_json::from_str(&patched).unwrap();
let deps = value["resolve"]["nodes"][0]["deps"].as_array().unwrap();
assert_eq!(deps.len(), 1, "self-edge should have been removed: {deps:?}");
assert_eq!(deps[0]["name"], "helper");
}
#[test]
fn strip_self_dev_dep_edges_preserves_non_self_edges() {
let raw = r#"{
"packages": [],
"resolve": {
"nodes": [
{
"id": "a 0.1.0 (path+file:///x/a)",
"deps": [
{"name": "b", "pkg": "b 0.1.0 (path+file:///x/b)", "dep_kinds": []}
]
},
{
"id": "b 0.1.0 (path+file:///x/b)",
"deps": [
{"name": "a", "pkg": "a 0.1.0 (path+file:///x/a)", "dep_kinds": []}
]
}
]
}
}"#;
let patched = strip_self_dev_dep_edges(raw).expect("valid JSON");
let value: serde_json::Value = serde_json::from_str(&patched).unwrap();
let nodes = value["resolve"]["nodes"].as_array().unwrap();
assert_eq!(nodes[0]["deps"].as_array().unwrap().len(), 1);
assert_eq!(nodes[1]["deps"].as_array().unwrap().len(), 1);
}
}