diff --git a/src/commands/watch.rs b/src/commands/watch.rs index 3a20b86794..ca2a5fc617 100644 --- a/src/commands/watch.rs +++ b/src/commands/watch.rs @@ -101,10 +101,13 @@ impl WatchCommand { // We create the build-related objects even if skip_build is on - the cost is insignificant, // and it saves having to check options all over the place. - let (artifact_tx, artifact_rx) = tokio::sync::watch::channel(Uuid::new_v4()); + let (artifact_tx, artifact_rx) = + tokio::sync::watch::channel((Uuid::new_v4(), "artifact".to_owned())); let (pause_tx, pause_rx) = tokio::sync::mpsc::channel(1); - let (source_code_tx, source_code_rx) = tokio::sync::watch::channel(Uuid::new_v4()); - let (manifest_tx, manifest_rx) = tokio::sync::watch::channel(Uuid::new_v4()); + let (source_code_tx, source_code_rx) = + tokio::sync::watch::channel((Uuid::new_v4(), "THIS_IS_ THE-FIRST BUILD".to_owned())); + let (manifest_tx, manifest_rx) = + tokio::sync::watch::channel((Uuid::new_v4(), "manifest".to_owned())); let (stop_tx, stop_rx) = tokio::sync::watch::channel(Uuid::new_v4()); let mut buildifier = Buildifier { @@ -239,7 +242,7 @@ impl WatchCommand { manifest_file: &Path, manifest_dir: &Path, filter_factory: Box, - notifier: Arc>, + notifier: Arc>, impact_description: &'static str, ) -> anyhow::Result<(ReconfigurableWatcher, tokio::task::JoinHandle<()>)> { let rtf = RuntimeConfigFactory { @@ -269,7 +272,7 @@ pub struct RuntimeConfigFactory { manifest_file: PathBuf, manifest_dir: PathBuf, filter_factory: Box, - notifier: Arc>, + notifier: Arc>, impact_description: &'static str, debounce: Duration, } @@ -295,19 +298,19 @@ impl RuntimeConfigFactory { } // This is the watchexec action handler that triggers the Uppificator -// to reload or Builidifer to rebuild by sending a notification. +// to reload or Buildifier to rebuild by sending a notification. // It is a struct rather than a closure because this makes it easier // for the compiler to confirm that all the data lives long enough and // is thread-safe for async stuff. struct NotifyOnFileChange { despurifier: despurifier::Despurifier, - notifier: Arc>, + notifier: Arc>, impact_description: &'static str, } impl NotifyOnFileChange { fn new( - notifier: Arc>, + notifier: Arc>, impact_description: &'static str, ) -> Self { Self { @@ -331,7 +334,7 @@ impl watchexec::handler::Handler for NotifyOnFileChan self.impact_description, paths_of(&action) ); - _ = self.notifier.send(Uuid::new_v4()); + _ = self.notifier.send((Uuid::new_v4(), paths_of(&action))); } action.outcome(watchexec::action::Outcome::DoNothing); Ok::<(), Box<(dyn std::error::Error + 'static)>>(()) diff --git a/src/commands/watch/buildifier.rs b/src/commands/watch/buildifier.rs index 74bd433d07..4d59c52081 100644 --- a/src/commands/watch/buildifier.rs +++ b/src/commands/watch/buildifier.rs @@ -1,15 +1,15 @@ use command_group::AsyncCommandGroup; -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; use uuid::Uuid; use super::uppificator::Pause; - +#[derive(Debug)] pub(crate) struct Buildifier { pub spin_bin: PathBuf, pub manifest: PathBuf, pub clear_screen: bool, pub has_ever_built: bool, - pub watched_changes: tokio::sync::watch::Receiver, // TODO: refine which component(s) a change affects + pub watched_changes: tokio::sync::watch::Receiver<(Uuid, String)>, // TODO: refine which component(s) a change affects pub uppificator_pauser: tokio::sync::mpsc::Sender, } @@ -28,9 +28,13 @@ impl Buildifier { break; } - let build_result = self.build_once().await; + let (_, ref changed_path) = self.watched_changes.borrow_and_update().clone(); + tracing::debug!("Detected changes in: {:?}", changed_path); + + let build_component_result = self.build_component(changed_path).await; + if !self.has_ever_built { - self.has_ever_built = matches!(build_result, Ok(true)); + self.has_ever_built = matches!(build_component_result, Ok(true)); } if self.has_ever_built { @@ -45,27 +49,73 @@ impl Buildifier { } } - pub(crate) async fn build_once(&mut self) -> std::io::Result { + pub(crate) async fn build_component(&mut self, component_path: &str) -> std::io::Result { + let manifest = spin_manifest::manifest_from_file(&self.manifest).unwrap(); + + let id_to_workdir: HashMap<_, _> = manifest + .components + .iter() + .filter_map(|(id, component)| { + component.build.as_ref().map(|build_config| { + ( + id.as_ref(), + build_config.workdir.clone().unwrap_or("".to_owned()), + ) + }) + }) + .collect(); + + let component_paths: Vec<&str> = component_path.split(", ").collect(); + let mut component_ids = Vec::new(); + let source_dir = id_to_workdir + .iter() + .find(|(_, value)| value.is_empty()) + .map(|(key, _)| key); + + for changed_path in &component_paths { + for (inner_id, workdir) in id_to_workdir.iter() { + if !workdir.is_empty() && changed_path.contains(workdir) { + component_ids.push(inner_id); + break; + } + } + } + + if component_ids.len() != component_paths.len() { + component_ids.push(source_dir.unwrap()); + } + loop { let mut cmd = tokio::process::Command::new(&self.spin_bin); - cmd.arg("build").arg("-f").arg(&self.manifest); + + if component_paths.contains(&"THIS_IS_ THE-FIRST BUILD") + || component_paths.contains(&self.manifest.to_str().unwrap()) + { + cmd.arg("build").arg("-f").arg(&self.manifest); + } else { + cmd.arg("build") + .arg("-c") + .args(&component_ids) + .arg("-f") + .arg(&self.manifest); + } + let mut child = cmd.group_spawn()?; tokio::select! { - exit_status = child.wait() => { - // It reports its own errors so we only care about success or failure (and then only for - // the initial build). - return Ok(exit_status?.success()); - } - _ = self.watched_changes.changed() => { - tracing::debug!("Cancelling build as there are new changes to process"); - child.kill()?; - if self.clear_screen { - _ = clearscreen::clear(); + exit_status = child.wait() => { + // It reports its own errors so we only care about success or failure (and then only for + // the initial build). + return Ok(exit_status?.success()); + } + _ = self.watched_changes.changed() => { + tracing::debug!("Cancelling build as there are new changes to process"); + child.kill()?; + if self.clear_screen { + _ = clearscreen::clear(); + } + continue; } - continue; - } - } } } diff --git a/src/commands/watch/reconfiguriser.rs b/src/commands/watch/reconfiguriser.rs index 9107a0f201..d0a69ced63 100644 --- a/src/commands/watch/reconfiguriser.rs +++ b/src/commands/watch/reconfiguriser.rs @@ -1,7 +1,7 @@ use uuid::Uuid; pub(crate) struct Reconfiguriser { - pub manifest_changes: tokio::sync::watch::Receiver, + pub manifest_changes: tokio::sync::watch::Receiver<(Uuid, String)>, pub artifact_watcher: super::ReconfigurableWatcher, pub build_watcher: super::ReconfigurableWatcher, } diff --git a/src/commands/watch/uppificator.rs b/src/commands/watch/uppificator.rs index 2e22bc39e0..fd3abbc266 100644 --- a/src/commands/watch/uppificator.rs +++ b/src/commands/watch/uppificator.rs @@ -7,7 +7,7 @@ pub(crate) struct Uppificator { pub up_args: Vec, pub manifest: PathBuf, pub clear_screen: bool, - pub watched_changes: tokio::sync::watch::Receiver, + pub watched_changes: tokio::sync::watch::Receiver<(Uuid, String)>, pub pause_feed: tokio::sync::mpsc::Receiver, pub stopper: tokio::sync::watch::Receiver, }