From 56b7649cf13f5d6160746897a4d2fdc486427931 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Fri, 15 Sep 2023 03:28:55 +0400 Subject: [PATCH 1/4] Watch mode clean up --- jscomp/bsb_exe/rescript_main.ml | 30 +- rescript | 748 ++++++++++++++++---------------- 2 files changed, 394 insertions(+), 384 deletions(-) diff --git a/jscomp/bsb_exe/rescript_main.ml b/jscomp/bsb_exe/rescript_main.ml index 8d041534f4..aa32653889 100644 --- a/jscomp/bsb_exe/rescript_main.ml +++ b/jscomp/bsb_exe/rescript_main.ml @@ -26,7 +26,7 @@ let () = Bsb_log.setup () let separator = "--" -let watch_mode = ref false +let no_deps_mode = ref false let do_install = ref false @@ -84,13 +84,13 @@ let ninja_command_exit (type t) (ninja_args : string array) : t = ninja -C _build *) let clean_usage = - "Usage: rescript.exe clean \n\n\ - `rescript clean` only cleans the current project\n" + "Usage: rescript clean \n\n\ + `rescript clean` cleans build artifacts. It is useful for edge-case reasons in case you get into a stale build\n" let build_usage = - "Usage: rescript.exe build -- \n\n\ - `rescript build` implicitly builds dependencies if they aren't built\n\n\ - `rescript.exe -- -h` for Ninja options (internal usage only; unstable)\n" + "Usage: rescript build -- \n\n\ + `rescript build` builds the project with dependencies\n\n\ + `rescript -- -h` for Ninja options (internal usage only; unstable)\n" let install_target () = let ( // ) = Filename.concat in @@ -114,7 +114,11 @@ let build_subcommand ~start argv argv_len = ?finish:(if i < 0 then None else Some i) ~argv [| - ("-w", unit_set_spec watch_mode, "Watch mode"); + ("-w", unit_set_spec (ref false), "Watch mode"); + ( "-ws", + string_set_spec (ref ""), + "[host]:port set up host & port for WebSocket build notifications" ); + ("-verbose", call_spec Bsb_log.verbose, "Set the output to be verbose"); ("-with-deps", unit_set_spec (ref true), "*deprecated* This is the default behavior now. This option will be removed in a future release"); ( "-install", unit_set_spec do_install, @@ -122,15 +126,12 @@ let build_subcommand ~start argv argv_len = (* This should be put in a subcommand previously it works with the implication `bsb && bsb -install` *) - ( "-ws", - string_set_spec (ref ""), - "[host]:port set up host & port for WebSocket build notifications" ); ( "-regen", unit_set_spec force_regenerate, "*internal* \n\ Always regenerate build.ninja no matter bsconfig.json is changed or \ not" ); - ("-verbose", call_spec Bsb_log.verbose, "Set the output to be verbose"); + ("-no-deps", unit_set_spec no_deps_mode, "*internal* Needed for watcher to build without dependencies on file change"); |] failed_annon; @@ -143,18 +144,17 @@ let build_subcommand ~start argv argv_len = let config_opt = Bsb_ninja_regen.regenerate_ninja ~package_kind:Toplevel ~per_proj_dir:Bsb_global_paths.cwd ~forced:!force_regenerate in - Bsb_world.make_world_deps Bsb_global_paths.cwd config_opt ninja_args; + if not !no_deps_mode then Bsb_world.make_world_deps Bsb_global_paths.cwd config_opt ninja_args; if !do_install then install_target (); - if !watch_mode then exit 0 (* let the watcher do the build*) - else ninja_command_exit ninja_args + ninja_command_exit ninja_args let clean_subcommand ~start argv = Bsb_arg.parse_exn ~usage:clean_usage ~start ~argv [| + ("-verbose", call_spec Bsb_log.verbose, "Set the output to be verbose"); ( "-with-deps", unit_set_spec (ref true), "*deprecated* This is the default behavior now. This option will be removed in a future release" ); - ("-verbose", call_spec Bsb_log.verbose, "Set the output to be verbose"); |] failed_annon; Bsb_clean.clean_bs_deps Bsb_global_paths.cwd; diff --git a/rescript b/rescript index ea4984d3d3..57b639b660 100755 --- a/rescript +++ b/rescript @@ -15,20 +15,13 @@ var bsc_exe = require("./scripts/bin_path").bsc_exe; var rescript_exe = require("./scripts/bin_path").rescript_exe; var bsconfig = "bsconfig.json"; -var LAST_BUILD_START = 0; -var LAST_FIRED_EVENT = 0; -/** - * @type {[string,string][]} - */ -var reasonsToRebuild = [["proj", "started"]]; - var LAST_SUCCESS_BUILD_STAMP = 0; var cwd = process.cwd(); var lockFileName = path.join(cwd, ".bsb.lock"); process.env.BSB_PROJECT_ROOT = cwd; -var error_is_tty = process.stderr.isTTY; -var std_is_tty = process.stdout.isTTY; +const isTtyError = process.stderr.isTTY; +const isTtyStd = process.stdout.isTTY; // If the project uses gentype and uses custom file extension // via generatedFileExtension, ignore them in watch mode @@ -41,31 +34,17 @@ if (fs.existsSync(bsConfigFile)) { } } -// All clients of type MiniWebSocket -/** - * @type {any[]} - */ -var wsClients = []; -var isWatchMode = false; -var verbose = false; -/** - * @type {string | undefined} - */ -var postBuild = undefined; -var withWebSocket = false; -var webSocketHost = "localhost"; -var webSocketPort = 9999; +let verbose = false; /** * @time{[number,number]} */ -var startTime; +let startTime; function updateStartTime() { startTime = process.hrtime(); - return ""; } function updateFinishTime() { - var diff = process.hrtime(startTime); + const diff = process.hrtime(startTime); return diff[0] * 1e9 + diff[1]; } @@ -92,51 +71,6 @@ function dlog(str) { } } -function notifyClients() { - wsClients = wsClients.filter(x => !x.closed && !x.socket.destroyed); - var wsClientsLen = wsClients.length; - dlog(`Alive sockets number: ${wsClientsLen}`); - var data = '{"LAST_SUCCESS_BUILD_STAMP":' + LAST_SUCCESS_BUILD_STAMP + "}"; - for (var i = 0; i < wsClientsLen; ++i) { - // in reverse order, the last pushed get notified earlier - var client = wsClients[wsClientsLen - i - 1]; - if (!client.closed) { - client.sendText(data); - } - } -} - -function setUpWebSocket() { - var WebSocket = require("./lib/minisocket.js").MiniWebSocket; - var id = setInterval(notifyClients, 3000); - require("http") - .createServer() - .on("upgrade", function (req, socket, upgradeHead) { - dlog("connection opened"); - var ws = new WebSocket(req, socket, upgradeHead); - socket.on("error", function (err) { - dlog(`Socket Error ${err}`); - }); - wsClients.push(ws); - }) - .on("error", function (err) { - // @ts-ignore - if (err !== undefined && err.code === "EADDRINUSE") { - var error = std_is_tty ? `\x1b[1;31mERROR:\x1b[0m` : `ERROR:`; - console.error(`${error} The websocket port number ${webSocketPort} is in use. -Please pick a different one using the \`-ws [host:]port\` flag from bsb.`); - } else { - console.error(err); - } - process.exit(2); - }) - .listen(webSocketPort, webSocketHost); -} - -/** - * @type {string[]} - */ -var delegatedArgs = []; var process_argv = process.argv; if (process.env.NINJA_ANSI_FORCED === undefined) { @@ -204,108 +138,53 @@ function onUncaughtException(err) { releaseBuild(); process.exit(1); } + function exitProcess() { releaseBuild(); process.exit(0); } -process.on("uncaughtException", onUncaughtException); - -// OS signal handlers -// Ctrl+C -process.on("SIGINT", exitProcess); -// kill pid -process.on("SIGUSR1", exitProcess); -process.on("SIGUSR2", exitProcess); -process.on("SIGTERM", exitProcess); -process.on("SIGHUP", exitProcess); - -var maybeSubcommand = process_argv[2]; -if ( - maybeSubcommand !== undefined && - maybeSubcommand !== "build" && - maybeSubcommand !== "clean" && - maybeSubcommand !== "info" - // delegate to native -) { - switch (maybeSubcommand) { - case "format": - require("./scripts/rescript_format.js").main( - process.argv.slice(3), - rescript_exe, - bsc_exe - ); - break; - case "dump": - require("./scripts/rescript_dump.js").main( - process.argv.slice(3), - rescript_exe, - bsc_exe - ); - break; - case "dump": - require("./scripts/rescript_dump.js").main( - process.argv.slice(3), - rescript_exe, - bsc_exe - ); - break; - case "convert": - // Todo - require("./scripts/rescript_convert.js").main( - process.argv.slice(3), - rescript_exe, - bsc_exe - ); - break; - case "-h": - case "-help": - case "help": - help(); - break; - case "-v": - case "-version": - console.log(require("./package.json").version); - break; - default: - console.error(`Unknown subcommand or flags: ${maybeSubcommand}`); - help(); - process.exit(2); +/** + * @param {number} [code] + */ +function logFinishCompiling(code) { + let log = `>>>> Finish compiling`; + if (code) { + log = log + " (exit: " + code + ")"; } -} else { - var delegatedArgs = process_argv.slice(2); - var isWatchMode = delegatedArgs.includes("-w"); - var wsParamIndex = delegatedArgs.indexOf("-ws"); - if (wsParamIndex > -1) { - var hostAndPortNumber = (delegatedArgs[wsParamIndex + 1] || "").split(":"); - /** - * @type {number} - */ - var portNumber; - if (hostAndPortNumber.length === 1) { - portNumber = parseInt(hostAndPortNumber[0]); - } else { - webSocketHost = hostAndPortNumber[0]; - portNumber = parseInt(hostAndPortNumber[1]); - } - if (!isNaN(portNumber)) { - webSocketPort = portNumber; - } - withWebSocket = true; - dlog(`WebSocket host & port number: ${webSocketHost}:${webSocketPort}`); + if (isTtyStd) { + log = "\x1b[36m" + log + "\x1b[0m"; } + if (code) { + console.log(log); + } else { + console.log(log, Math.floor(updateFinishTime() / 1e6), "mseconds"); + } +} - verbose = delegatedArgs.includes("-verbose"); +function logStartCompiling() { + updateStartTime(); + let log = `>>>> Start compiling`; + if (isTtyStd) { + log = "\x1b[36m" + log + "\x1b[0m"; + } + console.log(log); +} + +/** + * @param {Array} args + * @param {() => void} [maybeOnSuccess] + */ +function delegateCommand(args, maybeOnSuccess) { /** * @type {child_process.ChildProcess} */ var p; if (acquireBuild()) { try { - p = child_process.spawn(rescript_exe, delegatedArgs, { + p = child_process.spawn(rescript_exe, args, { stdio: "inherit", }); - LAST_BUILD_START = Date.now(); } catch (e) { if (e.code === "ENOENT") { // when bsb is actually not found @@ -318,251 +197,382 @@ if ( // 'error' if the child failed to spawn. p.on("close", code => { releaseBuild(); - if (code !== 0) { - process.exit(code); - } else if (isWatchMode) { - startWatchMode(withWebSocket); + if (maybeOnSuccess && !code) { + maybeOnSuccess(); + return; } + process.exit(code || 0); }); } else { console.warn(`Another build detected or stale lockfile ${lockFileName}`); - // racing magic code + // rasing magic code process.exit(133); } +} + +process.on("uncaughtException", onUncaughtException); + +// OS signal handlers +// Ctrl+C +process.on("SIGINT", exitProcess); +// kill pid +process.on("SIGUSR1", exitProcess); +process.on("SIGUSR2", exitProcess); +process.on("SIGTERM", exitProcess); +process.on("SIGHUP", exitProcess); + +const maybeSubcommand = process_argv[2]; + +if (maybeSubcommand === "build" && process_argv.includes("-w")) { + // All clients of type MiniWebSocket /** - * - * @param {boolean} withWebSocket + * @type {any[]} */ - function startWatchMode(withWebSocket) { - if (withWebSocket) { - setUpWebSocket(); - } - // for column one based error message + let wsClients = []; + let withWebSocket = false; + let webSocketHost = "localhost"; + let webSocketPort = 9999; - /** - * watchers are held so that we close it later - */ - var watchers = []; + const sourcedirs = path.join("lib", "bs", ".sourcedirs.json"); - process.stdin.on("close", exitProcess); - // close when stdin stops - if (os.platform() !== "win32") { - process.stdin.on("end", exitProcess); - process.stdin.resume(); - } + let LAST_BUILD_START = 0; + let LAST_FIRED_EVENT = 0; + /** + * @type {[string,string][]} + */ + let reasonsToRebuild = []; + let watchGenerated = []; - var sourcedirs = path.join("lib", "bs", ".sourcedirs.json"); - var watchGenerated = []; - - function watchBuild(watchConfig) { - var watchFiles = watchConfig.dirs; - watchGenerated = watchConfig.generated; - // close and remove all unused watchers - watchers = watchers.filter(function (watcher) { - if (watcher.dir === bsconfig) { - return true; - } else if (watchFiles.indexOf(watcher.dir) < 0) { - dlog(`${watcher.dir} is no longer watched`); - watcher.watcher.close(); - return false; - } else { - return true; - } - }); + /** + * watchers are held so that we close it later + */ + let watchers = []; - // adding new watchers - for (var i = 0; i < watchFiles.length; ++i) { - var dir = watchFiles[i]; - if ( - !watchers.find(function (watcher) { - return watcher.dir === dir; - }) - ) { - dlog(`watching dir ${dir} now`); - var watcher = fs.watch(dir, onChange); - watchers.push({ dir: dir, watcher: watcher }); - } else { - // console.log(dir, 'already watched') - } - } - } + const delegatedArgs = process_argv.slice(2); + verbose = delegatedArgs.includes("-verbose"); + var wsParamIndex = delegatedArgs.indexOf("-ws"); + if (wsParamIndex > -1) { + var hostAndPortNumber = (delegatedArgs[wsParamIndex + 1] || "").split(":"); /** - * @param {string | null} fileName + * @type {number} */ - function checkIsRebuildReason(fileName) { - // Return true if filename is nil, filename is only provided on Linux, macOS, Windows, and AIX. - // On other systems, we just have to assume that any change is valid. - // This could cause problems if source builds (generating js files in the same directory) are supported. - if (!fileName) return true; - - return !( - fileName === ".merlin" || - fileName.endsWith(".js") || - fileName.endsWith(".mjs") || - fileName.endsWith(".cjs") || - fileName.endsWith(genTypeFileExtension) || - watchGenerated.indexOf(fileName) >= 0 || - fileName.endsWith(".swp") - ); + var portNumber; + if (hostAndPortNumber.length === 1) { + portNumber = parseInt(hostAndPortNumber[0]); + } else { + webSocketHost = hostAndPortNumber[0]; + portNumber = parseInt(hostAndPortNumber[1]); } - - /** - * @return {boolean} - */ - function needRebuild() { - return reasonsToRebuild.length !== 0; + if (!isNaN(portNumber)) { + webSocketPort = portNumber; } + withWebSocket = true; + dlog(`WebSocket host & port number: ${webSocketHost}:${webSocketPort}`); + } - function logFinish(code) { - let log = `>>>> Finish compiling`; - if (code !== 0) { - log = log + " (exit: " + code + ")"; + const rescriptWatchBuildArgs = verbose + ? ["build", "-no-deps", "-verbose"] + : ["build", "-no-deps"]; + + function notifyClients() { + wsClients = wsClients.filter(x => !x.closed && !x.socket.destroyed); + var wsClientsLen = wsClients.length; + dlog(`Alive sockets number: ${wsClientsLen}`); + var data = '{"LAST_SUCCESS_BUILD_STAMP":' + LAST_SUCCESS_BUILD_STAMP + "}"; + for (var i = 0; i < wsClientsLen; ++i) { + // in reverse order, the last pushed get notified earlier + var client = wsClients[wsClientsLen - i - 1]; + if (!client.closed) { + client.sendText(data); } - if (std_is_tty) { - log = "\x1b[36m" + log + "\x1b[0m"; + } + } + + function setUpWebSocket() { + var WebSocket = require("./lib/minisocket.js").MiniWebSocket; + var id = setInterval(notifyClients, 3000); + require("http") + .createServer() + .on("upgrade", function (req, socket, upgradeHead) { + dlog("connection opened"); + var ws = new WebSocket(req, socket, upgradeHead); + socket.on("error", function (err) { + dlog(`Socket Error ${err}`); + }); + wsClients.push(ws); + }) + .on("error", function (err) { + // @ts-ignore + if (err !== undefined && err.code === "EADDRINUSE") { + var error = isTtyStd ? `\x1b[1;31mERROR:\x1b[0m` : `ERROR:`; + console.error(`${error} The websocket port number ${webSocketPort} is in use. +Please pick a different one using the \`-ws [host:]port\` flag from bsb.`); + } else { + console.error(err); + } + process.exit(2); + }) + .listen(webSocketPort, webSocketHost); + } + + function watchBuild(watchConfig) { + var watchFiles = watchConfig.dirs; + watchGenerated = watchConfig.generated; + // close and remove all unused watchers + watchers = watchers.filter(function (watcher) { + if (watcher.dir === bsconfig) { + return true; + } else if (watchFiles.indexOf(watcher.dir) < 0) { + dlog(`${watcher.dir} is no longer watched`); + watcher.watcher.close(); + return false; + } else { + return true; } - if (code !== 0) { - console.log(log); + }); + + // adding new watchers + for (var i = 0; i < watchFiles.length; ++i) { + var dir = watchFiles[i]; + if ( + !watchers.find(function (watcher) { + return watcher.dir === dir; + }) + ) { + dlog(`watching dir ${dir} now`); + var watcher = fs.watch(dir, onChange); + watchers.push({ dir: dir, watcher: watcher }); } else { - console.log(log, Math.floor(updateFinishTime() / 1e6), "mseconds"); + // console.log(dir, 'already watched') } } + } - function logStart() { - updateStartTime(); - let log = `>>>> Start compiling`; - if (std_is_tty) { - log = "\x1b[36m" + log + "\x1b[0m"; - } - console.log(log); + /** + * @param {string | null} fileName + */ + function checkIsRebuildReason(fileName) { + // Return true if filename is nil, filename is only provided on Linux, macOS, Windows, and AIX. + // On other systems, we just have to assume that any change is valid. + // This could cause problems if source builds (generating js files in the same directory) are supported. + if (!fileName) return true; + + return !( + fileName === ".merlin" || + fileName.endsWith(".js") || + fileName.endsWith(".mjs") || + fileName.endsWith(".cjs") || + fileName.endsWith(genTypeFileExtension) || + watchGenerated.indexOf(fileName) >= 0 || + fileName.endsWith(".swp") + ); + } + + /** + * @return {boolean} + */ + function needRebuild() { + return reasonsToRebuild.length !== 0; + } + + /** + * @param code {number} + */ + function buildFinishedCallback(code) { + if (code === 0) { + LAST_SUCCESS_BUILD_STAMP = Date.now(); + notifyClients(); } + logFinishCompiling(code); + releaseBuild(); + if (needRebuild()) { + build(0); + } else { + var files = getWatchFiles(sourcedirs); + watchBuild(files); + } + } - /** - * - * @param code {number} - * @param signal {string} - */ - function buildFinishedCallback(code, signal) { - if (code === 0) { - LAST_SUCCESS_BUILD_STAMP = Date.now(); - notifyClients(); - if (postBuild) { - dlog(`running postbuild command: ${postBuild}`); - child_process.exec(postBuild); - } - } - logFinish(code); - releaseBuild(); - if (needRebuild()) { - build(0); - } else { - var files = getWatchFiles(sourcedirs); - watchBuild(files); - } + /** + * TODO: how to make it captured by vscode + * @param error {string} + * @param highlight {string} + */ + function outputError(error, highlight) { + if (isTtyError && highlight) { + process.stderr.write( + error.replace(highlight, "\x1b[1;31m" + highlight + "\x1b[0m") + ); + } else { + process.stderr.write(error); } + } - /** - * TODO: how to make it captured by vscode - * @param error {string} - * @param highlight {string} - */ - function outputError(error, highlight) { - if (error_is_tty && highlight) { - process.stderr.write( - error.replace(highlight, "\x1b[1;31m" + highlight + "\x1b[0m") - ); - } else { - process.stderr.write(error); - } + // Note this function filters the error output + // it relies on the fact that ninja will merege stdout and stderr + // of the compiler output, if it does not + // then we should have a way to not filter the compiler output + /** + * + * @param {number} depth + */ + function build(depth) { + if (reasonsToRebuild.length === 0) { + dlog("No need to rebuild"); + return; + } else { + dlog(`Rebuilding since ${reasonsToRebuild}`); } - // Note this function filters the error output - // it relies on the fact that ninja will merege stdout and stderr - // of the compiler output, if it does not - // then we should have a way to not filter the compiler output - /** - * - * @param {number} depth - * @returns - */ - function build(depth) { - if (reasonsToRebuild.length === 0) { - dlog("No need to rebuild"); - return; - } else { - dlog(`Rebuilding since ${reasonsToRebuild}`); - } - if (acquireBuild()) { - logStart(); - child_process - .spawn(rescript_exe, [], { - stdio: ["inherit", "inherit", "pipe"], - }) - // @ts-ignore - .on("data", function (s) { - outputError(s, "ninja: error"); - }) - .on("exit", buildFinishedCallback) - .stderr.setEncoding("utf8"); - // This is important to clean up all - // previous queued events - reasonsToRebuild = []; - LAST_BUILD_START = Date.now(); - } - // if acquiring lock failed, no need retry here - // since buildFinishedCallback will try again - // however this is no longer the case for multiple-process - // it could fail due to other issues like .bsb.lock - else { - dlog( - `Acquire lock failed, do the build later ${depth} : ${reasonsToRebuild}` - ); - var waitTime = Math.pow(2, depth) * 40; - setTimeout(function () { - var d = Math.min(depth + 1, 5); - build(d); - }, waitTime); - } + if (acquireBuild()) { + logStartCompiling(); + child_process + .spawn(rescript_exe, rescriptWatchBuildArgs, { + stdio: ["inherit", "inherit", "pipe"], + }) + // @ts-ignore + .on("data", function (s) { + outputError(s, "ninja: error"); + }) + .on("exit", buildFinishedCallback) + .stderr.setEncoding("utf8"); + // This is important to clean up all + // previous queued events + reasonsToRebuild = []; + LAST_BUILD_START = Date.now(); } - /** - * - * @param {fs.WatchEventType} event - * @param {string | null} reason - */ - function onChange(event, reason) { - var eventTime = Date.now(); - var timeDiff = eventTime - LAST_BUILD_START; - var eventDiff = eventTime - LAST_FIRED_EVENT; - dlog(`Since last build : ${timeDiff} -- ${eventDiff}`); - if (timeDiff < 5 || eventDiff < 5) { - // for 5ms, we could think that the ninja not get - // kicked yet, so there is really no need - // to send more events here - - // note reasonsToRebuild also - // helps avoid redundant build, but this will - // save the event loop call `setImmediate` - return; - } - if (checkIsRebuildReason(reason)) { - dlog(`\nEvent ${event} ${reason}`); - LAST_FIRED_EVENT = eventTime; - reasonsToRebuild.push([event, reason || ""]); - // Some editors are using temporary files to store edits. - // This results in two sync change events: change + rename and two sync builds. - // Using setImmediate will ensure that only one build done. - setImmediate(() => { - if (needRebuild()) { - if (process.env.BS_WATCH_CLEAR && console.clear) { - console.clear(); - } - build(0); + // if acquiring lock failed, no need retry here + // since buildFinishedCallback will try again + // however this is no longer the case for multiple-process + // it could fail due to other issues like .bsb.lock + else { + dlog( + `Acquire lock failed, do the build later ${depth} : ${reasonsToRebuild}` + ); + const waitTime = Math.pow(2, depth) * 40; + setTimeout(() => { + build(Math.min(depth + 1, 5)); + }, waitTime); + } + } + + /** + * + * @param {fs.WatchEventType} event + * @param {string | null} reason + */ + function onChange(event, reason) { + var eventTime = Date.now(); + var timeDiff = eventTime - LAST_BUILD_START; + var eventDiff = eventTime - LAST_FIRED_EVENT; + dlog(`Since last build: ${timeDiff} -- ${eventDiff}`); + if (timeDiff < 5 || eventDiff < 5) { + // for 5ms, we could think that the ninja not get + // kicked yet, so there is really no need + // to send more events here + + // note reasonsToRebuild also + // helps avoid redundant build, but this will + // save the event loop call `setImmediate` + return; + } + if (checkIsRebuildReason(reason)) { + dlog(`\nEvent ${event} ${reason}`); + LAST_FIRED_EVENT = eventTime; + reasonsToRebuild.push([event, reason || ""]); + // Some editors are using temporary files to store edits. + // This results in two sync change events: change + rename and two sync builds. + // Using setImmediate will ensure that only one build done. + setImmediate(() => { + if (needRebuild()) { + if (process.env.BS_WATCH_CLEAR && console.clear) { + console.clear(); } - }); - } + build(0); + } + }); + } + } + + /** + * + * @param {boolean} withWebSocket + */ + function startWatchMode(withWebSocket) { + if (withWebSocket) { + setUpWebSocket(); + } + // for column one based error message + + process.stdin.on("close", exitProcess); + // close when stdin stops + if (os.platform() !== "win32") { + process.stdin.on("end", exitProcess); + process.stdin.resume(); } watchers.push({ watcher: fs.watch(bsconfig, onChange), dir: bsconfig }); - build(0); + } + + logStartCompiling(); + delegateCommand(delegatedArgs, () => { + startWatchMode(withWebSocket); + buildFinishedCallback(0); + }); +} else { + switch (maybeSubcommand) { + case "info": + case "clean": { + delegateCommand(process_argv.slice(2)); + break; + } + case undefined: + case "build": { + logStartCompiling(); + delegateCommand(process_argv.slice(2), logFinishCompiling); + break; + } + case "format": + require("./scripts/rescript_format.js").main( + process.argv.slice(3), + rescript_exe, + bsc_exe + ); + break; + case "dump": + require("./scripts/rescript_dump.js").main( + process.argv.slice(3), + rescript_exe, + bsc_exe + ); + break; + case "dump": + require("./scripts/rescript_dump.js").main( + process.argv.slice(3), + rescript_exe, + bsc_exe + ); + break; + case "convert": + // Todo + require("./scripts/rescript_convert.js").main( + process.argv.slice(3), + rescript_exe, + bsc_exe + ); + break; + case "-h": + case "-help": + case "help": + help(); + break; + case "-v": + case "-version": + console.log(require("./package.json").version); + break; + default: + console.error(`Unknown subcommand or flags: ${maybeSubcommand}`); + help(); + process.exit(2); } } From 4d1f7fb76a4f19a20c8f2bbc3f6af680f7a6042c Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 20 Sep 2023 10:43:19 +0300 Subject: [PATCH 2/4] Update clean command help --- jscomp/bsb_exe/rescript_main.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jscomp/bsb_exe/rescript_main.ml b/jscomp/bsb_exe/rescript_main.ml index aa32653889..799266ff26 100644 --- a/jscomp/bsb_exe/rescript_main.ml +++ b/jscomp/bsb_exe/rescript_main.ml @@ -85,7 +85,7 @@ let ninja_command_exit (type t) (ninja_args : string array) : t = *) let clean_usage = "Usage: rescript clean \n\n\ - `rescript clean` cleans build artifacts. It is useful for edge-case reasons in case you get into a stale build\n" + `rescript clean` cleans build artifacts\n" let build_usage = "Usage: rescript build -- \n\n\ From 56c464b0a37eb8d298e5c6e46fa4b590e3f9fe3e Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 20 Sep 2023 10:47:26 +0300 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73ce10beba..be3412ad37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ #### :bug: Bug Fix -- Fix issue with GenType and labelled arguments https://github.com/rescript-lang/rescript-compiler/pull/6406 +- Fix issue with GenType and labelled arguments. https://github.com/rescript-lang/rescript-compiler/pull/6406 +- Fix dependencies reinitialization on every change in watch mode. Leads to faster rebuilds and cleaner terminal. https://github.com/rescript-lang/rescript-compiler/pull/6404 #### :rocket: New Feature @@ -29,6 +30,9 @@ - The error message for "toplevel expressions should evaluate to unit" has been revamped and improved. https://github.com/rescript-lang/rescript-compiler/pull/6407 - Improve "Somewhere wanted" error messages by changing wording and adding more context + suggested solutions to the error messages where appropriate. https://github.com/rescript-lang/rescript-compiler/pull/6410 - Add smart printer for pipe-chains. https://github.com/rescript-lang/rescript-compiler/pull/6411 +- Display the compile time for `rescript build` command. https://github.com/rescript-lang/rescript-compiler/pull/6404 +- Actualize help message for `build` and `clean` commands. https://github.com/rescript-lang/rescript-compiler/pull/6404 +- Pass through the `-verbose` flag to builds in watch mode. https://github.com/rescript-lang/rescript-compiler/pull/6404 # 11.0.0-rc.3 From 70b514a8741596263928e5f6fe7196171c664e00 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 20 Sep 2023 11:16:21 +0300 Subject: [PATCH 4/4] Update CHANGELOG.md Co-authored-by: Christoph Knittel --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be3412ad37..76d7cfbe1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ - Improve "Somewhere wanted" error messages by changing wording and adding more context + suggested solutions to the error messages where appropriate. https://github.com/rescript-lang/rescript-compiler/pull/6410 - Add smart printer for pipe-chains. https://github.com/rescript-lang/rescript-compiler/pull/6411 - Display the compile time for `rescript build` command. https://github.com/rescript-lang/rescript-compiler/pull/6404 -- Actualize help message for `build` and `clean` commands. https://github.com/rescript-lang/rescript-compiler/pull/6404 +- Improve help message for `build` and `clean` commands. https://github.com/rescript-lang/rescript-compiler/pull/6404 - Pass through the `-verbose` flag to builds in watch mode. https://github.com/rescript-lang/rescript-compiler/pull/6404 # 11.0.0-rc.3