renso-code-graph 1.0.6

Dependency graph analyzer for code, tests, docs, and policy surfaces. Installs the prebuilt `code_graph` binary from GitHub Releases.
//! Trampoline for the `code_graph` stub crate.
//!
//! The real `code_graph` binary is embedded at compile time by
//! `build.rs`. On first invocation we extract it to a per-user cache
//! directory, set the executable bit (Unix), and exec it forwarding
//! argv + env. Subsequent invocations re-use the cached file.

#![forbid(unsafe_code)]

use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::process;

const EMBEDDED: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/embedded_binary"));
const PLACEHOLDER_PREAMBLE: &[u8] = b"CG_STUB_PLACEHOLDER\n";

const BIN_NAME: &str = "code_graph";

fn main() {
    if EMBEDDED.starts_with(PLACEHOLDER_PREAMBLE) {
        eprintln!(
            "error: this `{BIN_NAME}` build is a development placeholder.\n\
             \n\
             Install a real release via one of:\n  \
               cargo install renso-code-graph renso-code-graph-mcp\n  \
               curl -fsSL https://cg.renso.ai/install.sh | sh\n  \
               brew tap renso-ai/code-graph https://github.com/renso-ai/code-graph-homebrew && brew install renso-ai/code-graph/code-graph\n  \
               npm install -g @rensoai/code-graph\n  \
               pipx install rensoai-code-graph"
        );
        process::exit(126);
    }

    let cache_dir = resolve_cache_dir().unwrap_or_else(|err| {
        eprintln!("error: cannot resolve cache directory: {err}");
        process::exit(127);
    });
    let bin_path = cache_dir.join(bin_file_name());

    if !bin_path.exists() {
        if let Err(err) = extract(&cache_dir, &bin_path) {
            eprintln!(
                "error: failed to extract {BIN_NAME} to {}: {err}",
                bin_path.display()
            );
            process::exit(127);
        }
    }

    let args: Vec<OsString> = env::args_os().skip(1).collect();
    exec(&bin_path, &args);
}

fn bin_file_name() -> &'static str {
    if cfg!(windows) {
        "code_graph.exe"
    } else {
        "code_graph"
    }
}

fn resolve_cache_dir() -> io::Result<PathBuf> {
    let version = env!("CARGO_PKG_VERSION");

    #[cfg(windows)]
    let base = env::var_os("LOCALAPPDATA")
        .map(PathBuf::from)
        .or_else(|| {
            env::var_os("USERPROFILE").map(|p| PathBuf::from(p).join("AppData").join("Local"))
        })
        .ok_or_else(|| io::Error::other("LOCALAPPDATA and USERPROFILE both unset"))?;

    #[cfg(not(windows))]
    let base = env::var_os("XDG_CACHE_HOME")
        .map(PathBuf::from)
        .or_else(|| env::var_os("HOME").map(|p| PathBuf::from(p).join(".cache")))
        .ok_or_else(|| io::Error::other("XDG_CACHE_HOME and HOME both unset"))?;

    Ok(base.join("code-graph").join(version))
}

fn extract(cache_dir: &Path, bin_path: &Path) -> io::Result<()> {
    fs::create_dir_all(cache_dir)?;
    // Write atomically: write to a temp file in the same directory, then rename.
    // Two concurrent invocations could both reach this branch; the rename
    // is what serializes them.
    let tmp_path = bin_path.with_extension("partial");
    fs::write(&tmp_path, EMBEDDED)?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&tmp_path)?.permissions();
        perms.set_mode(0o755);
        fs::set_permissions(&tmp_path, perms)?;
    }

    fs::rename(&tmp_path, bin_path)?;
    Ok(())
}

#[cfg(unix)]
fn exec(bin_path: &Path, args: &[OsString]) -> ! {
    use std::os::unix::process::CommandExt;
    let err = process::Command::new(bin_path).args(args).exec();
    eprintln!("error: exec {} failed: {err}", bin_path.display());
    process::exit(127);
}

#[cfg(windows)]
fn exec(bin_path: &Path, args: &[OsString]) -> ! {
    let status = match process::Command::new(bin_path).args(args).status() {
        Ok(s) => s,
        Err(err) => {
            eprintln!("error: spawn {} failed: {err}", bin_path.display());
            process::exit(127);
        }
    };
    process::exit(status.code().unwrap_or(127));
}