From b8b4c723d2ad448c75ed4343b590d78c4435dad6 Mon Sep 17 00:00:00 2001 From: xdustinface Date: Mon, 9 Mar 2026 11:22:32 +0700 Subject: [PATCH] build: centralize FFI header generation in `ffi-header-gen` crate Introduces `ffi-header-gen` as a library + binary crate that wraps `cbindgen` creation for the ffi crates. Both FFI crates use it as a build-dependency, and `verify_ffi.py` runs the binary directly. `cbindgen` is now defined in a single place. --- Cargo.toml | 2 +- contrib/verify_ffi.py | 15 ++++--- dash-spv-ffi/CLAUDE.md | 4 +- dash-spv-ffi/Cargo.toml | 6 +-- dash-spv-ffi/build.rs | 19 ++------- ffi-header-gen/Cargo.toml | 17 ++++++++ ffi-header-gen/src/lib.rs | 28 +++++++++++++ ffi-header-gen/src/main.rs | 46 +++++++++++++++++++++ key-wallet-ffi/Cargo.toml | 2 +- key-wallet-ffi/build.rs | 23 +++-------- key-wallet-ffi/generate_header.sh | 67 ------------------------------- 11 files changed, 114 insertions(+), 115 deletions(-) create mode 100644 ffi-header-gen/Cargo.toml create mode 100644 ffi-header-gen/src/lib.rs create mode 100644 ffi-header-gen/src/main.rs delete mode 100755 key-wallet-ffi/generate_header.sh diff --git a/Cargo.toml b/Cargo.toml index 42b96ab0d..f5401aa09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi", "test-utils"] +members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi", "test-utils", "ffi-header-gen"] resolver = "2" [workspace.package] diff --git a/contrib/verify_ffi.py b/contrib/verify_ffi.py index e591b4460..9a1894089 100755 --- a/contrib/verify_ffi.py +++ b/contrib/verify_ffi.py @@ -9,18 +9,17 @@ FFI_CRATES = ["key-wallet-ffi", "dash-spv-ffi"] -def build_ffi_crates(repo_root: Path) -> bool: - """Build all FFI crates to regenerate headers.""" - print(" Building FFI crates...") +def regenerate_headers(repo_root: Path) -> bool: + """Regenerate FFI headers using the ffi-header-gen workspace binary.""" + print(" Generating FFI headers...") result = subprocess.run( - ["cargo", "build", "--quiet", "--target-dir", "target/verify-ffi"] - + [f"-p={crate}" for crate in FFI_CRATES], + ["cargo", "run", "--quiet", "-p", "ffi-header-gen", "--", str(repo_root)], cwd=repo_root, capture_output=True, text=True ) if result.returncode != 0: - print("Build failed:", file=sys.stderr) + print("Header generation failed:", file=sys.stderr) if result.stderr: print(result.stderr, file=sys.stderr) return False @@ -48,8 +47,8 @@ def main(): print("Regenerating FFI headers and documentation") - # Build all FFI crates first - if not build_ffi_crates(repo_root): + # Generate headers + if not regenerate_headers(repo_root): sys.exit(1) # Generate docs in parallel diff --git a/dash-spv-ffi/CLAUDE.md b/dash-spv-ffi/CLAUDE.md index 979fa9507..94604ac23 100644 --- a/dash-spv-ffi/CLAUDE.md +++ b/dash-spv-ffi/CLAUDE.md @@ -22,9 +22,9 @@ cargo build --release --target aarch64-apple-ios-sim ``` ### Header Generation -The C header is auto-generated by the build script. To regenerate manually: +The C header is auto-generated by the build script via the `ffi-header-gen` workspace crate. To regenerate all FFI headers manually: ```bash -cbindgen --config cbindgen.toml --crate dash-spv-ffi --output include/dash_spv_ffi.h +cargo run -p ffi-header-gen ``` ### Unified SDK Build diff --git a/dash-spv-ffi/Cargo.toml b/dash-spv-ffi/Cargo.toml index 15714da06..9590baec0 100644 --- a/dash-spv-ffi/Cargo.toml +++ b/dash-spv-ffi/Cargo.toml @@ -33,15 +33,15 @@ key-wallet-manager = { path = "../key-wallet-manager" } rand = "0.8" clap = { version = "4.5", features = ["derive"] } +[build-dependencies] +ffi-header-gen = { path = "../ffi-header-gen" } + [dev-dependencies] tempfile = "3.8" serial_test = "3.0" env_logger = "0.10" dashcore-test-utils = { path = "../test-utils" } -[build-dependencies] -cbindgen = "0.29" - [[bin]] name = "dash-spv-ffi" path = "src/bin/ffi_cli.rs" diff --git a/dash-spv-ffi/build.rs b/dash-spv-ffi/build.rs index bd1de9edc..d693876c1 100644 --- a/dash-spv-ffi/build.rs +++ b/dash-spv-ffi/build.rs @@ -3,25 +3,12 @@ use std::path::PathBuf; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let output_path = PathBuf::from(&crate_dir).join("include"); + let crate_path = PathBuf::from(&crate_dir); - std::fs::create_dir_all(&output_path).unwrap(); - - // Ensure the build script reruns when header-relevant files change println!("cargo:rerun-if-changed=cbindgen.toml"); println!("cargo:rerun-if-changed=src"); - let config = cbindgen::Config::from_file("cbindgen.toml") - .expect("cbindgen config missing or invalid: cbindgen.toml"); - - match cbindgen::Builder::new().with_crate(&crate_dir).with_config(config).generate() { - Ok(bindings) => { - bindings.write_to_file(output_path.join("dash_spv_ffi.h")); - println!("cargo:warning=Generated C header at {:?}", output_path); - } - Err(e) => { - // Fail the build to avoid shipping stale headers - panic!("Failed to generate C header via cbindgen: {}", e); - } + if let Err(e) = ffi_header_gen::generate_header(&crate_path, "dash_spv_ffi.h") { + panic!("{}", e); } } diff --git a/ffi-header-gen/Cargo.toml b/ffi-header-gen/Cargo.toml new file mode 100644 index 000000000..c73c6f606 --- /dev/null +++ b/ffi-header-gen/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ffi-header-gen" +version = { workspace = true } +edition = "2021" +publish = false +description = "Generates C headers for FFI crates using cbindgen" + +[lib] +name = "ffi_header_gen" +path = "src/lib.rs" + +[[bin]] +name = "ffi-header-gen" +path = "src/main.rs" + +[dependencies] +cbindgen = "0.29" diff --git a/ffi-header-gen/src/lib.rs b/ffi-header-gen/src/lib.rs new file mode 100644 index 000000000..8650301ed --- /dev/null +++ b/ffi-header-gen/src/lib.rs @@ -0,0 +1,28 @@ +use std::path::Path; + +use cbindgen::Config; + +/// Generates a C header file for the given crate directory. +/// +/// Loads `cbindgen.toml` from `crate_dir`, runs cbindgen, and writes the +/// header to `/include/`. +pub fn generate_header(crate_dir: &Path, header_name: &str) -> Result<(), String> { + let config_path = crate_dir.join("cbindgen.toml"); + let output_dir = crate_dir.join("include"); + let output_path = output_dir.join(header_name); + + std::fs::create_dir_all(&output_dir) + .map_err(|e| format!("failed to create {}: {}", output_dir.display(), e))?; + + let config = Config::from_file(&config_path) + .map_err(|e| format!("failed to load {}: {}", config_path.display(), e))?; + + let bindings = cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(config) + .generate() + .map_err(|e| format!("failed to generate header for {}: {}", crate_dir.display(), e))?; + + bindings.write_to_file(&output_path); + Ok(()) +} diff --git a/ffi-header-gen/src/main.rs b/ffi-header-gen/src/main.rs new file mode 100644 index 000000000..a0580294d --- /dev/null +++ b/ffi-header-gen/src/main.rs @@ -0,0 +1,46 @@ +use std::path::PathBuf; +use std::process; + +use ffi_header_gen::generate_header; + +struct FfiCrate { + dir: &'static str, + header: &'static str, +} + +const FFI_CRATES: &[FfiCrate] = &[ + FfiCrate { + dir: "key-wallet-ffi", + header: "key_wallet_ffi.h", + }, + FfiCrate { + dir: "dash-spv-ffi", + header: "dash_spv_ffi.h", + }, +]; + +fn main() { + let repo_root = std::env::args() + .nth(1) + .map(PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().expect("failed to get current directory")); + + let mut failed = false; + + for ffi in FFI_CRATES { + let crate_dir = repo_root.join(ffi.dir); + match generate_header(&crate_dir, ffi.header) { + Ok(()) => { + eprintln!("generated {}", crate_dir.join("include").join(ffi.header).display()) + } + Err(e) => { + eprintln!("{}", e); + failed = true; + } + } + } + + if failed { + process::exit(1); + } +} diff --git a/key-wallet-ffi/Cargo.toml b/key-wallet-ffi/Cargo.toml index 5148da999..e1a0516c4 100644 --- a/key-wallet-ffi/Cargo.toml +++ b/key-wallet-ffi/Cargo.toml @@ -30,7 +30,7 @@ libc = "0.2" hex = "0.4" [build-dependencies] -cbindgen = "0.29" +ffi-header-gen = { path = "../ffi-header-gen" } [dev-dependencies] tempfile = "3.0" diff --git a/key-wallet-ffi/build.rs b/key-wallet-ffi/build.rs index 5519a0d86..00fd90122 100644 --- a/key-wallet-ffi/build.rs +++ b/key-wallet-ffi/build.rs @@ -1,5 +1,4 @@ // Build script for key-wallet-ffi -// Generates C header file using cbindgen use std::env; use std::path::PathBuf; @@ -18,24 +17,14 @@ fn main() { _ => {} } - // Generate C header file using cbindgen + // Generate C header file let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let output_path = PathBuf::from(&crate_dir).join("include/key_wallet_ffi.h"); + let crate_path = PathBuf::from(&crate_dir); - // Create include directory if it doesn't exist - std::fs::create_dir_all(output_path.parent().unwrap()).ok(); + println!("cargo:rerun-if-changed=cbindgen.toml"); + println!("cargo:rerun-if-changed=src"); - match cbindgen::Builder::new() - .with_crate(&crate_dir) - .with_config(cbindgen::Config::from_file("cbindgen.toml").unwrap_or_default()) - .generate() - { - Ok(bindings) => { - bindings.write_to_file(&output_path); - println!("cargo:warning=Generated C header at {:?}", output_path); - } - Err(e) => { - println!("cargo:warning=Failed to generate C header: {}", e); - } + if let Err(e) = ffi_header_gen::generate_header(&crate_path, "key_wallet_ffi.h") { + println!("cargo:warning=Failed to generate C header: {}", e); } } diff --git a/key-wallet-ffi/generate_header.sh b/key-wallet-ffi/generate_header.sh deleted file mode 100755 index a3ea6a365..000000000 --- a/key-wallet-ffi/generate_header.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - -# Script to generate C header file from Rust FFI code using cbindgen -# Usage: ./generate_header.sh - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${GREEN}Generating C header file for key-wallet-ffi...${NC}" - -# Check if cbindgen is installed -if ! command -v cbindgen &> /dev/null; then - echo -e "${YELLOW}cbindgen is not installed. Installing...${NC}" - cargo install cbindgen -fi - -# Create include directory if it doesn't exist -mkdir -p include - -# Generate the header file -echo -e "${GREEN}Running cbindgen...${NC}" -cbindgen \ - --config cbindgen.toml \ - --crate key-wallet-ffi \ - --output include/key_wallet_ffi.h \ - --lang c \ - . - -if [ $? -eq 0 ]; then - echo -e "${GREEN}✓ Header file generated successfully at include/key_wallet_ffi.h${NC}" - - # Show statistics - echo -e "${GREEN}Header file statistics:${NC}" - echo " Functions: $(grep -c "^[^/]*(" include/key_wallet_ffi.h 2>/dev/null || echo 0)" - echo " Structs: $(grep -c "^typedef struct" include/key_wallet_ffi.h 2>/dev/null || echo 0)" - echo " Enums: $(grep -c "^typedef enum" include/key_wallet_ffi.h 2>/dev/null || echo 0)" - -else - echo -e "${RED}✗ Failed to generate header file${NC}" - exit 1 -fi - -# Optional: Verify the header compiles -echo -e "${GREEN}Verifying header compilation...${NC}" -cat > /tmp/test_header.c << EOF -#include "$(pwd)/include/key_wallet_ffi.h" -int main() { return 0; } -EOF - -if cc -c /tmp/test_header.c -o /tmp/test_header.o 2>/dev/null; then - echo -e "${GREEN}✓ Header file compiles successfully${NC}" - rm -f /tmp/test_header.c /tmp/test_header.o -else - echo -e "${YELLOW}⚠ Warning: Header file may have compilation issues${NC}" - echo -e "${YELLOW} This might be normal if some types are platform-specific${NC}" - rm -f /tmp/test_header.c -fi - -echo -e "${GREEN}Done!${NC}"