Skip to content

Commit

Permalink
Watch include exclude (#94)
Browse files Browse the repository at this point in the history
* Add `--watch` option to `serve` and `watch`.

The new option allows to watch specific folder(s) or file(s) when using the `serve` or `watch` subcommand.

* Allocate vec with capacity to prevent reallocation.
  • Loading branch information
malobre authored Jan 25, 2021
1 parent ba813c7 commit 670ccf9
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 30 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ changelog
This changelog follows the patterns described here: https://keepachangelog.com/en/1.0.0/.

## Unreleased
### added
- Closed [#93](https://github.com/thedodd/trunk/issues/93): The `watch` and `serve` subcommands can now watch specific folder(s) or file(s) through the new `--watch <path>...` option.

## 0.7.4
### fixed
Expand Down
4 changes: 3 additions & 1 deletion Trunk.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ dist = "dist"
public_url = "/"

[watch]
# Additional paths to ignore.
# Paths to watch, defaults to build target parent folder.
path = ["src"]
# Paths to ignore.
ignore = []

[serve]
Expand Down
24 changes: 19 additions & 5 deletions src/config/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ pub struct ConfigOptsBuild {
/// Config options for the watch system.
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
pub struct ConfigOptsWatch {
/// Additional paths to ignore [default: []]
#[structopt(short, long, parse(from_os_str))]
/// Watch specific file(s) or folder(s) [default: build target parent folder]
#[structopt(short, long, parse(from_os_str), value_name = "path")]
pub watch: Option<Vec<PathBuf>>,
/// Paths to ignore [default: []]
#[structopt(short, long, parse(from_os_str), value_name = "path")]
pub ignore: Option<Vec<PathBuf>>,
}

Expand Down Expand Up @@ -159,7 +162,10 @@ impl ConfigOpts {
}

fn cli_opts_layer_watch(cli: ConfigOptsWatch, cfg_base: Self) -> Self {
let opts = ConfigOptsWatch { ignore: cli.ignore };
let opts = ConfigOptsWatch {
watch: cli.watch,
ignore: cli.ignore,
};
let cfg = ConfigOpts {
build: None,
watch: Some(opts),
Expand Down Expand Up @@ -239,8 +245,15 @@ impl ConfigOpts {
});
});
cfg.watch.iter_mut().for_each(|watch| {
watch.ignore.iter_mut().for_each(|ignores_vec| {
ignores_vec.iter_mut().for_each(|ignore_path| {
watch.watch.iter_mut().for_each(|paths| {
paths.iter_mut().for_each(|path| {
if !path.is_absolute() {
*path = parent.join(&path);
}
});
});
watch.ignore.iter_mut().for_each(|ignore_vec| {
ignore_vec.iter_mut().for_each(|ignore_path| {
if !ignore_path.is_absolute() {
*ignore_path = parent.join(&ignore_path);
}
Expand Down Expand Up @@ -292,6 +305,7 @@ impl ConfigOpts {
(None, None) => None,
(Some(val), None) | (None, Some(val)) => Some(val),
(Some(l), Some(mut g)) => {
g.watch = g.watch.or(l.watch);
g.ignore = g.ignore.or(l.ignore);
Some(g)
}
Expand Down
28 changes: 24 additions & 4 deletions src/config/rt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use http_types::Url;

use crate::config::{ConfigOptsBuild, ConfigOptsClean, ConfigOptsProxy, ConfigOptsServe, ConfigOptsWatch};
Expand Down Expand Up @@ -44,16 +44,36 @@ impl RtcBuild {
pub struct RtcWatch {
/// Runtime config for the build system.
pub build: Arc<RtcBuild>,
/// Additional paths to ignore.
pub ignore: Vec<PathBuf>,
/// Paths to watch, defaults to the build target parent directory.
pub paths: Vec<PathBuf>,
/// Paths to ignore.
pub ignored_paths: Vec<PathBuf>,
}

impl RtcWatch {
pub(super) fn new(build_opts: ConfigOptsBuild, opts: ConfigOptsWatch) -> Result<Self> {
let build = Arc::new(RtcBuild::new(build_opts)?);

let paths = {
let mut paths = opts.watch.unwrap_or_default();

if paths.is_empty() {
paths.push(
build
.target
.parent()
.ok_or_else(|| anyhow!("couldn't get parent of {:?}", build.target))?
.to_path_buf(),
)
}

paths
};

Ok(Self {
build,
ignore: opts.ignore.unwrap_or_default(),
paths,
ignored_paths: opts.ignore.unwrap_or_default(),
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/pipelines/rust_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct RustApp {
progress: ProgressBar,
/// All metadata associated with the target Cargo project.
manifest: CargoMetadata,
/// An optional channel to be used to communicate ignore paths to the watcher.
/// An optional channel to be used to communicate paths to ignore back to the watcher.
ignore_chan: Option<Sender<PathBuf>>,
/// An optional binary name which will cause cargo & wasm-bindgen to process only the target
/// binary.
Expand Down
50 changes: 31 additions & 19 deletions src/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub struct WatchSystem {
/// The build system.
build: BuildSystem,
/// The current vector of paths to be ignored.
ignores: Vec<PathBuf>,
ignored_paths: Vec<PathBuf>,
/// A channel of FS watch events.
watch_rx: Receiver<DebouncedEvent>,
/// A channel of new paths to ignore from the build system.
Expand All @@ -35,22 +35,26 @@ impl WatchSystem {
let (build_tx, build_rx) = channel(1);

// Process ignore list.
let mut ignores = cfg.ignore.iter().try_fold(vec![], |mut acc, path| -> Result<Vec<PathBuf>> {
let abs_path = path.canonicalize().map_err(|err| anyhow!("invalid path provided: {}", err))?;
acc.push(abs_path);
Ok(acc)
})?;
ignores.append(&mut vec![cfg.build.dist.clone()]);
let mut ignored_paths =
cfg.ignored_paths
.iter()
.try_fold(Vec::with_capacity(cfg.ignored_paths.len() + 1), |mut acc, path| -> Result<Vec<PathBuf>> {
let abs_path = path.canonicalize().map_err(|err| anyhow!("invalid path provided: {}", err))?;
acc.push(abs_path);
Ok(acc)
})?;

ignored_paths.push(cfg.build.dist.clone());

// Build the watcher.
let _watcher = build_watcher(watch_tx)?;
let _watcher = build_watcher(watch_tx, cfg.paths.clone())?;

// Build dependencies.
let build = BuildSystem::new(cfg.build.clone(), progress.clone(), Some(build_tx)).await?;
Ok(Self {
progress,
build,
ignores,
ignored_paths,
watch_rx,
build_rx,
_watcher,
Expand Down Expand Up @@ -84,33 +88,41 @@ impl WatchSystem {
DebouncedEvent::Create(path) | DebouncedEvent::Write(path) | DebouncedEvent::Remove(path) | DebouncedEvent::Rename(_, path) => path,
_ => return,
};
for path in ev_path.ancestors() {
if self.ignores.iter().map(|p| p.as_path()).any(|p| p == path) {
return; // Don't emit a notification if ignored.
}

if ev_path
.ancestors()
.any(|path| self.ignored_paths.iter().any(|ignored_path| ignored_path == path))
{
return; // Don't emit a notification if path is ignored.
}

if let Err(err) = self.build.build().await {
self.progress.println(format!("{}", err));
}
}

fn update_ignore_list(&mut self, path: PathBuf) {
if !self.ignores.contains(&path) {
self.ignores.push(path);
if !self.ignored_paths.contains(&path) {
self.ignored_paths.push(path);
}
}
}

fn build_watcher(mut watch_tx: Sender<DebouncedEvent>) -> Result<(JoinHandle<()>, RecommendedWatcher)> {
fn build_watcher(mut watch_tx: Sender<DebouncedEvent>, paths: Vec<PathBuf>) -> Result<(JoinHandle<()>, RecommendedWatcher)> {
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = watcher(tx, std::time::Duration::from_secs(1)).context("failed to build file system watcher")?;
watcher
.watch(".", RecursiveMode::Recursive)
.context("failed to watch CWD for file system changes")?;

for path in paths {
watcher
.watch(path.clone(), RecursiveMode::Recursive)
.context(format!("failed to watch {:?} for file system changes", path))?;
}

let handle = spawn_blocking(move || loop {
if let Ok(event) = rx.recv() {
let _ = watch_tx.try_send(event);
}
});

Ok((handle, watcher))
}

0 comments on commit 670ccf9

Please # to comment.