tree-sitter.tree-sitter/lib/binding_rust/build.rs
Max Brunsfeld 0e2af0d8d1
Allow using Tree-sitter rust lib in multi-threaded web apps compiled to wasm32-unknown-unknown (#5851)
* feat(wasm): make syntax trees sendable

* test(wasm): transfer trees across workers

* test(wasm): use JSON grammar for tree transfer

* test(wasm): edit trees across workers

* test(wasm): share dlmalloc with tree-sitter

* test(wasm): simplify worker tree exchange

* test(wasm): drive tree exchange from Rust

* test(wasm): split sendable-tree xtask

* test(wasm): generalize Rust web fixture

* test(wasm): exercise parallel Rust tree access

* fix(wasm): use Rust global allocator for C core

* test(wasm): use default Rust allocator

* feat(wasm): support external scanners in Rust web apps

* Simplify example further, add a readme

* Regenerate wasm-stdlib

* Fix wasm_stdlib check script

* Vendor the Wasm standard library subset

* Test Unicode Ruby scanner behavior in Wasm

* Make Wasm tree languages instance-aware

* Test multi-threaded use of queries in wasm32-unknown

* Refactor reference-counted language storage

* Check ABI version compat before loading rest of language

* 🎨 Remove redundant #ifdef block

* Reject unsupported Rust Wasm builds on 0.26
2026-08-14 17:05:42 -07:00

120 lines
3.5 KiB
Rust

use std::{env, fs, path::PathBuf};
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let target = env::var("TARGET").unwrap();
#[cfg(feature = "bindgen")]
generate_bindings(&out_dir);
fs::copy(
"src/wasm-stdlib/imports.txt",
out_dir.join("stdlib-symbols.txt"),
)
.unwrap();
let mut config = cc::Build::new();
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_WASM");
if env::var("CARGO_FEATURE_WASM").is_ok() {
config
.define("TREE_SITTER_FEATURE_WASM", "")
.define("static_assert(...)", "")
.include(env::var("DEP_WASMTIME_C_API_INCLUDE").unwrap());
}
let manifest_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let include_path = manifest_path.join("include");
let src_path = manifest_path.join("src");
let wasm_path = src_path.join("wasm");
if target.starts_with("wasm32-unknown") {
configure_wasm_build(&mut config);
}
for entry in fs::read_dir(&src_path).unwrap() {
let entry = entry.unwrap();
let path = src_path.join(entry.file_name());
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
}
config
.std("c11")
.flag_if_supported("-fvisibility=hidden")
.flag_if_supported("-Wshadow")
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-incompatible-pointer-types")
.include(&src_path)
.include(&wasm_path)
.include(&include_path)
.define("_POSIX_C_SOURCE", "200112L")
.define("_DEFAULT_SOURCE", None)
.define("_BSD_SOURCE", None)
.define("_DARWIN_C_SOURCE", None)
.warnings(false)
.file(src_path.join("lib.c"))
.compile("tree-sitter");
println!("cargo:include={}", include_path.display());
}
fn configure_wasm_build(config: &mut cc::Build) {
let Ok(wasm_headers) = env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else {
panic!(
"Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate"
);
};
config
.define("TREE_SITTER_WASM_STDLIB", "")
.include(&wasm_headers);
}
#[cfg(feature = "bindgen")]
fn generate_bindings(out_dir: &std::path::Path) {
use std::str::FromStr;
use bindgen::RustTarget;
const HEADER_PATH: &str = "include/tree_sitter/api.h";
println!("cargo:rerun-if-changed={HEADER_PATH}");
let no_copy = [
"TSInput",
"TSLanguage",
"TSLogger",
"TSLookaheadIterator",
"TSParser",
"TSTree",
"TSQuery",
"TSQueryCursor",
"TSQueryCapture",
"TSQueryMatch",
"TSQueryPredicateStep",
];
let rust_version = env!("CARGO_PKG_RUST_VERSION");
let bindings = bindgen::Builder::default()
.header(HEADER_PATH)
.layout_tests(false)
.allowlist_type("^TS.*")
.allowlist_function("^ts_.*")
.allowlist_var("^TREE_SITTER.*")
.no_copy(no_copy.join("|"))
.prepend_enum_name(false)
.use_core()
.clang_arg("-D TREE_SITTER_FEATURE_WASM")
.rust_target(RustTarget::from_str(rust_version).unwrap())
.generate()
.expect("Failed to generate bindings");
let bindings_rs = out_dir.join("bindings.rs");
bindings.write_to_file(&bindings_rs).unwrap_or_else(|_| {
panic!(
"Failed to write bindings into path: {}",
bindings_rs.display()
)
});
}