@@ -15,7 +15,7 @@ use notify::event::ModifyKind;
1515use notify:: { Config , Error , Event , EventKind , RecommendedWatcher , RecursiveMode , Watcher } ;
1616use std:: path:: { Path , PathBuf } ;
1717use std:: sync:: Arc ;
18- use std:: sync:: Mutex ;
18+ use std:: sync:: atomic :: { AtomicBool , Ordering } ;
1919use std:: time:: { Duration , Instant } ;
2020
2121#[ derive( Debug , Clone , PartialEq , Eq , Copy ) ]
@@ -25,6 +25,9 @@ enum CompileType {
2525 None ,
2626}
2727
28+ type WatchPaths = Vec < ( PathBuf , RecursiveMode ) > ;
29+ type StartupBuildResult = ( BuildCommandState , WatchPaths , Option < Instant > ) ;
30+
2831fn is_rescript_file ( path_buf : & Path ) -> bool {
2932 let extension = path_buf. extension ( ) . and_then ( |ext| ext. to_str ( ) ) ;
3033
@@ -204,11 +207,24 @@ fn carry_forward_compile_warnings(previous: &BuildCommandState, next: &mut Build
204207 }
205208}
206209
210+ fn cleanup_before_watch_exit (
211+ path : & Path ,
212+ build_state : & BuildCommandState ,
213+ show_progress : bool ,
214+ message : & str ,
215+ ) {
216+ if show_progress {
217+ println ! ( "{message}" ) ;
218+ }
219+ build:: with_build_lock ( path, || clean:: cleanup_after_build ( build_state) ) ;
220+ }
221+
207222struct AsyncWatchArgs < ' a > {
208223 watcher : & ' a mut RecommendedWatcher ,
209224 current_watch_paths : Vec < ( PathBuf , RecursiveMode ) > ,
210225 initial_build_state : BuildCommandState ,
211226 q : Arc < FifoQueue < Result < Event , Error > > > ,
227+ ctrlc_pressed : Arc < AtomicBool > ,
212228 path : & ' a Path ,
213229 show_progress : bool ,
214230 filter : & ' a Option < regex:: Regex > ,
@@ -223,6 +239,7 @@ async fn async_watch(
223239 mut current_watch_paths,
224240 initial_build_state,
225241 q,
242+ ctrlc_pressed,
226243 path,
227244 show_progress,
228245 filter,
@@ -233,23 +250,10 @@ async fn async_watch(
233250) -> Result < ( ) > {
234251 let mut build_state = initial_build_state;
235252 let mut needs_compile_type = CompileType :: None ;
236- // create a mutex to capture if ctrl-c was pressed
237- let ctrlc_pressed = Arc :: new ( Mutex :: new ( false ) ) ;
238- let ctrlc_pressed_clone = Arc :: clone ( & ctrlc_pressed) ;
239-
240- ctrlc:: set_handler ( move || {
241- let pressed = Arc :: clone ( & ctrlc_pressed) ;
242- let mut pressed = pressed. lock ( ) . unwrap ( ) ;
243- * pressed = true ;
244- } )
245- . expect ( "Error setting Ctrl-C handler" ) ;
246253
247254 loop {
248- if * ctrlc_pressed_clone. lock ( ) . unwrap ( ) {
249- if show_progress {
250- println ! ( "\n Exiting..." ) ;
251- }
252- build:: with_build_lock ( path, || clean:: cleanup_after_build ( & build_state) ) ;
255+ if ctrlc_pressed. load ( Ordering :: SeqCst ) {
256+ cleanup_before_watch_exit ( path, & build_state, show_progress, "\n Exiting..." ) ;
253257 break Ok ( ( ) ) ;
254258 }
255259 let mut events: Vec < Event > = vec ! [ ] ;
@@ -271,10 +275,12 @@ async fn async_watch(
271275 . any ( |path| path. ends_with ( LockKind :: Watch . file_name ( ) ) )
272276 && let EventKind :: Remove ( _) = event. kind
273277 {
274- if show_progress {
275- println ! ( "\n Exiting... (lockfile removed)" ) ;
276- }
277- build:: with_build_lock ( path, || clean:: cleanup_after_build ( & build_state) ) ;
278+ cleanup_before_watch_exit (
279+ path,
280+ & build_state,
281+ show_progress,
282+ "\n Exiting... (lockfile removed)" ,
283+ ) ;
278284 return Ok ( ( ) ) ;
279285 }
280286
@@ -500,9 +506,16 @@ pub fn start(
500506
501507 let path = Path :: new ( folder) ;
502508
509+ let ctrlc_pressed = Arc :: new ( AtomicBool :: new ( false ) ) ;
510+ let ctrlc_pressed_for_handler = Arc :: clone ( & ctrlc_pressed) ;
511+ ctrlc:: set_handler ( move || {
512+ ctrlc_pressed_for_handler. store ( true , Ordering :: SeqCst ) ;
513+ } )
514+ . expect ( "Error setting Ctrl-C handler" ) ;
515+
503516 // Initialization can clean previous build artifacts, so it has to be serialized
504517 // with the initial compile too.
505- let ( build_state, current_watch_paths) : ( BuildCommandState , Vec < ( PathBuf , RecursiveMode ) > ) =
518+ let ( build_state, current_watch_paths, initial_compile_timing ) : StartupBuildResult =
506519 build:: with_build_lock ( path, || {
507520 let mut build_state = build:: initialize_build (
508521 None ,
@@ -519,7 +532,7 @@ pub fn start(
519532 register_watches ( & mut watcher, & current_watch_paths) ;
520533
521534 let timing_total = Instant :: now ( ) ;
522- if build:: incremental_build_without_lock (
535+ let initial_compile_timing = if build:: incremental_build_without_lock (
523536 & mut build_state,
524537 None ,
525538 true ,
@@ -530,26 +543,40 @@ pub fn start(
530543 )
531544 . is_ok ( )
532545 {
533- finish_successful_watch_compile (
534- after_build. clone ( ) ,
535- timing_total,
536- show_progress,
537- plain_output,
538- "Finished initial compilation" ,
539- ) ;
540- }
546+ Some ( timing_total)
547+ } else {
548+ None
549+ } ;
541550
542- Ok :: < ( BuildCommandState , Vec < ( PathBuf , RecursiveMode ) > ) , anyhow:: Error > ( (
551+ Ok :: < StartupBuildResult , anyhow:: Error > ( (
543552 build_state,
544553 current_watch_paths,
554+ initial_compile_timing,
545555 ) )
546556 } ) ?;
547557
558+ if ctrlc_pressed. load ( Ordering :: SeqCst ) {
559+ cleanup_before_watch_exit ( path, & build_state, show_progress, "\n Exiting..." ) ;
560+ return Ok ( ( ) ) ;
561+ }
562+
563+ // Run after-build outside build.lock. Hooks may invoke ReScript commands that need the same lock.
564+ if let Some ( timing_total) = initial_compile_timing {
565+ finish_successful_watch_compile (
566+ after_build. clone ( ) ,
567+ timing_total,
568+ show_progress,
569+ plain_output,
570+ "Finished initial compilation" ,
571+ ) ;
572+ }
573+
548574 async_watch ( AsyncWatchArgs {
549575 watcher : & mut watcher,
550576 current_watch_paths,
551577 initial_build_state : build_state,
552578 q : consumer,
579+ ctrlc_pressed,
553580 path,
554581 show_progress,
555582 filter,
0 commit comments