librecast-sys 1.0.1

Low-level bindings to the librecast 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/librecast";
    let dst = PathBuf::from(env::var("OUT_DIR").unwrap());

    // Try to find the librecast library using pkg-config first
    if pkg_config::Config::new()
        .atleast_version("0.11.1")
        .probe("librecast")
        .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");
            }
        }

        // We need to get our headers from liblcrq-sys crate and set them
        // in the CPPFLAGS environment variable for the configure script
        let include_path = env::var_os("DEP_LIBLCRQ_INCLUDE")
            .expect("DEP_LIBLCRQ_INCLUDE environment variable not set");
        let include_for_env = format!("-I{}", include_path.display());
        unsafe { env::set_var("CPPFLAGS", include_for_env) };

        // Set LDFLAGS environment variable too using the INCLUDE path but
        // swapping it to out/src
        let lib_path = PathBuf::from(include_path);
        let lib_search_path = lib_path.join("..").join("src");

        let lib_for_env = format!("-L{}", lib_search_path.display());
        unsafe { env::set_var("LDFLAGS", lib_for_env) };

        let status = Command::new("./configure")
            .args(["--with-blake3", "--with-sodium", "--with-lcrq"])
            .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 librecast library
        println!("cargo:rustc-link-lib=librecast");

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

        // Set our LD_LIBRARY_PATH for dynamic linking for librecast src and lcrq
        // src directories, as they are different
        println!(
            "cargo:rustc-env=LD_LIBRARY_PATH={}/src:{}",
            dst.display(),
            lib_search_path.display()
        );
    }

    // lcrq.h is in the include directory of the liblcrq-sys crate
    let liblcrq_include_path =
        PathBuf::from(env::var_os("DEP_LIBLCRQ_INCLUDE").expect("DEP_LIBLCRQ_INCLUDE not set"));

    // Generate bindings
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        // Where to look for lcrq.h
        .clang_arg(format!("-I{}", liblcrq_include_path.display()))
        // Look in our OUT_DIR for our normal headers
        .clang_arg(format!("-I{}/include", dst.display()))
        // https://github.com/rust-lang/rust-bindgen/issues/1693
        // TODO: As per - https://github.com/rust-lang/rust-bindgen/issues/687#issuecomment-450750547
        .blocklist_item("IPPORT_RESERVED")
        .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!");
}