use std::path::{Path, PathBuf};
use std::process::Command;
use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub(crate) enum Target<'a> {
Lib,
Bin(&'a str),
}
pub(crate) fn build_and_parse(
target: &Target<'_>,
crate_name: &str,
manifest_path: &Path,
workspace_root: &Path,
) -> Result<rustdoc_types::Crate> {
let mut cmd = Command::new("cargo");
cmd.arg("+nightly").arg("rustdoc");
match target {
Target::Lib => {
cmd.arg("--lib");
}
Target::Bin(name) => {
cmd.arg("--bin").arg(name);
}
}
cmd.arg("--manifest-path").arg(manifest_path);
cmd.args(["--", "-Zunstable-options", "--output-format", "json"]);
cmd.current_dir(workspace_root);
let output = cmd.output().map_err(|e| Error::RustdocInvocation {
message: format!("failed to spawn `cargo +nightly rustdoc`: {e}"),
})?;
if !output.status.success() {
return Err(Error::RustdocInvocation {
message: format!(
"`cargo +nightly rustdoc` exited with {status} for crate `{crate_name}`\nstderr:\n{stderr}",
status = output.status,
stderr = String::from_utf8_lossy(&output.stderr).trim(),
),
});
}
let json_name = match target {
Target::Lib => crate_name.replace('-', "_"),
Target::Bin(name) => name.replace('-', "_"),
};
let json_path = workspace_root
.join("target")
.join("doc")
.join(format!("{json_name}.json"));
if !json_path.exists() {
return Err(Error::RustdocInvocation {
message: format!(
"`cargo +nightly rustdoc` reported success but the JSON payload was not found at {}",
json_path.display()
),
});
}
let bytes = std::fs::read(&json_path)?;
let payload: rustdoc_types::Crate = serde_json::from_slice(&bytes)?;
if payload.format_version != rustdoc_types::FORMAT_VERSION {
return Err(Error::FormatVersionMismatch {
expected: rustdoc_types::FORMAT_VERSION,
found: payload.format_version,
});
}
Ok(payload)
}
#[allow(dead_code)]
pub(crate) fn json_path(workspace_root: &Path, crate_name: &str) -> PathBuf {
workspace_root
.join("target")
.join("doc")
.join(format!("{}.json", crate_name.replace('-', "_")))
}