wavepeek 2.1.0

Command-line tool for RTL waveform inspection with deterministic machine-friendly output.
Documentation
use std::env;
use std::path::{Path, PathBuf};

fn main() {
    println!("cargo:rerun-if-env-changed=VERDI_HOME");
    println!("cargo:rerun-if-env-changed=WAVEPEEK_FSDB_READER_LIBDIR");
    println!("cargo:rerun-if-env-changed=WAVEPEEK_FSDB_ABI");
    println!("cargo:rerun-if-env-changed=WAVEPEEK_FSDB_EMBED_RPATH");
    println!("cargo:rerun-if-changed=native/fsdb/wavepeek_fsdb_shim.cpp");
    println!("cargo:rerun-if-changed=native/fsdb/wavepeek_fsdb_shim.h");

    if env::var_os("CARGO_FEATURE_FSDB").is_none() {
        return;
    }

    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
    let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
    if target_os != "linux" || target_arch != "x86_64" {
        panic!("FSDB support is currently Linux x86_64 only; target is {target_os}/{target_arch}");
    }

    let sdk = resolve_fsdb_sdk();
    compile_fsdb_shim(&sdk);
    emit_fsdb_link_settings(&sdk);
}

#[derive(Debug)]
struct FsdbSdk {
    reader_root: PathBuf,
    libdir: PathBuf,
}

fn resolve_fsdb_sdk() -> FsdbSdk {
    let verdi_home = env::var_os("VERDI_HOME")
        .map(PathBuf::from)
        .filter(|path| !path.as_os_str().is_empty())
        .unwrap_or_else(|| {
            panic!(
                "FSDB support requires VERDI_HOME; set VERDI_HOME to a Synopsys Verdi installation containing share/FsdbReader"
            )
        });

    let reader_root = verdi_home.join("share").join("FsdbReader");
    require_file(
        &reader_root.join("ffrAPI.h"),
        "set VERDI_HOME to a Verdi installation containing share/FsdbReader/ffrAPI.h",
    );
    require_file(
        &reader_root.join("ffrKit.h"),
        "set VERDI_HOME to a Verdi installation containing share/FsdbReader/ffrKit.h",
    );
    require_file(
        &reader_root.join("fsdbShr.h"),
        "set VERDI_HOME to a Verdi installation containing share/FsdbReader/fsdbShr.h",
    );

    let libdir = match env::var_os("WAVEPEEK_FSDB_READER_LIBDIR") {
        Some(value) if !value.is_empty() => PathBuf::from(value),
        _ => {
            let abi = env::var_os("WAVEPEEK_FSDB_ABI")
                .filter(|value| !value.is_empty())
                .unwrap_or_else(|| "linux64".into());
            reader_root.join(abi)
        }
    };

    require_file(
        &libdir.join("libnffr.so"),
        "set WAVEPEEK_FSDB_READER_LIBDIR to the FSDB Reader library directory or try WAVEPEEK_FSDB_ABI=linux64_gcc950",
    );
    require_file(
        &libdir.join("libnsys.so"),
        "set WAVEPEEK_FSDB_READER_LIBDIR to the FSDB Reader library directory or try WAVEPEEK_FSDB_ABI=linux64_gcc950",
    );

    FsdbSdk {
        reader_root,
        libdir,
    }
}

fn require_file(path: &Path, hint: &str) {
    if !path.is_file() {
        panic!(
            "FSDB Reader SDK check failed: missing {}; {}",
            path.display(),
            hint
        );
    }
}

fn compile_fsdb_shim(sdk: &FsdbSdk) {
    cc::Build::new()
        .cpp(true)
        .flag_if_supported("-std=c++17")
        .flag_if_supported("-fvisibility=hidden")
        .flag_if_supported("-Wno-unused-parameter")
        .include(&sdk.reader_root)
        .include("native/fsdb")
        .file("native/fsdb/wavepeek_fsdb_shim.cpp")
        .compile("wavepeek_fsdb_shim");
}

fn emit_fsdb_link_settings(sdk: &FsdbSdk) {
    println!("cargo:rustc-link-search=native={}", sdk.libdir.display());
    println!("cargo:rustc-link-lib=dylib=nffr");
    println!("cargo:rustc-link-lib=dylib=nsys");
    println!("cargo:rustc-link-arg=-Wl,--no-as-needed");
    println!("cargo:rustc-link-arg=-lnffr");
    println!("cargo:rustc-link-arg=-lnsys");
    println!("cargo:rustc-link-arg=-l:libz.so.1");
    println!("cargo:rustc-link-arg=-Wl,--as-needed");

    if embed_fsdb_rpath_enabled() {
        println!("cargo:rustc-link-arg=-Wl,-rpath,{}", sdk.libdir.display());
    }
}

fn embed_fsdb_rpath_enabled() -> bool {
    let Some(value) = env::var_os("WAVEPEEK_FSDB_EMBED_RPATH") else {
        return true;
    };
    if value.is_empty() {
        return true;
    }

    match value.to_string_lossy().to_ascii_lowercase().as_str() {
        "1" | "true" | "yes" | "on" => true,
        "0" | "false" | "no" | "off" => false,
        _ => {
            panic!("WAVEPEEK_FSDB_EMBED_RPATH must be one of 1/true/yes/on/0/false/no/off")
        }
    }
}