Skip to content

Commit

Permalink
feat: terminates current running watcher upon config changes
Browse files Browse the repository at this point in the history
relates to #106
  • Loading branch information
cristianoliveira committed Jul 6, 2024
1 parent 6b74af0 commit f0d18fc
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .watch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
- "examples/*.yaml"
- "examples/*.yml"
ignore:
- "examples/reload-config-example.yml"
- "examples/workdir/ignored/**"
- "**/*.log"
run_on_init: true
Expand All @@ -72,4 +73,5 @@
- "**/*.nix"
ignore:
- "examples/workdir/**"
- "examples/reload-config-example.yml"
run_on_init: true
32 changes: 32 additions & 0 deletions examples/reload-config-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This example shows the auto reload feature
#
# This workflow efectivelly creates a dependency between tasks with conditionals
# I don't want to give ideas, but one can create some pretty crazy workflows
# maybe even turing complete.
#
#@#CHANGED#@
#
- name: task runs on init (1)
run:
- echo 'run me first'
- sed -i '1,7s/#THIS_WILL_CHANGE#@$/#CHANGED#@/' examples/reload-config-example.yml
- echo 1 > examples/workdir/trigger-first-task.txt
change:
- '__noop__'
# LOL! To setup a LOOP uncomment this line
# - examples/workdir/trigger-start.txt
run_on_init: true

- name: task that run at the end (3)
run:
- echo 'then me in third'
- cat examples/reload-config-example.yml
- echo 1 > examples/workdir/trigger-start.txt
change: "examples/workdir/trigger-second-task.txt"

- name: this task runs after on init (2)
run:
- echo 'then me in second'
- sed -i '1,7s/#CHANGED#@$/#THIS_WILL_CHANGE#@/' examples/reload-config-example.yml
- echo 1 > examples/workdir/trigger-second-task.txt
change: "examples/workdir/trigger-first-task.txt"
4 changes: 3 additions & 1 deletion examples/tasks-with-filepath-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
- "echo 'this file has changed: {{filepath}}'"
- "cat '{{filepath}}' || echo 'nothing to run'"
change: "examples/workdir/**/*"
ignore: "examples/workdir/ignored/**/*.txt"
ignore:
- "examples/workdir/ignored/**/*.txt"
- "examples/workdir/another_ignored_file.foo"
run_on_init: true

- name: more advanced usage of {{filepath}}
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions examples/workdir/trigger-first-task.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
1 change: 1 addition & 0 deletions examples/workdir/trigger-second-task.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
1 change: 1 addition & 0 deletions examples/workdir/trigger-start.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
68 changes: 57 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ mod workers;
mod yaml;

use cli::*;
use nix::{
libc::signal,
sys::signal::{self, Signal},
unistd::Pid,
};
use watches::Watches;

use serde_derive::Deserialize;
Expand Down Expand Up @@ -109,16 +114,20 @@ fn main() {

_ => {
let rules = if args.flag_config.is_empty() {
let default_filename = cli::watch::DEFAULT_FILENAME;
match rules::from_file(default_filename) {
Ok(rules) => rules,
Err(err) => {
match rules::from_file(&default_filename.replace(".yaml", ".yml")) {
Ok(rules) => rules,
Err(_) => error("Failed to read default config file", err),
}
}
}
rules::from_default_file_config().unwrap_or_else(|err| {
stdout::error(
&vec![
"Missing config file",
"Expected file .watch.yaml or .watch.yml to be present",
"in the current directory",
"Debugging:",
" - Did you forget to run 'fzz init'?",
]
.join("\n"),
);

error("Failed to read default config file", err);
})
} else {
match rules::from_file(&args.flag_config) {
Ok(rules) => rules,
Expand Down Expand Up @@ -157,17 +166,54 @@ fn main() {
execute_watch_command(Watches::new(rules), args);
}
}
}
};
}

pub fn execute_watch_command(watches: Watches, args: Args) {
let config_file_paths = if args.flag_config.is_empty() {
vec![
cli::watch::DEFAULT_FILENAME.replace("yaml", "yml"),
cli::watch::DEFAULT_FILENAME.to_string(),
]
} else {
vec![format!("{}", &args.flag_config)]
};

// This here restarts the watcher if the config file changes
let watcher_pid = std::process::id();
let th = std::thread::spawn(move || {
watcher::events(
config_file_paths,
|file_changed| {
stdout::warn(
&vec![
"The config file has changed while an instance was running.",
"Terminating the current watcher instance...",
&format!("Config file: {}", file_changed),
]
.join("\n"),
);

println!("Watcher PID: {}", watcher_pid);

match signal::kill(Pid::from_raw(watcher_pid as i32), Signal::SIGINT) {
Ok(_) => stdout::info("Terminating watcher..."),
Err(err) => panic!("Failed to terminate watcher forcefully.\nCause: {:?}", err),
}
},
false,
)
});

let verbose = args.flag_V;
let fail_fast = args.flag_fail_fast;
if args.flag_non_block {
execute(WatchNonBlockCommand::new(watches, verbose, fail_fast))
} else {
execute(WatchCommand::new(watches, verbose, fail_fast))
}

let _ = th.join().expect("Failed to join config watcher thread");
}

fn execute<T: Command>(command: T) {
Expand Down
11 changes: 11 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ pub fn from_file(filename: &str) -> Result<Vec<Rules>, String> {
}
}

pub fn from_default_file_config() -> Result<Vec<Rules>, String> {
let default_filename = cli::watch::DEFAULT_FILENAME;
match from_file(default_filename) {
Ok(rules) => Ok(rules),
Err(err) => match from_file(&default_filename.replace(".yaml", ".yml")) {
Ok(rules) => Ok(rules),
Err(_) => Err(format!("Failed to read default config file {}", err)),
},
}
}

fn pattern(pattern: &str) -> Pattern {
Pattern::new(&format!("**{}", pattern))
.expect(format!("Invalid glob pattern {}", pattern).as_str())
Expand Down
39 changes: 39 additions & 0 deletions tests/watcher_reloads_config_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::io::prelude::*;

#[path = "./common/lib.rs"]
mod setup;

#[test]
fn it_terminates_the_current_running_watcher_when_config_changes() {
setup::with_example(
setup::Options {
output_file: "it_terminates_the_current_running_watcher_when_config_changes.log",
example_file: "examples/reload-config-example.yml",
},
|fzz_cmd, mut output_log| {
let mut child = fzz_cmd
.arg("--non-block")
.arg("-V")
.spawn()
.expect("failed to spawn child");

defer!({
child.kill().expect("failed to kill child");
});

let mut output = String::new();
wait_until!(
{
output_log
.read_to_string(&mut output)
.expect("failed to read from file");

output.contains("The config file has changed")
&& output.contains("Terminating watcher...")
},
"No task in the example was configured with run_on_init {}",
output
);
},
);
}
8 changes: 5 additions & 3 deletions tests/watching_filtered_tasks_with_target_flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn test_it_filter_tasks_with_target_flag() {
setup::with_example(
setup::Options {
output_file: "test_it_filter_tasks_with_target_flag.log",
example_file: "examples/multiple-tasks.yml",
example_file: "examples/tasks-with-tags-to-filter.yml",
},
|fzz_cmd, mut output_log| {
let mut child = fzz_cmd
Expand Down Expand Up @@ -77,7 +77,7 @@ fn test_it_list_the_available_tasks_when_nothing_matches() {
setup::with_example(
setup::Options {
output_file: "test_it_list_the_available_tasks_when_nothing_matches.log",
example_file: "examples/multiple-tasks.yml",
example_file: "examples/tasks-with-tags-to-filter.yml",
},
|fzz_cmd, mut output_log| {
let mut child = fzz_cmd
Expand Down Expand Up @@ -114,7 +114,9 @@ Available targets:
run my lint @quick
Finished there is no task to run.
"
",
"failed to find the expected output: {}",
output
);
},
);
Expand Down

0 comments on commit f0d18fc

Please sign in to comment.