diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2023-06-14 18:29:19 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-14 22:29:19 +0000 |
commit | 84c793275b324c262dde02a432462565584c83f7 (patch) | |
tree | a5995a842fabb37b2ecdab794dbbce76b530ac3a /cli/util/file_watcher.rs | |
parent | 48c6f7178703d448da229a5baf19efb403416da0 (diff) |
fix: reload config files on watcher restarts (#19487)
Closes #19468
Diffstat (limited to 'cli/util/file_watcher.rs')
-rw-r--r-- | cli/util/file_watcher.rs | 200 |
1 files changed, 36 insertions, 164 deletions
diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 1ad5e9ba0..ddeedb741 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::args::Flags; use crate::colors; use crate::util::fs::canonicalize_path; @@ -21,6 +22,7 @@ use std::time::Duration; use tokio::select; use tokio::sync::mpsc; use tokio::sync::mpsc::UnboundedReceiver; +use tokio::sync::mpsc::UnboundedSender; use tokio::time::sleep; const CLEAR_SCREEN: &str = "\x1B[2J\x1B[1;1H"; @@ -66,7 +68,7 @@ impl DebouncedReceiver { } } -async fn error_handler<F>(watch_future: F) +async fn error_handler<F>(watch_future: F) -> bool where F: Future<Output = Result<(), AnyError>>, { @@ -81,42 +83,9 @@ where colors::red_bold("error"), error_string.trim_start_matches("error: ") ); - } -} - -pub enum ResolutionResult<T> { - Restart { - paths_to_watch: Vec<PathBuf>, - result: Result<T, AnyError>, - }, - Ignore, -} - -async fn next_restart<R, T, F>( - resolver: &mut R, - debounced_receiver: &mut DebouncedReceiver, -) -> (Vec<PathBuf>, Result<T, AnyError>) -where - R: FnMut(Option<Vec<PathBuf>>) -> F, - F: Future<Output = ResolutionResult<T>>, -{ - loop { - let changed = debounced_receiver.recv().await; - match resolver(changed).await { - ResolutionResult::Ignore => { - log::debug!("File change ignored") - } - ResolutionResult::Restart { - mut paths_to_watch, - result, - } => { - // watch the current directory when empty - if paths_to_watch.is_empty() { - paths_to_watch.push(PathBuf::from(".")); - } - return (paths_to_watch, result); - } - } + false + } else { + true } } @@ -139,138 +108,26 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { } } -/// Creates a file watcher, which will call `resolver` with every file change. -/// -/// - `resolver` is used for resolving file paths to be watched at every restarting -/// of the watcher, and can also return a value to be passed to `operation`. -/// It returns a [`ResolutionResult`], which can either instruct the watcher to restart or ignore the change. -/// This always contains paths to watch; -/// -/// - `operation` is the actual operation we want to run every time the watcher detects file -/// changes. For example, in the case where we would like to bundle, then `operation` would -/// have the logic for it like bundling the code. -pub async fn watch_func<R, O, T, F1, F2>( - mut resolver: R, - mut operation: O, - print_config: PrintConfig, -) -> Result<(), AnyError> -where - R: FnMut(Option<Vec<PathBuf>>) -> F1, - O: FnMut(T) -> F2, - F1: Future<Output = ResolutionResult<T>>, - F2: Future<Output = Result<(), AnyError>>, -{ - let (sender, mut receiver) = DebouncedReceiver::new_with_sender(); - - let PrintConfig { - job_name, - clear_screen, - } = print_config; - - // Store previous data. If module resolution fails at some point, the watcher will try to - // continue watching files using these data. - let mut paths_to_watch; - let mut resolution_result; - - let print_after_restart = create_print_after_restart_fn(clear_screen); - - match resolver(None).await { - ResolutionResult::Ignore => { - // The only situation where it makes sense to ignore the initial 'change' - // is if the command isn't supposed to do anything until something changes, - // e.g. a variant of `deno test` which doesn't run the entire test suite to start with, - // but instead does nothing until you make a change. - // - // In that case, this is probably the correct output. - info!( - "{} Waiting for file changes...", - colors::intense_blue("Watcher"), - ); - - let (paths, result) = next_restart(&mut resolver, &mut receiver).await; - paths_to_watch = paths; - resolution_result = result; - - print_after_restart(); - } - ResolutionResult::Restart { - paths_to_watch: mut paths, - result, - } => { - // watch the current directory when empty - if paths.is_empty() { - paths.push(PathBuf::from(".")); - } - paths_to_watch = paths; - resolution_result = result; - } - }; - - info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); - - loop { - let mut watcher = new_watcher(sender.clone())?; - add_paths_to_watcher(&mut watcher, &paths_to_watch); - - match resolution_result { - Ok(operation_arg) => { - let fut = error_handler(operation(operation_arg)); - select! { - (paths, result) = next_restart(&mut resolver, &mut receiver) => { - if result.is_ok() { - paths_to_watch = paths; - } - resolution_result = result; - - print_after_restart(); - continue; - }, - _ = fut => {}, - }; - - info!( - "{} {} finished. Restarting on file change...", - colors::intense_blue("Watcher"), - job_name, - ); - } - Err(error) => { - eprintln!("{}: {}", colors::red_bold("error"), error); - info!( - "{} {} failed. Restarting on file change...", - colors::intense_blue("Watcher"), - job_name, - ); - } - } - - let (paths, result) = next_restart(&mut resolver, &mut receiver).await; - if result.is_ok() { - paths_to_watch = paths; - } - resolution_result = result; - - print_after_restart(); - - drop(watcher); - } -} - /// Creates a file watcher. /// /// - `operation` is the actual operation we want to run every time the watcher detects file /// changes. For example, in the case where we would like to bundle, then `operation` would /// have the logic for it like bundling the code. -pub async fn watch_func2<T: Clone, O, F>( - mut paths_to_watch_receiver: UnboundedReceiver<Vec<PathBuf>>, - mut operation: O, - operation_args: T, +pub async fn watch_func<O, F>( + mut flags: Flags, print_config: PrintConfig, + mut operation: O, ) -> Result<(), AnyError> where - O: FnMut(T) -> Result<F, AnyError>, + O: FnMut( + Flags, + UnboundedSender<Vec<PathBuf>>, + Option<Vec<PathBuf>>, + ) -> Result<F, AnyError>, F: Future<Output = Result<(), AnyError>>, { + let (paths_to_watch_sender, mut paths_to_watch_receiver) = + tokio::sync::mpsc::unbounded_channel(); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); @@ -303,6 +160,7 @@ where } } + let mut changed_paths = None; loop { // We may need to give the runtime a tick to settle, as cancellations may need to propagate // to tasks. We choose yielding 10 times to the runtime as a decent heuristic. If watch tests @@ -320,21 +178,34 @@ where add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; - let operation_future = error_handler(operation(operation_args.clone())?); + let operation_future = error_handler(operation( + flags.clone(), + paths_to_watch_sender.clone(), + changed_paths.take(), + )?); + + // don't reload dependencies after the first run + flags.reload = false; select! { _ = receiver_future => {}, - _ = watcher_receiver.recv() => { + received_changed_paths = watcher_receiver.recv() => { print_after_restart(); + changed_paths = received_changed_paths; continue; }, - _ = operation_future => { + success = operation_future => { consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); // TODO(bartlomieju): print exit code here? info!( - "{} {} finished. Restarting on file change...", + "{} {} {}. Restarting on file change...", colors::intense_blue("Watcher"), job_name, + if success { + "finished" + } else { + "failed" + } ); }, }; @@ -347,8 +218,9 @@ where }; select! { _ = receiver_future => {}, - _ = watcher_receiver.recv() => { + received_changed_paths = watcher_receiver.recv() => { print_after_restart(); + changed_paths = received_changed_paths; continue; }, }; |