Subject: [PATCH v2 14/18] build-helper: cbindgen, let crates generate a header file

From: "Ezekiel Newren via GitGitGadget" <[email-masked]>
Date: Wed, 17 Sep 2025 01:16:34 +0000

From: Ezekiel Newren <[email-masked]>

Signed-off-by: Ezekiel Newren <[email-masked]>
---
 rust/build-helper/Cargo.toml |  5 ++++
 rust/build-helper/src/lib.rs | 44 +++++++++++++++++++++++++++++++++++-
 rust/cbindgen-template.toml  | 16 +++++++++++++
 3 files changed, 64 insertions(+), 1 deletion(-)
 create mode 100644 rust/cbindgen-template.toml

diff --git a/rust/build-helper/Cargo.toml b/rust/build-helper/Cargo.toml
index 8939b4b876..2e42bb5405 100644
--- a/rust/build-helper/Cargo.toml
+++ b/rust/build-helper/Cargo.toml
@@ -4,3 +4,8 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
+cbindgen = "0.24.0"
+textwrap = "=0.16.1"
+once_cell = "=1.20.3"
+unicode-width = "=0.1.13"
+
diff --git a/rust/build-helper/src/lib.rs b/rust/build-helper/src/lib.rs
index 70e95d16f5..640bc19734 100644
--- a/rust/build-helper/src/lib.rs
+++ b/rust/build-helper/src/lib.rs
@@ -1,5 +1,7 @@
 use std::collections::HashMap;
+use std::io::Write;
 use std::path::PathBuf;
+use cbindgen::Config;

 fn parse_bool_from_str(value: &str) -> bool {
@@ -23,12 +25,20 @@ fn parse_bool_from_option(value: Option<&String>, default: bool) -> bool {
 /// To run tests set GIT_BUILD_DIR and run `USE_LINKING=true cargo test`
 pub struct BuildHelper {
     crate_env: HashMap<String, String>,
+    generate_header: bool,
+    file_out: PathBuf,
+    config: Config,
 }

 impl BuildHelper {
     pub fn new(crate_env: HashMap<String, String>) -> Self {
-        let it = Self {crate_env};
+        let mut it = Self {
+            crate_env,
+            generate_header: false,
+            file_out: PathBuf::default(),
+            config: Config::default(),
+        };
 
         let dir_crate = it.dir_crate();
         let dir_workspace = dir_crate.parent().unwrap();
@@ -38,6 +48,12 @@ impl BuildHelper {
             std::fs::create_dir(dir_interop.clone()).unwrap();
         }
 
+        let file_cbindgen = dir_workspace.join("cbindgen-template.toml");
+        it.file_out = dir_interop.join(format!("{}.h", it.crate_name()));
+
+        it.config = Config::from_file(file_cbindgen.display().to_string().as_str()).unwrap();
+        it.config.include_guard = Some(format!("{}_H", it.crate_name().to_uppercase()));
+
         it
     }
 
@@ -49,6 +65,16 @@ impl BuildHelper {
         PathBuf::from(self.crate_env["CARGO_MANIFEST_DIR"].clone())
     }
 
+    pub fn generate_header<F>(mut self, editor: F) -> Self
+    where
+        F: Fn(&mut Config)
+    {
+        self.generate_header = true;
+        editor(&mut self.config);
+
+        self
+    }
+
     pub fn build(self) {
         let use_linking = parse_bool_from_option(self.crate_env.get("USE_LINKING"), self.crate_env.get("CARGO_TARGET_DIR").is_none());
         let dir_crate = self.dir_crate();
@@ -78,6 +104,22 @@ impl BuildHelper {
         } else {
             println!("cargo:warning={} is not linking against C objects, `USE_LINKING=true cargo test`", self.crate_env["CARGO_PKG_NAME"]);
         }
+
+        if self.generate_header {
+            let mut buffer = Vec::<u8>::new();
+            cbindgen::Builder::new()
+                .with_crate(dir_crate.clone())
+                .with_config(self.config)
+                .with_std_types(true)
+                .generate()
+                .expect("Unable to generate bindings")
+                .write(&mut buffer);
+
+            let mut fd = std::fs::File::create(self.file_out).unwrap();
+            fd.write(buffer.as_slice()).unwrap();
+        } else {
+            let _ = std::fs::remove_file(self.file_out);
+        }
     }
 }
 
diff --git a/rust/cbindgen-template.toml b/rust/cbindgen-template.toml
new file mode 100644
index 0000000000..cab83218d0
--- /dev/null
+++ b/rust/cbindgen-template.toml
@@ -0,0 +1,16 @@
+## compat/posix.h includes stdbool.h where git-compat-util.h does not
+## this is mandatory for correct bool C <-> Rust interop. Though the
+## 'includes' (and all other variables in this file) can be
+## overridden in build.rs.
+sys_includes = ["compat/posix.h"]
+
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+
+language = "C"
+no_includes = true
+usize_is_size_t = true
+style = "tag"
+tab_width = 4
+
+[parse]
+parse_deps = false
-- 
gitgitgadget