rns-cli 0.2.0

CLI tools for the Reticulum Network Stack
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
    println!("cargo:rerun-if-changed=../.git/HEAD");
    println!("cargo:rerun-if-changed=../.git/refs");
    println!("cargo:rerun-if-changed=../rns-stats-hook/src/lib.rs");
    println!("cargo:rerun-if-changed=../rns-stats-hook/Cargo.toml");

    let pkg_version = env!("CARGO_PKG_VERSION");
    let parts: Vec<&str> = pkg_version.split('.').collect();
    let major = parts.first().unwrap_or(&"0");
    let minor = parts.get(1).unwrap_or(&"0");

    let commit_count = Command::new("git")
        .args(["rev-list", "--count", "HEAD"])
        .output()
        .ok()
        .and_then(|o| String::from_utf8(o.stdout).ok())
        .map(|s| s.trim().to_string())
        .unwrap_or_else(|| "0".to_string());

    let commit_hash = Command::new("git")
        .args(["rev-parse", "--short", "HEAD"])
        .output()
        .ok()
        .and_then(|o| String::from_utf8(o.stdout).ok())
        .map(|s| s.trim().to_string())
        .unwrap_or_else(|| "unknown".to_string());

    let version = format!("{}.{}.{}-{}", major, minor, commit_count, commit_hash);
    println!("cargo:rustc-env=FULL_VERSION={}", version);

    embed_stats_hook().expect("failed to build embedded stats hook");
}

fn embed_stats_hook() -> anyhow::Result<()> {
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
    let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
    let profile = if env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()) == "release" {
        "release"
    } else {
        "debug"
    };
    let hook_manifest = resolve_stats_hook_manifest(&manifest_dir, &cargo)?;

    let mut cmd = Command::new(cargo);
    let target_root = PathBuf::from(env::var("OUT_DIR")?).join("embedded-hook-target");
    cmd.arg("build")
        .arg("--manifest-path")
        .arg(&hook_manifest)
        .arg("--target")
        .arg("wasm32-unknown-unknown")
        .arg("--target-dir")
        .arg(&target_root);
    if profile == "release" {
        cmd.arg("--release");
    }
    let status = cmd.status()?;
    if !status.success() {
        anyhow::bail!("stats hook build failed with status {}", status);
    }

    let wasm_path = target_root
        .join("wasm32-unknown-unknown")
        .join(profile)
        .join("rns_stats_hook.wasm");
    if !Path::new(&wasm_path).exists() {
        anyhow::bail!("expected embedded hook at {}", wasm_path.display());
    }

    println!(
        "cargo:rustc-env=RNS_STATSD_HOOK_WASM={}",
        wasm_path.display()
    );
    Ok(())
}

fn resolve_stats_hook_manifest(manifest_dir: &Path, cargo: &str) -> anyhow::Result<PathBuf> {
    let local = manifest_dir.join("../rns-stats-hook/Cargo.toml");
    if local.exists() {
        return Ok(local);
    }

    let output = Command::new(cargo)
        .args(["metadata", "--format-version", "1"])
        .current_dir(manifest_dir)
        .output()?;
    if !output.status.success() {
        anyhow::bail!("cargo metadata failed with status {}", output.status);
    }

    let value: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    let packages = value
        .get("packages")
        .and_then(|v| v.as_array())
        .ok_or_else(|| anyhow::anyhow!("cargo metadata response missing packages"))?;

    let manifest = packages.iter().find_map(|pkg| {
        let name = pkg.get("name").and_then(|v| v.as_str())?;
        (name == "rns-stats-hook")
            .then(|| pkg.get("manifest_path").and_then(|v| v.as_str()))
            .flatten()
            .map(PathBuf::from)
    });

    manifest.ok_or_else(|| anyhow::anyhow!("could not locate rns-stats-hook manifest"))
}