The library which powers Watchexec CLI and other tools.
- API documentation.
- Licensed under Apache 2.0.
- Status: maintained.
Here's a complete example showing some of the library's features:
use miette::{IntoDiagnostic, Result};
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use watchexec::{
command::{Command, Program, Shell},
job::CommandState,
Watchexec,
};
use watchexec_events::{Event, Priority};
use watchexec_signals::Signal;
#[tokio::main]
async fn main() -> Result<()> {
// this is okay to start with, but Watchexec logs a LOT of data,
// even at error level. you will quickly want to filter it down.
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
// initialise Watchexec with a simple initial action handler
let job = Arc::new(Mutex::new(None));
let wx = Watchexec::new({
let outerjob = job.clone();
move |mut action| {
let (_, job) = action.create_job(Arc::new(Command {
program: Program::Shell {
shell: Shell::new("bash"),
command: "
echo 'Hello world'
trap 'echo Not quitting yet!' TERM
read
"
.into(),
args: Vec::new(),
},
options: Default::default(),
}));
// store the job outside this closure too
*outerjob.lock().unwrap() = Some(job.clone());
// block SIGINT
#[cfg(unix)]
job.set_spawn_hook(|cmd, _| {
use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal};
unsafe {
cmd.pre_exec(|| {
let mut newset = SigSet::empty();
newset.add(Signal::SIGINT);
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?;
Ok(())
});
}
});
// start the command
job.start();
action
}
})?;
// start the engine
let main = wx.main();
// send an event to start
wx.send_event(Event::default(), Priority::Urgent)
.await
.unwrap();
// ^ this will cause the action handler we've defined above to run,
// creating and starting our little bash program, and storing it in the mutex
// spin until we've got the job
while job.lock().unwrap().is_none() {
tokio::task::yield_now().await;
}
// watch the job and restart it when it exits
let job = job.lock().unwrap().clone().unwrap();
let auto_restart = tokio::spawn(async move {
loop {
job.to_wait().await;
job.run(|context| {
if let CommandState::Finished {
status,
started,
finished,
} = context.current
{
let duration = *finished - *started;
eprintln!("[Program stopped with {status:?}; ran for {duration:?}]")
}
})
.await;
eprintln!("[Restarting...]");
job.start().await;
}
});
// now we change what the action does:
let auto_restart_abort = auto_restart.abort_handle();
wx.config.on_action(move |mut action| {
// if we get Ctrl-C on the Watchexec instance, we quit
if action.signals().any(|sig| sig == Signal::Interrupt) {
eprintln!("[Quitting...]");
auto_restart_abort.abort();
action.quit_gracefully(Signal::ForceStop, Duration::ZERO);
return action;
}
// if the action was triggered by file events, gracefully stop the program
if action.paths().next().is_some() {
// watchexec can manage ("supervise") more than one program;
// here we only have one but we don't know its Id so we grab it out of the iterator
if let Some(job) = action.list_jobs().next().map(|(_, job)| job.clone()) {
eprintln!("[Asking program to stop...]");
job.stop_with_signal(Signal::Terminate, Duration::from_secs(5));
}
}
action
});
// and watch all files in the current directory:
wx.config.pathset(["."]);
// then keep running until Watchexec quits!
let _ = main.await.into_diagnostic()?;
auto_restart.abort();
Ok(())
}
Other examples:
- Only Commands: skip watching files, only use the supervisor.
- Only Events: never start any processes, only print events.
- Restart
cargo run
only whencargo build
succeeds
Though not its primary usecase, the library exposes most of its relatively standalone components, available to make other tools that are not Watchexec-shaped:
-
Event sources: Filesystem, Signals, Keyboard.
-
Finding a common prefix of a set of paths.
-
A Changeable type, which powers the "live" configuration system.
-
And more!
Filterers are split into their own crates, so they can be evolved independently:
-
The Globset filterer implements the default Watchexec CLI filtering, based on the regex crate's ignore mechanisms.
-
The Tagged filtererwas an experiment in creating a more powerful filtering solution, which could operate on every part of events, not just their paths, using a custom syntax. It is no longer maintained. -
The Ignore filterer implements ignore-file semantics, and especially supports trees of ignore files. It is used as a subfilterer in both of the main filterers above.
There are also separate, standalone crates used to build Watchexec which you can tap into:
-
Supervisor is Watchexec's process supervisor and command abstraction.
-
ClearScreen makes clearing the terminal screen in a cross-platform way easy by default, and provides advanced options to fit your usecase.
-
Command Group augments the std and tokio
Command
with support for process groups, portable between Unix and Windows. -
Event types contains the event types used by Watchexec, including the JSON format used for passing event data to child processes.
-
Signal types contains the signal types used by Watchexec.
-
Ignore files finds, parses, and interprets ignore files.
-
Project Origins finds the origin (or root) path of a project, and what kind of project it is.
Due to the unpredictability of dependencies changing their MSRV, this library no longer tries to keep to a minimum supported Rust version behind stable. Instead, it is assumed that developers use the latest stable at all times.
Applications that wish to support lower-than-stable Rust (such as the Watchexec CLI does) should:
- use a lock file
- recommend the use of
--locked
when installing from source - provide pre-built binaries (and Binstall support) for non-distro users
- avoid using newer features until some time has passed, to let distro users catch up
- consider recommending that distro-Rust users switch to distro
rustup
where available