libbsd-sys 0.2.1

Raw FFI bindings to libbsd
Documentation
use std::path::Path;

/// Read an env var, registering it with cargo's rerun-if-env-changed tracking.
fn tracked_var_os(key: &str) -> Option<std::ffi::OsString> {
    println!("cargo:rerun-if-env-changed={key}");
    std::env::var_os(key)
}

/// Parse a boolean env var. Returns `None` if absent, `Some(bool)` if present.
/// Accepts 1/0/true/false/yes/no (case-insensitive). Panics on unrecognized values.
fn parse_bool_env(key: &str) -> Option<bool> {
    let val = tracked_var_os(key)?;
    let s = val
        .to_str()
        .unwrap_or_else(|| panic!("{key} is not valid UTF-8"));
    match s.to_ascii_lowercase().as_str() {
        "1" | "true" | "yes" => Some(true),
        "0" | "false" | "no" => Some(false),
        other => panic!("{key}={other}: expected 1/0/true/false/yes/no"),
    }
}

fn main() {
    let target_os = tracked_var_os("CARGO_CFG_TARGET_OS")
        .unwrap()
        .into_string()
        .unwrap();
    match target_os.as_str() {
        // On macOS, OpenBSD, and NetBSD the BSD functions live in libc
        // (or libSystem on macOS), so no extra library is needed.
        "macos" | "openbsd" | "netbsd" => return,
        // On FreeBSD, most functions are in libc, but humanize_number,
        // pidfile_*, flopen, and expand_number live in libutil.
        "freebsd" => {
            println!("cargo:rustc-link-lib=util");
            return;
        }
        // Windows and other unsupported platforms: nothing to link.
        "windows" => return,
        // Everything else (Linux, etc.) needs the libbsd library.
        _ => {}
    }

    // docs.rs builds and CI clippy without libbsd-dev: skip all linking.
    if tracked_var_os("DOCS_RS").is_some() {
        return;
    }

    let feat_static = tracked_var_os("CARGO_FEATURE_STATIC").is_some();
    let feat_overlay = tracked_var_os("CARGO_FEATURE_OVERLAY").is_some();
    let env_static = parse_bool_env("LIBBSD_STATIC");
    let no_pkgcfg = tracked_var_os("LIBBSD_NO_PKG_CONFIG").is_some();
    let lib_dir = tracked_var_os("LIBBSD_LIB_DIR");
    let inc_dir = tracked_var_os("LIBBSD_INCLUDE_DIR");
    // Env var wins over feature (openssl-sys semantics).
    let statik = env_static.unwrap_or(feat_static);

    // Manual override path: bypass pkg-config entirely.
    if let Some(dir) = &lib_dir {
        println!(
            "cargo:rustc-link-search=native={}",
            Path::new(dir).display()
        );
        println!("cargo:libdir={}", Path::new(dir).display());
        let kind = if statik { "static" } else { "dylib" };
        println!("cargo:rustc-link-lib={kind}=bsd");
        if let Some(inc) = &inc_dir {
            for p in std::env::split_paths(inc) {
                println!("cargo:include={}", p.display());
            }
        }
        return;
    }

    // No pkg-config, no lib dir: skip linking entirely.  This lets
    // `cargo clippy` (and similar check-only builds) succeed in downstream
    // crates without libbsd-dev installed.  Any final binary that actually
    // uses symbols from libbsd will need to arrange linkage itself
    // (e.g. via RUSTFLAGS="-l bsd" or by setting LIBBSD_LIB_DIR).
    if no_pkgcfg {
        if let Some(inc) = &inc_dir {
            for p in std::env::split_paths(inc) {
                println!("cargo:include={}", p.display());
            }
        }
        return;
    }

    // pkg-config path.
    let pkg = if feat_overlay {
        "libbsd-overlay"
    } else {
        "libbsd"
    };
    let mut cfg = pkg_config::Config::new();
    cfg.atleast_version("0.11");
    if statik {
        cfg.statik(true);
    }
    let lib = match cfg.probe(pkg) {
        Ok(lib) => lib,
        Err(e) => {
            // pkg-config failed.  Warn and skip the link step rather than
            // panic, so `cargo clippy` / `cargo check` in downstream crates
            // succeed on machines without libbsd-dev.  A real binary build
            // that actually references libbsd symbols will still fail at
            // link time, and the warnings below tell the user how to fix it.
            //
            // cargo:warning is single-line, so collapse pkg-config's error
            // text and emit each line of context as its own warning.
            let e = e.to_string().replace('\n', " ");
            println!("cargo:warning=libbsd-sys: {pkg} not found via pkg-config: {e}");
            println!(
                "cargo:warning=libbsd-sys: link step skipped; a real binary build \
                 will fail at link time."
            );
            println!(
                "cargo:warning=libbsd-sys: install libbsd-dev, set LIBBSD_LIB_DIR=/path/to/lib, \
                 or set LIBBSD_NO_PKG_CONFIG=1 to silence this warning."
            );
            return;
        }
    };

    // Re-export paths so downstream build scripts can use them via
    // DEP_BSD_INCLUDE / DEP_BSD_LIBDIR.
    for p in &lib.include_paths {
        println!("cargo:include={}", p.display());
    }
    for p in &lib.link_paths {
        println!("cargo:libdir={}", p.display());
    }
}