tree-sitter: support building grammars to WebAssembly

Build grammars for a WASI host platform through the existing grammar builder so pkgsCross.wasi32.tree-sitter.builtGrammars produces parser.wasm.

This keeps native grammar derivations unchanged and links parser.wasm directly with the WASI C toolchain while rejecting C++ external scanners.
This commit is contained in:
Austin Horstman
2026-06-24 11:51:27 -05:00
parent 1fb5e96c3b
commit d2ff8a9dbc
3 changed files with 76 additions and 17 deletions
@@ -109,6 +109,18 @@ This includes build-related flags and metadata.
}
```
## Building WebAssembly Parsers
`buildGrammar` builds a native `$out/parser`.
To build grammars as WebAssembly instead, use the `wasi32` cross package set, which installs `$out/parser.wasm`:
```nix
pkgsCross.wasi32.tree-sitter.builtGrammars.tree-sitter-nix
```
The Wasm build compiles `parser.c` and a C `scanner.c`.
Grammars with C++ external scanners are rejected; use the native `buildGrammar` for those.
## Updating
All grammar sources have a default update script defined.
@@ -16,6 +16,11 @@
...
}@args:
let
isWasi = stdenv.hostPlatform.isWasi;
parserOutput = if isWasi then "parser.wasm" else "parser";
exportSymbol = "tree_sitter_${lib.replaceStrings [ "-" ] [ "_" ] language}";
in
stdenv.mkDerivation (
{
pname = "tree-sitter-${language}";
@@ -32,11 +37,18 @@ stdenv.mkDerivation (
CFLAGS = [
"-Isrc"
"-O2"
# Match upstream `tree-sitter build --wasm`
(if isWasi then "-Os" else "-O2")
]
++ lib.optionals isWasi [
"-fvisibility=hidden"
];
CXXFLAGS = [
"-Isrc"
"-O2"
(if isWasi then "-Os" else "-O2")
]
++ lib.optionals isWasi [
"-fvisibility=hidden"
];
stripDebugList = [ "parser" ];
@@ -90,25 +102,44 @@ stdenv.mkDerivation (
tree-sitter generate
'';
# When both scanner.{c,cc} exist, we should not link both since they may be the same but in
# different languages. Just randomly prefer C++ if that happens.
buildPhase = ''
runHook preBuild
if [[ -e src/scanner.cc ]]; then
$CXX -fPIC -c src/scanner.cc -o scanner.o $CXXFLAGS
elif [[ -e src/scanner.c ]]; then
$CC -fPIC -c src/scanner.c -o scanner.o $CFLAGS
fi
$CC -fPIC -c src/parser.c -o parser.o $CFLAGS
rm -rf parser
$CXX -shared -o parser *.o
runHook postBuild
'';
buildPhase =
if isWasi then
''
runHook preBuild
if [[ -e src/scanner.cc || -e src/scanner.cpp ]]; then
nixErrorLog "tree-sitter wasm grammars only support C external scanners"
exit 1
fi
if [[ -e src/scanner.c ]]; then
$CC -fPIC -c src/scanner.c -o scanner.o $CFLAGS
fi
$CC -fPIC -c src/parser.c -o parser.o $CFLAGS
rm -rf parser.wasm
$CC -shared -o parser.wasm *.o \
-Wl,--export=${exportSymbol} \
-Wl,--allow-undefined \
-Wl,--no-entry \
-nostdlib
runHook postBuild
''
else
''
runHook preBuild
if [[ -e src/scanner.cc ]]; then
$CXX -fPIC -c src/scanner.cc -o scanner.o $CXXFLAGS
elif [[ -e src/scanner.c ]]; then
$CC -fPIC -c src/scanner.c -o scanner.o $CFLAGS
fi
$CC -fPIC -c src/parser.c -o parser.o $CFLAGS
rm -rf parser
$CXX -shared -o parser *.o
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir $out
mv parser $out/
mv ${parserOutput} $out/
if [[ -f tree-sitter.json ]]; then
cp tree-sitter.json $out/
fi
+16
View File
@@ -10,6 +10,7 @@
nix-update-script,
which,
rustPlatform,
runCommand,
emscripten,
openssl,
pkg-config,
@@ -112,6 +113,8 @@ let
allGrammars = lib.filter (p: !(p.meta.broken or false)) (lib.attrValues builtGrammars);
isWasi = stdenv.hostPlatform.isWasi;
in
rustPlatform.buildRustPackage (finalAttrs: {
pname = "tree-sitter";
@@ -246,6 +249,19 @@ rustPlatform.buildRustPackage (finalAttrs: {
tests = {
# make sure all grammars build
builtGrammars = lib.recurseIntoAttrs builtGrammars;
}
// lib.optionalAttrs isWasi {
wasmGrammar =
let
grammar = builtGrammars.tree-sitter-nix;
in
runCommand "tree-sitter-wasm-grammar-test" { } ''
test -f ${grammar}/parser.wasm
# WebAssembly binaries start with "\0asm".
test "$(od -An -tx1 -N4 ${grammar}/parser.wasm | tr -d ' \n')" = "0061736d"
test ! -e ${grammar}/parser
touch $out
'';
};
};