Skip to content

Commit 5bf71cb

Browse files
rubysclaude
andcommitted
ruby-rbs-sys: support wasm32 targets
Compile the vendored RBS C parser to wasm with the WASI SDK's clang (via WASI_SDK_PATH), and generate the FFI bindings against the host target (--target=$HOST, layout_tests off) rather than the wasm target. Running bindgen against the wasm target is libclang-version-fragile: some libclangs drop all function declarations, others emit the forward-referenced node structs (rbs_namespace, rbs_type_name, rbs_ast_symbol, ...) as opaque, which breaks the generated layout assertions. The emitted #[repr(C)] declarations are layout-portable, so generating them for the host and compiling them for wasm32 is both correct and reliable. Mirrors ruby-prism-sys's wasm build support. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e1373f0 commit 5bf71cb

1 file changed

Lines changed: 48 additions & 6 deletions

File tree

rust/ruby-rbs-sys/build.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,46 @@ fn main() -> Result<(), Box<dyn Error>> {
1111
let include = vendor_rbs.join("include");
1212
let c_src = vendor_rbs.join("src");
1313

14-
build(&include, &c_src)?;
14+
let target = env::var("TARGET").unwrap_or_default();
15+
let is_wasm = target.contains("wasm32");
1516

16-
let bindings = generate_bindings(&include)?;
17+
build(&include, &c_src, is_wasm)?;
18+
19+
let bindings = generate_bindings(&include, is_wasm)?;
1720
write_bindings(&bindings)?;
1821

1922
Ok(())
2023
}
2124

22-
fn build(include_dir: &Path, src_dir: &Path) -> Result<(), Box<dyn Error>> {
25+
fn build(include_dir: &Path, src_dir: &Path, is_wasm: bool) -> Result<(), Box<dyn Error>> {
2326
let mut build = cc::Build::new();
2427

2528
build.include(include_dir);
2629

2730
// Suppress unused parameter warnings from C code
2831
build.flag_if_supported("-Wno-unused-parameter");
2932

33+
// Cross-compile the C parser to wasm with the WASI SDK's clang. Only the C
34+
// compile targets wasm; bindgen (below) still runs against the host, since
35+
// the resulting #[repr(C)] declarations are layout-portable.
36+
if is_wasm {
37+
let wasi_sdk = PathBuf::from(
38+
env::var("WASI_SDK_PATH").expect("WASI_SDK_PATH must be set for wasm builds"),
39+
);
40+
build.compiler(wasi_sdk.join("bin").join("clang"));
41+
42+
let sysroot = wasi_sdk.join("share").join("wasi-sysroot");
43+
build.flag(&format!("--sysroot={}", sysroot.display()));
44+
build.include(sysroot.join("include"));
45+
46+
println!(
47+
"cargo:rustc-link-search=native={}",
48+
sysroot.join("lib/wasm32-wasi").display()
49+
);
50+
build.define("_WASI_EMULATED_MMAN", "1");
51+
println!("cargo:rustc-link-lib=wasi-emulated-mman");
52+
}
53+
3054
build.files(source_files(src_dir)?);
3155
build.try_compile("rbs")?;
3256

@@ -64,8 +88,11 @@ fn source_files<P: AsRef<Path>>(root_dir: P) -> Result<Vec<String>, Box<dyn Erro
6488
Ok(files)
6589
}
6690

67-
fn generate_bindings(include_path: &Path) -> Result<bindgen::Bindings, Box<dyn Error>> {
68-
let bindings = bindgen::Builder::default()
91+
fn generate_bindings(
92+
include_path: &Path,
93+
is_wasm: bool,
94+
) -> Result<bindgen::Bindings, Box<dyn Error>> {
95+
let mut builder = bindgen::Builder::default()
6996
.header("wrapper.h")
7097
.clang_arg(format!("-I{}", include_path.display()))
7198
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
@@ -170,7 +197,22 @@ fn generate_bindings(include_path: &Path) -> Result<bindgen::Bindings, Box<dyn E
170197
// Constant pool functions
171198
.allowlist_function("rbs_constant_pool_free")
172199
.allowlist_function("rbs_constant_pool_id_to_constant")
173-
.allowlist_function("rbs_constant_pool_init")
200+
.allowlist_function("rbs_constant_pool_init");
201+
202+
if is_wasm {
203+
// Generate the FFI declarations for the HOST target rather than the wasm
204+
// target: host bindgen is reliable, whereas wasm-target bindgen is
205+
// libclang-fragile (drops functions / emits opaque structs). The emitted
206+
// #[repr(C)] structs are layout-portable, so they compile correctly for
207+
// wasm32. Drop the layout assertions, which would otherwise hardcode the
208+
// host sizes and fail when recompiled for wasm.
209+
let host = env::var("HOST").expect("HOST is not set");
210+
builder = builder
211+
.clang_arg(format!("--target={host}"))
212+
.layout_tests(false);
213+
}
214+
215+
let bindings = builder
174216
.generate()
175217
.map_err(|_| "Unable to generate rbs bindings")?;
176218

0 commit comments

Comments
 (0)