Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Watch include exclude #94

Merged
merged 3 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
};
Comment on lines +47 to +71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this will not include the target dir's parent dir if it is not explicitly declared. That's fine. Just adding a note here. After I merge this, I'll update the docs to state that info.


Ok(Self {
build,
ignore: opts.ignore.unwrap_or_default(),
paths,
ignored_paths: opts.ignore.unwrap_or_default(),
})
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/pipelines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const ATTR_REL: &str = "rel";
const SNIPPETS_DIR: &str = "snippets";
const TRUNK_ID: &str = "data-trunk-id";

/// A model of all of the supported Trunk asset links expressed in the source HTML as `<trunk-link/>` elements.
/// A model of all of the supported Trunk asset links expressed in the source HTML as
/// `<trunk-link/>` elements.
///
/// Trunk will remove all `<trunk-link .../>` elements found in the HTML. It is the responsibility
/// of each pipeline to implement a pipeline finalizer method for its pipeline output in order to
Expand Down Expand Up @@ -180,7 +181,8 @@ impl AssetFile {
Ok(file_path)
}

/// Copy this asset to the target dir after hashing its contents & updating the filename with the hash.
/// Copy this asset to the target dir after hashing its contents & updating the filename with
/// the hash.
pub async fn copy_with_hash(&self, to_dir: &Path) -> Result<HashedFileOutput> {
let bytes = fs::read(&self.path)
.await
Expand Down
5 changes: 3 additions & 2 deletions src/pipelines/rust_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ 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.
/// An optional binary name which will cause cargo & wasm-bindgen to process only the target
/// binary.
bin: Option<String>,
}

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))
}