Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374#include "watch.h"#include "messages.h"
#include <uv.h>#include <stdio.h>#include <signal.h>#include <stdint.h>#include <stdlib.h>#include <string.h>#include <stdbool.h>#include <crprintf.h>
#ifdef _WIN32#include <direct.h>#endif
#ifndef SIGKILL#define SIGKILL SIGTERM#endif
typedef struct watch_state { uv_loop_t loop; uv_fs_event_t fs_event; uv_signal_t sigint; uv_signal_t sigterm; uv_timer_t kill_timer; uv_process_t *child; char **child_argv; char *watch_path; bool no_clear_screen; bool stop_requested; bool restart_pending; bool child_running; bool fs_event_inited; bool sigint_inited; bool sigterm_inited; bool kill_timer_inited; bool kill_timer_running; int final_exit_code;} watch_state_t;
static void watch_clear_screen(void) { fputs("\033[3J\033[2J\033[H", stdout); fflush(stdout);}
char *ant_watch_resolve_path(const char *path) {#ifdef _WIN32 char *resolved = _fullpath(NULL, path, 0); if (resolved) return resolved; return _strdup(path);#else char abs_path[PATH_MAX]; const char *resolved = realpath(path, abs_path); return strdup(resolved ? resolved : path);#endif}
int ant_watch_start( uv_loop_t *loop, uv_fs_event_t *event, const char *path, uv_fs_event_cb callback, void *data, unsigned int flags, char **resolved_path_out) { char *watch_path = NULL; int rc = 0;
if (!loop || !event || !path || !callback) return UV_EINVAL;
watch_path = ant_watch_resolve_path(path); if (!watch_path) return UV_ENOMEM;
rc = uv_fs_event_init(loop, event); if (rc != 0) goto cleanup;
event->data = data; rc = uv_fs_event_start(event, callback, watch_path, flags); if (rc != 0) goto cleanup;
if (resolved_path_out) *resolved_path_out = watch_path; else free(watch_path); return 0;
cleanup: free(watch_path); return rc;}
void ant_watch_stop(uv_fs_event_t *event) { if (!event) return; uv_fs_event_stop(event);}
static char **watch_build_child_argv(int argc, char **argv) { char **child_argv = calloc((size_t)argc + 1, sizeof(char *)); if (!child_argv) return NULL;
int out = 0; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "--watch") == 0) continue; if (strcmp(argv[i], "--no-clear-screen") == 0) continue; child_argv[out++] = argv[i]; }
child_argv[out] = NULL; if (out == 0) { free(child_argv); return NULL; }
return child_argv;}
static void watch_stop_kill_timer(watch_state_t *state) { if (!state->kill_timer_inited || !state->kill_timer_running) return; uv_timer_stop(&state->kill_timer); state->kill_timer_running = false;}
static void watch_on_child_closed(uv_handle_t *handle) { free(handle); }static void watch_request_shutdown(watch_state_t *state, int exit_code);static void watch_try_spawn_child(watch_state_t *state);
static void watch_on_kill_timer(uv_timer_t *timer) { watch_state_t *state = (watch_state_t *)timer->data; state->kill_timer_running = false;
if (state->child_running && state->child) uv_process_kill(state->child, SIGKILL); if (state->stop_requested && !uv_is_closing((uv_handle_t *)&state->kill_timer)) uv_close((uv_handle_t *)&state->kill_timer, NULL);}
static void watch_on_child_exit(uv_process_t *proc, int64_t exit_status, int term_signal) { watch_state_t *state = (watch_state_t *)proc->data; state->child_running = false; state->child = NULL; watch_stop_kill_timer(state);
if (state->stop_requested) { if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) uv_close((uv_handle_t *)&state->kill_timer, NULL); } else if (state->restart_pending) { watch_try_spawn_child(state); } else if (term_signal > 0) { state->final_exit_code = 128 + term_signal; } else state->final_exit_code = (int)exit_status;
uv_close((uv_handle_t *)proc, watch_on_child_closed);}
static int watch_spawn_child(watch_state_t *state) { uv_process_t *proc = calloc(1, sizeof(*proc)); if (!proc) return UV_ENOMEM;
uv_stdio_container_t stdio[3]; stdio[0].flags = UV_INHERIT_FD; stdio[0].data.fd = 0; stdio[1].flags = UV_INHERIT_FD; stdio[1].data.fd = 1; stdio[2].flags = UV_INHERIT_FD; stdio[2].data.fd = 2;
uv_process_options_t options = {0}; options.file = state->child_argv[0]; options.args = state->child_argv; options.exit_cb = watch_on_child_exit; options.stdio_count = 3; options.stdio = stdio;
int rc = uv_spawn(&state->loop, proc, &options); if (rc != 0) { free(proc); return rc; }
proc->data = state; state->child = proc; state->child_running = true; state->restart_pending = false; return 0;}
static void watch_try_spawn_child(watch_state_t *state) { if (state->stop_requested) return; if (!state->no_clear_screen && state->restart_pending) watch_clear_screen(); int rc = watch_spawn_child(state); if (rc != 0) { crfprintf(stderr, msg.watch_spawn_failed, uv_strerror(rc)); watch_request_shutdown(state, EXIT_FAILURE); }}
static void watch_start_kill_timer(watch_state_t *state, uint64_t timeout_ms) { if (!state->kill_timer_inited || state->kill_timer_running) return; int rc = uv_timer_start(&state->kill_timer, watch_on_kill_timer, timeout_ms, 0); if (rc == 0) state->kill_timer_running = true;}
static void watch_close_signals_and_watcher(watch_state_t *state) { if (state->fs_event_inited && !uv_is_closing((uv_handle_t *)&state->fs_event)) { uv_fs_event_stop(&state->fs_event); uv_close((uv_handle_t *)&state->fs_event, NULL); }
if (state->sigint_inited && !uv_is_closing((uv_handle_t *)&state->sigint)) { uv_signal_stop(&state->sigint); uv_close((uv_handle_t *)&state->sigint, NULL); }
if (state->sigterm_inited && !uv_is_closing((uv_handle_t *)&state->sigterm)) { uv_signal_stop(&state->sigterm); uv_close((uv_handle_t *)&state->sigterm, NULL); }}
static void watch_request_shutdown(watch_state_t *state, int exit_code) { if (state->stop_requested) return;
state->stop_requested = true; state->restart_pending = false; state->final_exit_code = exit_code;
watch_close_signals_and_watcher(state);
if (state->child_running && state->child) { int rc = uv_process_kill(state->child, SIGTERM); if (rc != 0 && rc != UV_ESRCH) { crfprintf(stderr, msg.watch_graceful_term, uv_strerror(rc)); } watch_start_kill_timer(state, 1000); } else if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) { uv_close((uv_handle_t *)&state->kill_timer, NULL); }}
static void watch_request_restart(watch_state_t *state) { if (state->stop_requested) return; if (!state->restart_pending) {} state->restart_pending = true;
if (state->child_running && state->child) { int rc = uv_process_kill(state->child, SIGTERM); if (rc != 0 && rc != UV_ESRCH) { crfprintf(stderr, msg.watch_child_error, uv_strerror(rc)); watch_request_shutdown(state, EXIT_FAILURE); return; } watch_start_kill_timer(state, 1000); } else watch_try_spawn_child(state);}
static void watch_on_fs_event(uv_fs_event_t *handle, const char *filename, int events, int status) { watch_state_t *state = (watch_state_t *)handle->data;
if (status < 0) { crfprintf(stderr, msg.watch_warn_normal, state->watch_path, uv_strerror(status)); return; }
if ((events & (UV_CHANGE | UV_RENAME)) == 0) return; watch_request_restart(state);}
static void watch_on_signal(uv_signal_t *handle, int signum) { watch_state_t *state = (watch_state_t *)handle->data; watch_request_shutdown(state, 128 + signum);}
int ant_watch_run(int argc, char **argv, const char *entry_file, bool no_clear_screen) { if (!entry_file || !*entry_file) { crfprintf(stderr, msg.watch_entrypoint_missing); return EXIT_FAILURE; }
watch_state_t state = {0}; state.child_argv = watch_build_child_argv(argc, argv); state.watch_path = ant_watch_resolve_path(entry_file); state.no_clear_screen = no_clear_screen; state.final_exit_code = EXIT_SUCCESS;
if (!state.child_argv || !state.watch_path) { free(state.child_argv); free(state.watch_path); crfprintf(stderr, msg.watch_start_fatal); return EXIT_FAILURE; }
int rc = uv_loop_init(&state.loop); if (rc != 0) { crfprintf(stderr, msg.watch_loop_fatal, uv_strerror(rc)); free(state.child_argv); free(state.watch_path); return EXIT_FAILURE; }
rc = ant_watch_start( &state.loop, &state.fs_event, state.watch_path, watch_on_fs_event, &state, 0, NULL ); if (rc == 0) state.fs_event_inited = true; if (rc != 0) { crfprintf(stderr, msg.watch_file_failed, state.watch_path, uv_strerror(rc)); watch_request_shutdown(&state, EXIT_FAILURE); }
if (rc == 0) { rc = uv_signal_init(&state.loop, &state.sigint); if (rc == 0) state.sigint_inited = true; if (rc == 0) { state.sigint.data = &state; rc = uv_signal_start(&state.sigint, watch_on_signal, SIGINT); } } if (rc == 0) { rc = uv_signal_init(&state.loop, &state.sigterm); if (rc == 0) state.sigterm_inited = true; if (rc == 0) { state.sigterm.data = &state; int sigterm_rc = uv_signal_start(&state.sigterm, watch_on_signal, SIGTERM); if (sigterm_rc != 0 && sigterm_rc != UV_ENOSYS && sigterm_rc != UV_EINVAL) { rc = sigterm_rc; } else if (sigterm_rc != 0) { uv_signal_stop(&state.sigterm); uv_close((uv_handle_t *)&state.sigterm, NULL); state.sigterm_inited = false; } } } if (rc == 0) { rc = uv_timer_init(&state.loop, &state.kill_timer); if (rc == 0) { state.kill_timer_inited = true; state.kill_timer.data = &state; } } if (rc != 0) { crfprintf(stderr, msg.watch_loop_handles_fatal, uv_strerror(rc)); watch_request_shutdown(&state, EXIT_FAILURE); }
if (!state.stop_requested) watch_try_spawn_child(&state); while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {}
if (state.fs_event_inited && !uv_is_closing((uv_handle_t *)&state.fs_event)) { uv_close((uv_handle_t *)&state.fs_event, NULL); } if (state.sigint_inited && !uv_is_closing((uv_handle_t *)&state.sigint)) { uv_close((uv_handle_t *)&state.sigint, NULL); } if (state.sigterm_inited && !uv_is_closing((uv_handle_t *)&state.sigterm)) { uv_close((uv_handle_t *)&state.sigterm, NULL); } if (state.kill_timer_inited && !uv_is_closing((uv_handle_t *)&state.kill_timer)) { uv_close((uv_handle_t *)&state.kill_timer, NULL); } while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {}
rc = uv_loop_close(&state.loop); if (rc != 0) crfprintf(stderr, msg.watch_loop_cleanup, uv_strerror(rc));
free(state.child_argv); free(state.watch_path); return state.final_exit_code;}