rns-cli 0.2.3

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

#[path = "build_common.rs"]
mod build_common;

fn main() {
    build_common::emit_git_rerun_inputs();
    println!("cargo:rerun-if-changed=../rns-stats-hook/src/lib.rs");
    println!("cargo:rerun-if-changed=../rns-stats-hook/Cargo.toml");
    println!("cargo:rerun-if-changed=../rns-sentinel-hook/src/lib.rs");
    println!("cargo:rerun-if-changed=../rns-sentinel-hook/Cargo.toml");
    build_common::emit_full_version();

    if env::var_os("CARGO_FEATURE_RNS_HOOKS_WASM").is_some() {
        embed_stats_hook().expect("failed to build embedded stats hook");
        embed_sentinel_hook().expect("failed to build embedded sentinel 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"))
}

fn embed_sentinel_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_sentinel_hook_manifest(&manifest_dir, &cargo)?;

    let mut cmd = Command::new(cargo);
    let target_root = PathBuf::from(env::var("OUT_DIR")?).join("embedded-sentinel-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!("sentinel hook build failed with status {}", status);
    }

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

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

fn resolve_sentinel_hook_manifest(manifest_dir: &Path, cargo: &str) -> anyhow::Result<PathBuf> {
    let local = manifest_dir.join("../rns-sentinel-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-sentinel-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-sentinel-hook manifest"))
}