@@ -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,13 @@ enum CompileType {
2525 None ,
2626}
2727
28+ type WatchPaths = Vec < ( PathBuf , RecursiveMode ) > ;
29+ type StartupBuildResult = (
30+ BuildCommandState ,
31+ WatchPaths ,
32+ Option < ( Instant , build:: CompilationOutcome ) > ,
33+ ) ;
34+
2835fn is_rescript_file ( path_buf : & Path ) -> bool {
2936 let extension = path_buf. extension ( ) . and_then ( |ext| ext. to_str ( ) ) ;
3037
@@ -58,6 +65,31 @@ fn matches_filter(path_buf: &Path, filter: &Option<regex::Regex>) -> bool {
5865 filter. as_ref ( ) . map ( |re| !re. is_match ( & name) ) . unwrap_or ( true )
5966}
6067
68+ fn finish_successful_watch_compile (
69+ after_build : Option < String > ,
70+ timing_total : Instant ,
71+ show_progress : bool ,
72+ plain_output : bool ,
73+ finished_message : & str ,
74+ compilation_kind : Option < & str > ,
75+ outcome : build:: CompilationOutcome ,
76+ ) {
77+ if let Some ( a) = after_build {
78+ cmd:: run ( a)
79+ }
80+ let timing_total_elapsed = timing_total. elapsed ( ) ;
81+ if show_progress {
82+ if plain_output {
83+ println ! ( "{finished_message}" )
84+ } else {
85+ println ! (
86+ "\n {}\n " ,
87+ build:: format_finished_compilation_message( compilation_kind, outcome, timing_total_elapsed)
88+ ) ;
89+ }
90+ }
91+ }
92+
6193/// Computes the list of paths to watch based on the build state.
6294/// Returns tuples of (path, recursive_mode) for each watch target.
6395fn compute_watch_paths ( build_state : & BuildCommandState , root : & Path ) -> Vec < ( PathBuf , RecursiveMode ) > {
@@ -178,13 +210,8 @@ fn carry_forward_compile_warnings(previous: &BuildCommandState, next: &mut Build
178210 }
179211}
180212
181- fn should_clear_screen (
182- clear_screen : bool ,
183- show_progress : bool ,
184- plain_output : bool ,
185- initial_build : bool ,
186- ) -> bool {
187- clear_screen && show_progress && !plain_output && !initial_build
213+ fn should_clear_screen ( clear_screen : bool , show_progress : bool , plain_output : bool ) -> bool {
214+ clear_screen && show_progress && !plain_output
188215}
189216
190217fn clear_terminal_screen ( ) {
@@ -203,11 +230,24 @@ fn print_build_failed_footer() {
203230 println ! ( "\n Build failed. Watching for changes..." ) ;
204231}
205232
233+ fn cleanup_before_watch_exit (
234+ path : & Path ,
235+ build_state : & BuildCommandState ,
236+ show_progress : bool ,
237+ message : & str ,
238+ ) {
239+ if show_progress {
240+ println ! ( "{message}" ) ;
241+ }
242+ build:: with_build_lock ( path, || clean:: cleanup_after_build ( build_state) ) ;
243+ }
244+
206245struct AsyncWatchArgs < ' a > {
207246 watcher : & ' a mut RecommendedWatcher ,
208247 current_watch_paths : Vec < ( PathBuf , RecursiveMode ) > ,
209248 initial_build_state : BuildCommandState ,
210249 q : Arc < FifoQueue < Result < Event , Error > > > ,
250+ ctrlc_pressed : Arc < AtomicBool > ,
211251 path : & ' a Path ,
212252 show_progress : bool ,
213253 filter : & ' a Option < regex:: Regex > ,
@@ -225,6 +265,7 @@ async fn async_watch(
225265 mut current_watch_paths,
226266 initial_build_state,
227267 q,
268+ ctrlc_pressed,
228269 path,
229270 show_progress,
230271 filter,
@@ -237,26 +278,10 @@ async fn async_watch(
237278 } : AsyncWatchArgs < ' _ > ,
238279) -> Result < ( ) > {
239280 let mut build_state = initial_build_state;
240- let mut needs_compile_type = CompileType :: Incremental ;
241- // create a mutex to capture if ctrl-c was pressed
242- let ctrlc_pressed = Arc :: new ( Mutex :: new ( false ) ) ;
243- let ctrlc_pressed_clone = Arc :: clone ( & ctrlc_pressed) ;
244-
245- ctrlc:: set_handler ( move || {
246- let pressed = Arc :: clone ( & ctrlc_pressed) ;
247- let mut pressed = pressed. lock ( ) . unwrap ( ) ;
248- * pressed = true ;
249- } )
250- . expect ( "Error setting Ctrl-C handler" ) ;
251-
252- let mut initial_build = true ;
253-
281+ let mut needs_compile_type = CompileType :: None ;
254282 loop {
255- if * ctrlc_pressed_clone. lock ( ) . unwrap ( ) {
256- if show_progress {
257- println ! ( "\n Exiting..." ) ;
258- }
259- build:: with_build_lock ( path, || clean:: cleanup_after_build ( & build_state) ) ;
283+ if ctrlc_pressed. load ( Ordering :: SeqCst ) {
284+ cleanup_before_watch_exit ( path, & build_state, show_progress, "\n Exiting..." ) ;
260285 break Ok ( ( ) ) ;
261286 }
262287 let mut events: Vec < Event > = vec ! [ ] ;
@@ -278,10 +303,12 @@ async fn async_watch(
278303 . any ( |path| path. ends_with ( LockKind :: Watch . file_name ( ) ) )
279304 && let EventKind :: Remove ( _) = event. kind
280305 {
281- if show_progress {
282- println ! ( "\n Exiting... (lockfile removed)" ) ;
283- }
284- build:: with_build_lock ( path, || clean:: cleanup_after_build ( & build_state) ) ;
306+ cleanup_before_watch_exit (
307+ path,
308+ & build_state,
309+ show_progress,
310+ "\n Exiting... (lockfile removed)" ,
311+ ) ;
285312 return Ok ( ( ) ) ;
286313 }
287314
@@ -413,7 +440,7 @@ async fn async_watch(
413440
414441 match needs_compile_type {
415442 CompileType :: Incremental => {
416- if should_clear_screen ( clear_screen, show_progress, plain_output, initial_build ) {
443+ if should_clear_screen ( clear_screen, show_progress, plain_output) {
417444 clear_terminal_screen ( ) ;
418445 print_rebuild_header ( CompileType :: Incremental ) ;
419446 }
@@ -422,47 +449,36 @@ async fn async_watch(
422449 let result = build:: incremental_build (
423450 & mut build_state,
424451 None ,
425- initial_build ,
452+ false ,
426453 show_progress,
427- !initial_build ,
454+ true ,
428455 create_sourcedirs,
429456 plain_output,
430457 ) ;
431458
432459 match result {
433460 Ok ( result) => {
434- if let Some ( a) = after_build. clone ( ) {
435- cmd:: run ( a)
436- }
437- let timing_total_elapsed = timing_total. elapsed ( ) ;
438- if show_progress {
439- let compilation_type = if initial_build { "initial" } else { "incremental" } ;
440- if plain_output {
441- println ! ( "Finished {compilation_type} compilation" )
442- } else {
443- println ! (
444- "\n {}\n " ,
445- build:: format_finished_compilation_message(
446- Some ( compilation_type) ,
447- result,
448- timing_total_elapsed,
449- )
450- ) ;
451- }
452- }
461+ finish_successful_watch_compile (
462+ after_build. clone ( ) ,
463+ timing_total,
464+ show_progress,
465+ plain_output,
466+ "Finished incremental compilation" ,
467+ Some ( "incremental" ) ,
468+ result,
469+ ) ;
453470 }
454471 Err ( _) => {
455- if should_clear_screen ( clear_screen, show_progress, plain_output, initial_build ) {
472+ if should_clear_screen ( clear_screen, show_progress, plain_output) {
456473 print_build_failed_footer ( ) ;
457474 }
458475 }
459476 }
460477
461478 needs_compile_type = CompileType :: None ;
462- initial_build = false ;
463479 }
464480 CompileType :: Full => {
465- if should_clear_screen ( clear_screen, show_progress, plain_output, initial_build ) {
481+ if should_clear_screen ( clear_screen, show_progress, plain_output) {
466482 clear_terminal_screen ( ) ;
467483 print_rebuild_header ( CompileType :: Full ) ;
468484 }
@@ -497,7 +513,7 @@ async fn async_watch(
497513 let result = build:: incremental_build_without_lock (
498514 & mut build_state,
499515 None ,
500- initial_build ,
516+ false ,
501517 show_progress,
502518 false ,
503519 create_sourcedirs,
@@ -508,34 +524,23 @@ async fn async_watch(
508524 } ) ;
509525 match result {
510526 Ok ( result) => {
511- if let Some ( a) = after_build. clone ( ) {
512- cmd:: run ( a)
513- }
514-
515- let timing_total_elapsed = timing_total. elapsed ( ) ;
516- if show_progress {
517- if plain_output {
518- println ! ( "Finished compilation" )
519- } else {
520- println ! (
521- "\n {}\n " ,
522- build:: format_finished_compilation_message(
523- None ,
524- result,
525- timing_total_elapsed,
526- )
527- ) ;
528- }
529- }
527+ finish_successful_watch_compile (
528+ after_build. clone ( ) ,
529+ timing_total,
530+ show_progress,
531+ plain_output,
532+ "Finished compilation" ,
533+ None ,
534+ result,
535+ ) ;
530536 }
531537 Err ( _) => {
532- if should_clear_screen ( clear_screen, show_progress, plain_output, initial_build ) {
538+ if should_clear_screen ( clear_screen, show_progress, plain_output) {
533539 print_build_failed_footer ( ) ;
534540 }
535541 }
536542 }
537543 needs_compile_type = CompileType :: None ;
538- initial_build = false ;
539544 }
540545 CompileType :: None => {
541546 // We want to sleep for a little while so the CPU can schedule other work. That way we end
@@ -569,31 +574,77 @@ pub fn start(
569574
570575 let path = Path :: new ( folder) ;
571576
572- // Do an initial build to discover packages and source folders. Initialization can clean
573- // previous build artifacts, so it has to be serialized with other build operations.
574- let build_state: BuildCommandState = build:: with_build_lock ( path, || {
575- build:: initialize_build (
576- None ,
577- filter,
577+ let ctrlc_pressed = Arc :: new ( AtomicBool :: new ( false ) ) ;
578+ let ctrlc_pressed_for_handler = Arc :: clone ( & ctrlc_pressed) ;
579+ ctrlc:: set_handler ( move || {
580+ ctrlc_pressed_for_handler. store ( true , Ordering :: SeqCst ) ;
581+ } )
582+ . expect ( "Error setting Ctrl-C handler" ) ;
583+
584+ // Initialization can clean previous build artifacts, so it has to be serialized
585+ // with the initial compile too.
586+ let ( build_state, current_watch_paths, initial_compile_result) : StartupBuildResult =
587+ build:: with_build_lock ( path, || {
588+ let mut build_state = build:: initialize_build (
589+ None ,
590+ filter,
591+ show_progress,
592+ path,
593+ plain_output,
594+ warn_error. clone ( ) ,
595+ prod,
596+ features. clone ( ) ,
597+ )
598+ . with_context ( || "Could not initialize build" ) ?;
599+
600+ // Compute and register targeted watches based on source folders.
601+ let current_watch_paths = compute_watch_paths ( & build_state, path) ;
602+ register_watches ( & mut watcher, & current_watch_paths) ;
603+
604+ let timing_total = Instant :: now ( ) ;
605+ let initial_compile_result = build:: incremental_build_without_lock (
606+ & mut build_state,
607+ None ,
608+ true ,
609+ show_progress,
610+ false ,
611+ create_sourcedirs,
612+ plain_output,
613+ )
614+ . ok ( )
615+ . map ( |result| ( timing_total, result) ) ;
616+
617+ Ok :: < StartupBuildResult , anyhow:: Error > ( (
618+ build_state,
619+ current_watch_paths,
620+ initial_compile_result,
621+ ) )
622+ } ) ?;
623+
624+ if ctrlc_pressed. load ( Ordering :: SeqCst ) {
625+ cleanup_before_watch_exit ( path, & build_state, show_progress, "\n Exiting..." ) ;
626+ return Ok ( ( ) ) ;
627+ }
628+
629+ // Run after-build outside build.lock. Hooks may invoke ReScript commands that need the same lock.
630+ if let Some ( ( timing_total, result) ) = initial_compile_result {
631+ finish_successful_watch_compile (
632+ after_build. clone ( ) ,
633+ timing_total,
578634 show_progress,
579- path,
580635 plain_output,
581- warn_error. clone ( ) ,
582- prod,
583- features. clone ( ) ,
584- )
585- . with_context ( || "Could not initialize build" )
586- } ) ?;
587-
588- // Compute and register targeted watches based on source folders
589- let current_watch_paths = compute_watch_paths ( & build_state, path) ;
590- register_watches ( & mut watcher, & current_watch_paths) ;
636+ "Finished initial compilation" ,
637+ Some ( "initial" ) ,
638+ result,
639+ ) ;
640+ }
591641
592642 async_watch ( AsyncWatchArgs {
593643 watcher : & mut watcher,
594644 current_watch_paths,
595645 initial_build_state : build_state,
596646 q : consumer,
647+ ctrlc_pressed,
597648 path,
598649 show_progress,
599650 filter,
@@ -735,12 +786,11 @@ mod tests {
735786 }
736787
737788 #[ test]
738- fn clears_screen_only_for_non_initial_interactive_rebuilds ( ) {
739- assert ! ( should_clear_screen( true , true , false , false ) ) ;
740- assert ! ( !should_clear_screen( true , true , false , true ) ) ;
741- assert ! ( !should_clear_screen( true , true , true , false ) ) ;
742- assert ! ( !should_clear_screen( true , false , false , false ) ) ;
743- assert ! ( !should_clear_screen( false , true , false , false ) ) ;
789+ fn clears_screen_only_for_interactive_rebuilds ( ) {
790+ assert ! ( should_clear_screen( true , true , false ) ) ;
791+ assert ! ( !should_clear_screen( true , true , true ) ) ;
792+ assert ! ( !should_clear_screen( true , false , false ) ) ;
793+ assert ! ( !should_clear_screen( false , true , false ) ) ;
744794 }
745795
746796 #[ test]
0 commit comments