This is not breaking for consumers of the web bindings, nor the playground as both filenames will be kept in the .github.io repo
258 lines
7.6 KiB
Rust
258 lines
7.6 KiB
Rust
use std::{
|
|
collections::HashSet,
|
|
ffi::{OsStr, OsString},
|
|
fmt::Write,
|
|
fs,
|
|
path::PathBuf,
|
|
process::Command,
|
|
time::Duration,
|
|
};
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use notify::{
|
|
event::{AccessKind, AccessMode},
|
|
EventKind, RecursiveMode,
|
|
};
|
|
use notify_debouncer_full::new_debouncer;
|
|
|
|
use crate::{bail_on_err, watch_wasm, BuildWasm, EMSCRIPTEN_TAG};
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
enum EmccSource {
|
|
Native,
|
|
Docker,
|
|
Podman,
|
|
}
|
|
|
|
const EXPORTED_RUNTIME_METHODS: [&str; 8] = [
|
|
"AsciiToString",
|
|
"stringToUTF8",
|
|
"UTF8ToString",
|
|
"lengthBytesUTF8",
|
|
"stringToUTF16",
|
|
"loadWebAssemblyModule",
|
|
"getValue",
|
|
"setValue",
|
|
];
|
|
|
|
pub fn run_wasm(args: &BuildWasm) -> Result<()> {
|
|
let mut emscripten_flags = vec!["-O3", "--minify", "0"];
|
|
|
|
if args.debug {
|
|
emscripten_flags.extend(["-s", "ASSERTIONS=1", "-s", "SAFE_HEAP=1", "-O0", "-g"]);
|
|
}
|
|
|
|
if args.verbose {
|
|
emscripten_flags.extend(["-s", "VERBOSE=1", "-v"]);
|
|
}
|
|
|
|
let emcc_name = if cfg!(windows) { "emcc.bat" } else { "emcc" };
|
|
|
|
// Order of preference: emscripten > docker > podman > error
|
|
let source = if !args.docker && Command::new(emcc_name).output().is_ok() {
|
|
EmccSource::Native
|
|
} else if Command::new("docker")
|
|
.output()
|
|
.is_ok_and(|out| out.status.success())
|
|
{
|
|
EmccSource::Docker
|
|
} else if Command::new("podman")
|
|
.arg("--version")
|
|
.output()
|
|
.is_ok_and(|out| out.status.success())
|
|
{
|
|
EmccSource::Podman
|
|
} else {
|
|
return Err(anyhow!(
|
|
"You must have either emcc, docker, or podman on your PATH to run this command"
|
|
));
|
|
};
|
|
|
|
let mut command = match source {
|
|
EmccSource::Native => Command::new(emcc_name),
|
|
EmccSource::Docker | EmccSource::Podman => {
|
|
let mut command = match source {
|
|
EmccSource::Docker => Command::new("docker"),
|
|
EmccSource::Podman => Command::new("podman"),
|
|
_ => unreachable!(),
|
|
};
|
|
command.args(["run", "--rm"]);
|
|
|
|
// Mount the root directory as a volume, which is the repo root
|
|
let mut volume_string = OsString::from(std::env::current_dir().unwrap());
|
|
volume_string.push(":/src:Z");
|
|
command.args([OsStr::new("--volume"), &volume_string]);
|
|
|
|
// In case `docker` is an alias to `podman`, ensure that podman
|
|
// mounts the current directory as writable by the container
|
|
// user which has the same uid as the host user. Setting the
|
|
// podman-specific variable is more reliable than attempting to
|
|
// detect whether `docker` is an alias for `podman`.
|
|
// see https://docs.podman.io/en/latest/markdown/podman-run.1.html#userns-mode
|
|
command.env("PODMAN_USERNS", "keep-id");
|
|
|
|
// Get the current user id so that files created in the docker container will have
|
|
// the same owner.
|
|
#[cfg(unix)]
|
|
{
|
|
#[link(name = "c")]
|
|
extern "C" {
|
|
fn getuid() -> u32;
|
|
}
|
|
// don't need to set user for podman since PODMAN_USERNS=keep-id is already set
|
|
if source == EmccSource::Docker {
|
|
let user_id = unsafe { getuid() };
|
|
command.args(["--user", &user_id.to_string()]);
|
|
}
|
|
};
|
|
|
|
// Run `emcc` in a container using the `emscripten-slim` image
|
|
command.args([EMSCRIPTEN_TAG, "emcc"]);
|
|
command
|
|
}
|
|
};
|
|
|
|
fs::create_dir_all("target/scratch").unwrap();
|
|
|
|
let exported_functions = format!(
|
|
"{}{}",
|
|
fs::read_to_string("lib/src/wasm/stdlib-symbols.txt")?,
|
|
fs::read_to_string("lib/binding_web/lib/exports.txt")?
|
|
)
|
|
.replace('"', "")
|
|
.lines()
|
|
.fold(String::new(), |mut output, line| {
|
|
let _ = write!(output, "_{line}");
|
|
output
|
|
})
|
|
.trim_end_matches(',')
|
|
.to_string();
|
|
|
|
let exported_functions = format!("EXPORTED_FUNCTIONS={exported_functions}");
|
|
let exported_runtime_methods = format!(
|
|
"EXPORTED_RUNTIME_METHODS={}",
|
|
EXPORTED_RUNTIME_METHODS.join(",")
|
|
);
|
|
|
|
// Clean up old files from prior runs
|
|
for file in [
|
|
"web-tree-sitter.mjs",
|
|
"web-tree-sitter.cjs",
|
|
"web-tree-sitter.wasm",
|
|
"web-tree-sitter.wasm.map",
|
|
] {
|
|
fs::remove_file(PathBuf::from("lib/binding_web/lib").join(file)).ok();
|
|
}
|
|
|
|
if !args.cjs {
|
|
emscripten_flags.extend(["-s", "EXPORT_ES6=1"]);
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
emscripten_flags.extend([
|
|
"-gsource-map",
|
|
"--source-map-base", ".",
|
|
"-fno-exceptions",
|
|
"-std=c11",
|
|
"-s", "WASM=1",
|
|
"-s", "MODULARIZE=1",
|
|
"-s", "INITIAL_MEMORY=33554432",
|
|
"-s", "ALLOW_MEMORY_GROWTH=1",
|
|
"-s", "SUPPORT_BIG_ENDIAN=1",
|
|
"-s", "MAIN_MODULE=2",
|
|
"-s", "FILESYSTEM=0",
|
|
"-s", "NODEJS_CATCH_EXIT=0",
|
|
"-s", "NODEJS_CATCH_REJECTION=0",
|
|
"-s", &exported_functions,
|
|
"-s", &exported_runtime_methods,
|
|
"-D", "fprintf(...)=",
|
|
"-D", "NDEBUG=",
|
|
"-D", "_POSIX_C_SOURCE=200112L",
|
|
"-D", "_DEFAULT_SOURCE=",
|
|
"-I", "lib/src",
|
|
"-I", "lib/include",
|
|
"--js-library", "lib/binding_web/lib/imports.js",
|
|
"--pre-js", "lib/binding_web/lib/prefix.js",
|
|
"-o", if args.cjs { "lib/binding_web/lib/web-tree-sitter.cjs" } else { "lib/binding_web/lib/web-tree-sitter.mjs" },
|
|
"lib/src/lib.c",
|
|
"lib/binding_web/lib/tree-sitter.c",
|
|
]);
|
|
if args.emit_tsd {
|
|
emscripten_flags.extend(["--emit-tsd", "web-tree-sitter.d.ts"]);
|
|
}
|
|
|
|
let command = command.args(&emscripten_flags);
|
|
|
|
if args.watch {
|
|
watch_wasm!(|| build_wasm(command));
|
|
} else {
|
|
build_wasm(command)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn build_wasm(cmd: &mut Command) -> Result<()> {
|
|
bail_on_err(
|
|
&cmd.spawn()?.wait_with_output()?,
|
|
"Failed to compile the Tree-sitter WASM library",
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn run_wasm_stdlib() -> Result<()> {
|
|
let export_flags = include_str!("../../lib/src/wasm/stdlib-symbols.txt")
|
|
.lines()
|
|
.map(|line| format!("-Wl,--export={}", &line[1..line.len() - 2]))
|
|
.collect::<Vec<String>>();
|
|
|
|
let mut command = Command::new("docker");
|
|
|
|
let output = command
|
|
.args([
|
|
"run",
|
|
"--rm",
|
|
"-v",
|
|
format!("{}:/src", std::env::current_dir().unwrap().display()).as_str(),
|
|
"-w",
|
|
"/src",
|
|
"ghcr.io/webassembly/wasi-sdk",
|
|
"/opt/wasi-sdk/bin/clang",
|
|
"-o",
|
|
"stdlib.wasm",
|
|
"-Os",
|
|
"-fPIC",
|
|
"-Wl,--no-entry",
|
|
"-Wl,--stack-first",
|
|
"-Wl,-z",
|
|
"-Wl,stack-size=65536",
|
|
"-Wl,--import-undefined",
|
|
"-Wl,--import-memory",
|
|
"-Wl,--import-table",
|
|
"-Wl,--strip-debug",
|
|
"-Wl,--export=reset_heap",
|
|
"-Wl,--export=__wasm_call_ctors",
|
|
"-Wl,--export=__stack_pointer",
|
|
])
|
|
.args(export_flags)
|
|
.arg("lib/src/wasm/stdlib.c")
|
|
.output()?;
|
|
|
|
bail_on_err(&output, "Failed to compile the Tree-sitter WASM stdlib")?;
|
|
|
|
let xxd = Command::new("xxd")
|
|
.args(["-C", "-i", "stdlib.wasm"])
|
|
.output()?;
|
|
|
|
bail_on_err(
|
|
&xxd,
|
|
"Failed to run xxd on the compiled Tree-sitter WASM stdlib",
|
|
)?;
|
|
|
|
fs::write("lib/src/wasm/wasm-stdlib.h", xxd.stdout)?;
|
|
|
|
fs::rename("stdlib.wasm", "target/stdlib.wasm")?;
|
|
|
|
Ok(())
|
|
}
|