Skip to content

Commit a32f77e

Browse files
Pass wrapper.h to bindgen in-memory to avoid rebuild loop (#90)
Fixes #89. Switch the build script from writing `$OUT_DIR/wrapper.h` and calling `bindgen::Builder::header(path)` to calling `bindgen::Builder::header_contents("wrapper.h", &content)`. The wrapper exists only in memory, so there's no file for bindgen's `CargoCallbacks` to register via `rerun-if-changed`, and cargo no longer treats the build script as stale on every invocation. The walk over `{includedir}/mlir-c/` is unchanged; the helper is renamed from `generate_wrapper -> PathBuf` to `generate_wrapper_contents -> String`. `PathBuf` import dropped. ### Verification Two consecutive `cargo build`s on a downstream crate: - **Before:** ~12s, full recompile, every time. - **After:** ~12s on the first build, **0.07s** on the second. Full downstream test suite (289 tests) passes. *Assisted by Claude (Anthropic).* Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c592848 commit a32f77e

1 file changed

Lines changed: 16 additions & 8 deletions

File tree

build.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ use std::{
33
error::Error,
44
ffi::OsStr,
55
fs,
6-
path::{Path, PathBuf},
6+
path::Path,
77
process::{Command, Stdio, exit},
88
str,
99
};
1010

11+
/// Logical name passed to bindgen for the in-memory wrapper. Bindgen needs a
12+
/// `header_name` for diagnostics; this string never touches disk.
13+
const WRAPPER_NAME: &str = "wrapper.h";
14+
1115
const LLVM_MAJOR_VERSION: usize = 22;
1216

1317
fn main() {
@@ -107,10 +111,10 @@ fn run() -> Result<(), Box<dyn Error>> {
107111
}
108112

109113
let include_dir = llvm_config("--includedir", &link_mode)?;
110-
let wrapper_path = generate_wrapper(&include_dir)?;
114+
let wrapper_contents = generate_wrapper_contents(&include_dir)?;
111115

112116
bindgen::builder()
113-
.header(wrapper_path.to_str().unwrap())
117+
.header_contents(WRAPPER_NAME, &wrapper_contents)
114118
.clang_arg(format!("-I{include_dir}"))
115119
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
116120
.generate()
@@ -233,7 +237,14 @@ fn parse_shared_lib_name(name: &str) -> Option<&str> {
233237
}
234238
}
235239

236-
fn generate_wrapper(include_dir: &str) -> Result<PathBuf, Box<dyn Error>> {
240+
/// Walk `{includedir}/mlir-c/` and build an in-memory list of `#include`s
241+
/// covering every header. Returned as a `String` so it can be passed to
242+
/// bindgen via `header_contents`, avoiding any on-disk wrapper file. A
243+
/// disk-backed wrapper would have its mtime rewritten on every build,
244+
/// which interacts badly with bindgen's `CargoCallbacks::rerun-if-changed`
245+
/// tracking and forces cargo to rebuild this crate (and everything that
246+
/// depends on it) on every invocation.
247+
fn generate_wrapper_contents(include_dir: &str) -> Result<String, Box<dyn Error>> {
237248
let mlir_c_dir = Path::new(include_dir).join("mlir-c");
238249
let mut headers = Vec::new();
239250
collect_headers(&mlir_c_dir, &mlir_c_dir, &mut headers)?;
@@ -243,10 +254,7 @@ fn generate_wrapper(include_dir: &str) -> Result<PathBuf, Box<dyn Error>> {
243254
for header in &headers {
244255
content.push_str(&format!("#include <mlir-c/{header}>\n"));
245256
}
246-
247-
let out_path = PathBuf::from(env::var("OUT_DIR")?).join("wrapper.h");
248-
fs::write(&out_path, content)?;
249-
Ok(out_path)
257+
Ok(content)
250258
}
251259

252260
fn collect_headers(

0 commit comments

Comments
 (0)