diff --git a/Cargo.lock b/Cargo.lock index 3dbc7a216..3ca5cf0ec 100644 Binary files a/Cargo.lock and b/Cargo.lock differ diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 774fd8e81..67cef0c87 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -28,3 +28,5 @@ semver.workspace = true serde.workspace = true serde_json.workspace = true ureq = "2.12.1" +notify = "7.0.0" +notify-debouncer-full = "0.4.0" diff --git a/xtask/src/build_wasm.rs b/xtask/src/build_wasm.rs index 6bf2edddd..77a198b99 100644 --- a/xtask/src/build_wasm.rs +++ b/xtask/src/build_wasm.rs @@ -1,13 +1,21 @@ 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, BuildWasm, EMSCRIPTEN_TAG}; +use crate::{bail_on_err, watch_wasm, BuildWasm, EMSCRIPTEN_TAG}; #[derive(PartialEq, Eq)] enum EmccSource { @@ -95,9 +103,10 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { fs::create_dir_all("target/scratch").unwrap(); - let exported_functions = concat!( - include_str!("../../lib/src/wasm/stdlib-symbols.txt"), - include_str!("../../lib/binding_web/exports.txt") + let exported_functions = format!( + "{}{}", + fs::read_to_string("lib/src/wasm/stdlib-symbols.txt")?, + fs::read_to_string("lib/binding_web/exports.txt")? ) .replace('"', "") .lines() @@ -159,9 +168,20 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> { "-o", "target/scratch/tree-sitter.js", ]); + 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( - &command.args(emscripten_flags).spawn()?.wait_with_output()?, + &cmd.spawn()?.wait_with_output()?, "Failed to compile the Tree-sitter WASM library", )?; diff --git a/xtask/src/check_wasm_exports.rs b/xtask/src/check_wasm_exports.rs index 3d0cab407..4bc9e9fbc 100644 --- a/xtask/src/check_wasm_exports.rs +++ b/xtask/src/check_wasm_exports.rs @@ -1,12 +1,19 @@ use std::{ collections::HashSet, io::BufRead, + path::PathBuf, process::{Command, Stdio}, + time::Duration, }; use anyhow::{anyhow, Result}; +use notify::{ + event::{AccessKind, AccessMode}, + EventKind, RecursiveMode, +}; +use notify_debouncer_full::new_debouncer; -use crate::{bail_on_err, build_wasm::run_wasm, BuildWasm}; +use crate::{bail_on_err, build_wasm::run_wasm, watch_wasm, BuildWasm, CheckWasmExports}; const EXCLUDES: [&str; 28] = [ // Unneeded because the JS side has its own way of implementing it @@ -44,15 +51,26 @@ const EXCLUDES: [&str; 28] = [ "ts_query_cursor_timeout_micros", ]; -pub fn run() -> Result<()> { +pub fn run(args: &CheckWasmExports) -> Result<()> { + if args.watch { + watch_wasm!(check_wasm_exports); + } else { + check_wasm_exports()?; + } + + Ok(()) +} + +fn check_wasm_exports() -> Result<()> { // Build the wasm module with debug symbols for wasm-objdump run_wasm(&BuildWasm { debug: true, verbose: false, docker: false, + watch: false, })?; - let mut wasm_exports = include_str!("../../lib/binding_web/exports.txt") + let mut wasm_exports = std::fs::read_to_string("lib/binding_web/exports.txt")? .lines() .map(|s| s.replace("_wasm", "").replace("byte", "index")) // remove leading and trailing quotes, trailing comma diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a1333d65f..9d8069dfb 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -30,7 +30,7 @@ enum Commands { /// Bumps the version of the workspace. BumpVersion(BumpVersion), /// Checks that WASM exports are synced. - CheckWasmExports, + CheckWasmExports(CheckWasmExports), /// Runs `cargo clippy`. Clippy(Clippy), /// Fetches emscripten. @@ -82,6 +82,9 @@ struct BuildWasm { /// Run emscripten with verbose output. #[arg(long, short)] verbose: bool, + /// Rebuild when relevant files are changed. + #[arg(long, short)] + watch: bool, } #[derive(Args)] @@ -91,6 +94,13 @@ struct BumpVersion { version: Option, } +#[derive(Args)] +struct CheckWasmExports { + /// Recheck when relevant files are changed. + #[arg(long, short)] + watch: bool, +} + #[derive(Args)] struct Clippy { /// Automatically apply lint suggestions (`clippy --fix`). @@ -207,7 +217,7 @@ fn run() -> Result<()> { Commands::BuildWasm(build_wasm_options) => build_wasm::run_wasm(&build_wasm_options)?, Commands::BuildWasmStdlib => build_wasm::run_wasm_stdlib()?, Commands::BumpVersion(bump_options) => bump::run(bump_options)?, - Commands::CheckWasmExports => check_wasm_exports::run()?, + Commands::CheckWasmExports(check_options) => check_wasm_exports::run(&check_options)?, Commands::Clippy(clippy_options) => clippy::run(&clippy_options)?, Commands::FetchEmscripten => fetch::run_emscripten()?, Commands::FetchFixtures => fetch::run_fixtures()?, @@ -289,3 +299,57 @@ pub fn create_commit(repo: &Repository, msg: &str, paths: &[&str]) -> Result { + if let Err(e) = $watch_fn() { + eprintln!("{e}"); + } + + let watch_files = [ + "binding.c", + "binding.js", + "exports.txt", + "imports.js", + "prefix.js", + "suffix.js", + ] + .iter() + .map(PathBuf::from) + .collect::>(); + let (tx, rx) = std::sync::mpsc::channel(); + let mut debouncer = new_debouncer(Duration::from_secs(1), None, tx)?; + debouncer.watch("lib/binding_web", RecursiveMode::NonRecursive)?; + + for result in rx { + match result { + Ok(events) => { + for event in events { + if event.kind == EventKind::Access(AccessKind::Close(AccessMode::Write)) + && event + .paths + .iter() + .filter_map(|p| p.file_name()) + .any(|p| watch_files.contains(&PathBuf::from(p))) + { + if let Err(e) = $watch_fn() { + eprintln!("{e}"); + } + } + } + } + Err(errors) => { + return Err(anyhow!( + "{}", + errors + .into_iter() + .map(|e| e.to_string()) + .collect::>() + .join("\n") + )); + } + } + } + }; +}