liblcrq-sys 1.0.2

Low-level bindings to the liblcrq C library
Documentation
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
// Copyright (c) 2025 Gavin Henry <ghenry@sentrypeer.org>

extern crate bindgen;
extern crate cc;
use walkdir::WalkDir;

use std::env;
use std::path::PathBuf;
use std::process::Command;

fn main() {
    // Initialize and update the Git submodule
    Command::new("git")
        .args(["submodule", "update", "--init", "--recursive"])
        .status()
        .expect("Failed to initialize/update submodules");

    let submodule_path = "vendor/liblcrq";
    let dst = PathBuf::from(env::var("OUT_DIR").unwrap());

    // Try to find the liblcrq library using pkg-config first
    if pkg_config::Config::new()
        .atleast_version("0.2.4")
        .probe("lcrq")
        .is_ok()
    {
        // If found, the necessary env vars will get set automatically
    } else {
        let src = PathBuf::from(submodule_path);

        // Copy everything from the submodule to our OUT_DIR using WalkDir
        for entry in WalkDir::new(&src).into_iter().filter_map(Result::ok) {
            let src_path = entry.path();
            let dst_path = dst.join(src_path.strip_prefix(&src).unwrap());

            // Create the destination directory if it doesn't exist
            if src_path.is_dir() {
                std::fs::create_dir_all(&dst_path).expect("Failed to create directory");
            } else {
                // Skip symlinks, as all the tests are like so:
                // 0000-0114.test -> ../0000-0114.test which don't
                // exist in the test directory
                if src_path.is_symlink() {
                    continue;
                }

                std::fs::copy(src_path, &dst_path).expect("Failed to copy a file");
            }
        }

        // Set CFLAGS environment variable for the configure script
        let include_for_env = format!(
            "-I{}",
            "-Wall -Wextra -pedantic -O3 -march=native -mpopcnt -ffast-math -funroll-loops -flto -DNDEBUG"
        );
        unsafe { env::set_var("CFLAGS", include_for_env) };

        let status = Command::new("./configure")
            .arg("--enable-native")
            .current_dir(&dst)
            .status()
            .expect("Failed to find ./configure");

        if !status.success() {
            panic!("./configure failed to complete or run in {}", dst.display());
        }

        let status = Command::new("make")
            .current_dir(&dst)
            .status()
            .expect("Failed to run make");

        if !status.success() {
            panic!("Failed to run make in {}", dst.display());
        }

        // Set our shared library path
        println!("cargo:rustc-link-search={}/src", dst.display());

        // Search for headers in our OUT_DIR
        println!("cargo:include={}/include", dst.display());

        // Link against the liblcrq library
        println!("cargo:rustc-link-lib=lcrq");

        // Set our LD_LIBRARY_PATH for dynamic linking
        println!("cargo:rustc-env=LD_LIBRARY_PATH={}/src", dst.display());
    }

    // Generate bindings
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        // Look in our OUT_DIR for the headers
        .clang_arg(format!("-I{}/include", dst.display()))
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file
    bindings
        .write_to_file(dst.join("bindings.rs"))
        .expect("Couldn't write bindings.rs!");
}