Skip to main content

pharos_runtime_docs/
rustdoc.rs

1//! Cargo arguments for the shipping runtime's API documentation.
2
3use std::ffi::OsString;
4use std::path::{Path, PathBuf};
5
6/// Selects the shipping runtime surface without enabling optional media engines or host bridges.
7pub fn cargo_arguments(manifest: &Path, target_dir: &Path) -> Vec<OsString> {
8    [
9        OsString::from("doc"),
10        OsString::from("--manifest-path"),
11        manifest.as_os_str().to_owned(),
12        OsString::from("--workspace"),
13        OsString::from("--no-default-features"),
14        OsString::from("--features"),
15        OsString::from("pharos_native/full-release,pharos_runtime_ops_cli/full-release"),
16        OsString::from("--locked"),
17        OsString::from("--offline"),
18        OsString::from("--no-deps"),
19        OsString::from("--document-private-items"),
20        OsString::from("--lib"),
21        OsString::from("--bins"),
22        OsString::from("--target-dir"),
23        target_dir.as_os_str().to_owned(),
24    ]
25    .into()
26}
27
28/// Builds the candidate Cargo executable list for Rustdoc generation.
29pub fn docs_cargo_executable_candidates() -> Vec<std::ffi::OsString> {
30    let mut candidates = Vec::new();
31    for var_name in ["CARGO", "CARGO_BIN"] {
32        if let Some(cargo) = std::env::var_os(var_name).filter(|value| !value.is_empty()) {
33            docs_push_cargo_candidate(&mut candidates, PathBuf::from(cargo));
34        }
35    }
36    candidates.push(std::ffi::OsString::from("cargo"));
37    if let Some(home) = std::env::var_os("HOME").filter(|value| !value.is_empty()) {
38        docs_push_cargo_candidate(
39            &mut candidates,
40            PathBuf::from(home).join(".cargo/bin/cargo"),
41        );
42    }
43    for cargo_path in [
44        "/opt/local/bin/cargo",
45        "/usr/local/bin/cargo",
46        "/opt/homebrew/bin/cargo",
47    ] {
48        docs_push_cargo_candidate(&mut candidates, PathBuf::from(cargo_path));
49    }
50    let mut deduped = Vec::new();
51    for candidate in candidates {
52        if !deduped.iter().any(|seen| seen == &candidate) {
53            deduped.push(candidate);
54        }
55    }
56    deduped
57}
58
59/// Adds a cargo candidate plus its canonical target when available.
60fn docs_push_cargo_candidate(candidates: &mut Vec<std::ffi::OsString>, cargo: PathBuf) {
61    if !cargo.is_file() {
62        return;
63    }
64    candidates.push(cargo.clone().into_os_string());
65    if let Ok(canonical) = cargo.canonicalize()
66        && canonical.is_file()
67        && canonical != cargo
68    {
69        candidates.push(canonical.into_os_string());
70    }
71}
72
73/// Builds the process PATH used by Cargo when Rustdoc generation runs from a sparse environment.
74fn docs_rustdoc_toolchain_path(cargo: &std::ffi::OsStr) -> std::ffi::OsString {
75    let mut paths = Vec::new();
76    let cargo_path = PathBuf::from(cargo);
77    if let Some(parent) = cargo_path.parent().filter(|path| path.is_dir()) {
78        paths.push(parent.to_path_buf());
79    }
80    for path in ["/opt/local/bin", "/usr/local/bin", "/opt/homebrew/bin"] {
81        let candidate = PathBuf::from(path);
82        if candidate.is_dir() {
83            paths.push(candidate);
84        }
85    }
86    if let Some(existing) = std::env::var_os("PATH") {
87        paths.extend(std::env::split_paths(&existing));
88    }
89    let mut deduped = Vec::new();
90    for path in paths {
91        if !deduped.iter().any(|seen| seen == &path) {
92            deduped.push(path);
93        }
94    }
95    std::env::join_paths(deduped).unwrap_or_else(|_| {
96        std::ffi::OsString::from("/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin")
97    })
98}
99
100/// Starts Cargo rustdoc generation with fallback executable paths and direct arguments.
101pub fn docs_run_cargo_doc(repo_root: &Path) -> Result<std::process::Output, String> {
102    let manifest_path = repo_root.join("Cargo.toml");
103    let target_dir = repo_root.join("target/rustdoc");
104    let mut launch_errors = Vec::new();
105    for cargo in docs_cargo_executable_candidates() {
106        let toolchain_path = docs_rustdoc_toolchain_path(&cargo);
107        match std::process::Command::new(&cargo)
108            .args(cargo_arguments(&manifest_path, &target_dir))
109            .env("PATH", toolchain_path)
110            .current_dir(repo_root)
111            .output()
112        {
113            Ok(output) => return Ok(output),
114            Err(error) => {
115                launch_errors.push(format!("{}: {error}", cargo.to_string_lossy()));
116            }
117        }
118    }
119    Err(launch_errors.join("; "))
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    /// Guards the feature boundary and preserves executable and private API coverage.
127    #[test]
128    fn documentation_uses_explicit_runtime_features() {
129        let args = cargo_arguments(Path::new("Cargo.toml"), Path::new("target/rustdoc"));
130        assert!(!args.contains(&OsString::from("--all-features")));
131        assert!(args.contains(&OsString::from("--locked")));
132        assert!(args.contains(&OsString::from("--offline")));
133        assert!(args.contains(&OsString::from("--document-private-items")));
134        assert!(args.contains(&OsString::from("--bins")));
135        assert!(args.contains(&OsString::from(
136            "pharos_native/full-release,pharos_runtime_ops_cli/full-release"
137        )));
138    }
139}