From 51c47a300c97e49e6b78329312ba892ce636ff39 Mon Sep 17 00:00:00 2001 From: Jared Hoberock Date: Wed, 8 Apr 2026 18:00:37 -0500 Subject: [PATCH] Pass wrapper.h to bindgen in-memory to avoid rebuild loop Previously the build script wrote `wrapper.h` to `$OUT_DIR` and passed the path to bindgen via `.header(...)`. Bindgen's `CargoCallbacks` then emitted `cargo:rerun-if-changed=`, telling cargo to track the file. Since the build script unconditionally rewrote `wrapper.h` on every invocation (newer mtime than the script's `output` file, which is finalized when cargo captures the script's stdout), cargo always saw the file as newer than its recorded fingerprint and marked the build script as stale. This forced a rerun on every `cargo build` / `cargo test`, which in turn invalidated `mlir-sys`'s lib target and every downstream crate. Switch to `bindgen::Builder::header_contents("wrapper.h", &content)` so the wrapper exists only in memory. Bindgen still needs a logical header name for diagnostics; the string `wrapper.h` is passed but nothing is written to disk, so there is no file for `CargoCallbacks` to track and the rerun loop is gone. Co-Authored-By: Claude Opus 4.6 (1M context) --- build.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/build.rs b/build.rs index f000270..8f04e46 100644 --- a/build.rs +++ b/build.rs @@ -3,11 +3,15 @@ use std::{ error::Error, ffi::OsStr, fs, - path::{Path, PathBuf}, + path::Path, process::{Command, Stdio, exit}, str, }; +/// Logical name passed to bindgen for the in-memory wrapper. Bindgen needs a +/// `header_name` for diagnostics; this string never touches disk. +const WRAPPER_NAME: &str = "wrapper.h"; + const LLVM_MAJOR_VERSION: usize = 22; fn main() { @@ -107,10 +111,10 @@ fn run() -> Result<(), Box> { } let include_dir = llvm_config("--includedir", &link_mode)?; - let wrapper_path = generate_wrapper(&include_dir)?; + let wrapper_contents = generate_wrapper_contents(&include_dir)?; bindgen::builder() - .header(wrapper_path.to_str().unwrap()) + .header_contents(WRAPPER_NAME, &wrapper_contents) .clang_arg(format!("-I{include_dir}")) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() @@ -233,7 +237,14 @@ fn parse_shared_lib_name(name: &str) -> Option<&str> { } } -fn generate_wrapper(include_dir: &str) -> Result> { +/// Walk `{includedir}/mlir-c/` and build an in-memory list of `#include`s +/// covering every header. Returned as a `String` so it can be passed to +/// bindgen via `header_contents`, avoiding any on-disk wrapper file. A +/// disk-backed wrapper would have its mtime rewritten on every build, +/// which interacts badly with bindgen's `CargoCallbacks::rerun-if-changed` +/// tracking and forces cargo to rebuild this crate (and everything that +/// depends on it) on every invocation. +fn generate_wrapper_contents(include_dir: &str) -> Result> { let mlir_c_dir = Path::new(include_dir).join("mlir-c"); let mut headers = Vec::new(); collect_headers(&mlir_c_dir, &mlir_c_dir, &mut headers)?; @@ -243,10 +254,7 @@ fn generate_wrapper(include_dir: &str) -> Result> { for header in &headers { content.push_str(&format!("#include \n")); } - - let out_path = PathBuf::from(env::var("OUT_DIR")?).join("wrapper.h"); - fs::write(&out_path, content)?; - Ok(out_path) + Ok(content) } fn collect_headers(