hyperscan-sys 0.3.2

Hyperscan bindings for Rust with Multiple Pattern and Streaming Scan
Documentation
use std::env;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, bail, Context, Result};

fn find_hyperscan() -> Result<PathBuf> {
    cargo_emit::rerun_if_env_changed!("HYPERSCAN_ROOT");

    let link_kind = if cfg!(feature = "static") { "static" } else { "dylib" };
    let static_libstd = cfg!(feature = "contained");

    if let Ok(prefix) = env::var("HYPERSCAN_ROOT") {
        let prefix = Path::new(&prefix);
        let inc_path = prefix.join("include/hs");
        let link_path = prefix.join("lib");

        if cfg!(feature = "tracing") {
            cargo_emit::warning!("use HYPERSCAN_ROOT = {}", prefix.display());
        }

        if !prefix.exists() || !prefix.is_dir() {
            bail!("HYPERSCAN_ROOT should point to a directory that exists.");
        }

        if link_path.exists() && link_path.is_dir() {
            cargo_emit::rustc_link_search!(link_path.to_string_lossy() => "native");
        } else {
            bail!("`$HYPERSCAN_ROOT/lib` subdirectory not found.");
        }

        cargo_emit::rustc_link_search!(link_path.to_string_lossy() => "native");

        if cfg!(feature = "static") {
            let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
            let std_link = if target_os == "macos" { "c++" } else { "stdc++" };
            if static_libstd {
                cargo_emit::rustc_link_lib!(std_link => "static:-bundle");
            } else {
                cargo_emit::rustc_link_lib!(std_link);
            }
        }

        if !cfg!(feature = "compile") && cfg!(feature = "runtime") {
            cargo_emit::rustc_link_lib!("hs_runtime" => link_kind);
        } else {
            cargo_emit::rustc_link_lib!("hs" => link_kind);
        }

        if cfg!(feature = "chimera") {
            cargo_emit::rustc_link_lib!("chimera" => "static");
            cargo_emit::rustc_link_lib!("pcre" => "static");
        }

        if cfg!(feature = "tracing") {
            cargo_emit::warning!(
                "building with Hyperscan with {} library @ {:?}, link_paths=[{:?}], include_paths=[{:?}]",
                link_kind,
                prefix,
                link_path,
                inc_path
            );
        }

        Ok(inc_path)
    } else {
        let libhs = pkg_config::Config::new()
            .statik(cfg!(feature = "static"))
            .cargo_metadata(true)
            .env_metadata(true)
            .probe("libhs")?;

        if cfg!(feature = "tracing") {
            cargo_emit::warning!(
                "building with Hyperscan {} with {} library, libs={:?}, link_paths={:?}, include_paths={:?}",
                libhs.version,
                link_kind,
                libhs.libs,
                libhs.link_paths,
                libhs.include_paths
            );
        }

        if cfg!(feature = "chimera") {
            let libch = pkg_config::Config::new()
                .statik(cfg!(feature = "static"))
                .cargo_metadata(true)
                .env_metadata(true)
                .probe("libch")?;

            if cfg!(feature = "tracing") {
                cargo_emit::warning!(
                    "building with Chimera {} with {} library, libs={:?}, link_paths={:?}, include_paths={:?}",
                    libch.version,
                    link_kind,
                    libch.libs,
                    libch.link_paths,
                    libch.include_paths
                );
            }
        }

        libhs
            .include_paths
            .first()
            .cloned()
            .ok_or_else(|| anyhow!("missing include path"))
    }
}

#[cfg(any(feature = "gen", not(target_pointer_width = "64")))]
fn generate_binding(inc_dir: &Path, out_dir: &Path) -> Result<()> {
    let out_file = out_dir.join("hyperscan.rs");
    let inc_file = inc_dir.join("hs.h");
    let inc_file = inc_file.to_str().expect("header file");

    if cfg!(feature = "tracing") {
        cargo_emit::warning!("generating raw Hyperscan binding file @ {}", out_file.display());
    }

    cargo_emit::rerun_if_changed!(inc_file);

    bindgen::builder()
        .header(inc_file)
        .use_core()
        .ctypes_prefix("::libc")
        .clang_args(&["-x", "c++", "-std=c++11"])
        .allowlist_var("^HS_.*")
        .allowlist_type("^hs_.*")
        .allowlist_function("^hs_.*")
        .blocklist_type("^__darwin_.*")
        .size_t_is_usize(true)
        .derive_copy(true)
        .derive_debug(true)
        .derive_default(true)
        .derive_partialeq(true)
        .derive_eq(true)
        .generate()
        .map_err(|_| anyhow::Error::msg("generate binding files"))?
        .write_to_file(out_file)
        .with_context(|| "write wrapper")
}

#[cfg(all(not(feature = "gen"), target_pointer_width = "64"))]
fn generate_binding(_: &Path, out_dir: &Path) -> Result<()> {
    std::fs::copy("src/hyperscan.rs", out_dir.join("hyperscan.rs"))
        .map(|_| ())
        .with_context(|| "copy binding file")
}

#[cfg(all(feature = "chimera", any(feature = "gen", not(target_pointer_width = "64"))))]
fn generate_chimera_binding(inc_dir: &Path, out_dir: &Path) -> Result<()> {
    let out_file = out_dir.join("chimera.rs");
    let inc_file = inc_dir.join("ch.h");
    let inc_file = inc_file.to_str().expect("header file");

    if cfg!(feature = "tracing") {
        cargo_emit::warning!("generating raw Chimera binding file @ {}", out_file.display());
    }

    cargo_emit::rerun_if_changed!(inc_file);

    bindgen::builder()
        .header(inc_file)
        .use_core()
        .ctypes_prefix("::libc")
        .clang_args(&["-x", "c++", "-std=c++11"])
        .allowlist_var("^CH_.*")
        .allowlist_type("^ch_.*")
        .allowlist_function("^ch_.*")
        .blocklist_type("^__darwin_.*")
        .size_t_is_usize(true)
        .derive_copy(true)
        .derive_debug(true)
        .derive_default(true)
        .derive_partialeq(true)
        .derive_eq(true)
        .generate()
        .map_err(|_| anyhow::Error::msg("generate binding files"))?
        .write_to_file(out_file)
        .with_context(|| "write wrapper")
}

#[cfg(all(not(feature = "gen"), feature = "chimera", target_pointer_width = "64"))]
fn generate_chimera_binding(_: &Path, out_dir: &Path) -> Result<()> {
    std::fs::copy("src/chimera.rs", out_dir.join("chimera.rs"))
        .map(|_| ())
        .with_context(|| "copy binding file")
}

#[cfg(all(not(feature = "chimera"), target_pointer_width = "64"))]
fn generate_chimera_binding(_: &Path, _: &Path) -> Result<()> {
    Ok(())
}

fn main() -> Result<()> {
    let inc_dir = if std::env::var("DOCS_RS").is_ok() {
        PathBuf::new()
    } else {
        find_hyperscan().with_context(|| "please download and install hyperscan from https://www.hyperscan.io/")?
    };
    let out_dir = env::var("OUT_DIR")?;
    let out_dir = Path::new(&out_dir);

    generate_binding(&inc_dir, out_dir)?;

    if cfg!(feature = "chimera") {
        generate_chimera_binding(&inc_dir, out_dir)?;
    }

    Ok(())
}