pub mod cargo;
pub mod wasm;
pub mod wasm_pack;
pub use cargo::CargoBuilder;
pub use wasm::WasmBuilder;
pub use wasm_pack::WasmPackBuilder;
use std::sync::Arc;
use concurrent_queue::ConcurrentQueue;
use foundation_core::valtron::QueueReadiness;
use foundation_core::valtron::run_background_job;
use foundation_nativeapis::valtron::CompositeReadiness;
use foundation_nativeapis::shared::WatchEvent;
use crate::watcher::FileChange;
use crate::ToolingError;
pub type BuildResult = std::result::Result<BuildOutput, ToolingError>;
#[derive(Debug, Clone)]
pub enum BuildOutput {
CheckPassed,
BuildComplete { binary: String },
}
pub trait ProjectBuilder: Send + Sync + std::fmt::Debug {
fn name(&self) -> &str;
fn should_build(&self, change: &FileChange) -> bool;
fn build(&self, change: &FileChange) -> BuildResult;
}
pub struct ProjectBuilderTask {
builders: Vec<Arc<dyn ProjectBuilder>>,
change_rx: foundation_core::synca::mpp::Receiver<WatchEvent>,
change_queue: Arc<ConcurrentQueue<FileChange>>,
change_ready: QueueReadiness<FileChange>,
result_queue: Arc<ConcurrentQueue<BuildResult>>,
result_ready: QueueReadiness<BuildResult>,
build_complete_queue: Arc<ConcurrentQueue<()>>,
stop_on_failure: bool,
}
impl ProjectBuilderTask {
pub fn new(
change_rx: foundation_core::synca::mpp::Receiver<WatchEvent>,
build_complete_queue: Arc<ConcurrentQueue<()>>,
) -> Self {
let change_queue = Arc::new(ConcurrentQueue::unbounded());
let result_queue = Arc::new(ConcurrentQueue::unbounded());
Self {
builders: Vec::new(),
change_rx,
change_queue: change_queue.clone(),
change_ready: QueueReadiness::new(change_queue),
result_queue: result_queue.clone(),
result_ready: QueueReadiness::new(result_queue),
build_complete_queue,
stop_on_failure: false,
}
}
pub fn builder(mut self, b: impl ProjectBuilder + 'static) -> Self {
self.builders.push(Arc::new(b));
self
}
pub fn with_stop_on_failure(mut self, stop: bool) -> Self {
self.stop_on_failure = stop;
self
}
pub fn readiness(&self) -> Arc<CompositeReadiness> {
Arc::new(CompositeReadiness::new(
Arc::new(self.change_ready.clone()),
Arc::new(self.result_ready.clone()),
))
}
}
impl ProjectBuilderTask {
fn drain_watch_events(&mut self) {
for event in self.change_rx.try_iter() {
let change: FileChange = (&event).into();
let _ = self.change_queue.push(change.clone());
for builder in &self.builders {
if builder.should_build(&change) {
self.submit_build(builder.clone(), change.clone());
}
}
}
}
fn submit_build(&self, builder: Arc<dyn ProjectBuilder>, _change: FileChange) {
let result_queue = self.result_queue.clone();
let complete_queue = self.build_complete_queue.clone();
run_background_job(move || {
tracing::info!("{}: building for {:?}", builder.name(), _change);
let result = builder.build(&_change);
let _ = result_queue.push(result);
let _ = complete_queue.push(());
}).expect("background job pool available");
}
fn drain_results(&mut self) -> Option<BuildResult> {
let mut last_error = None;
while let Ok(result) = self.result_queue.pop() {
match &result {
Ok(output) => tracing::info!("build complete: {output:?}"),
Err(e) => {
tracing::error!("build failed: {e}");
if self.stop_on_failure {
last_error = Some(result);
}
}
}
}
last_error
}
}
use foundation_core::valtron::{NoSpawner, TaskIterator, TaskStatus};
impl TaskIterator for ProjectBuilderTask {
type Ready = BuildResult;
type Pending = ();
type Spawner = NoSpawner;
fn next_status(&mut self) -> Option<TaskStatus<Self::Ready, Self::Pending, Self::Spawner>> {
self.drain_watch_events();
if let Some(error) = self.drain_results() {
return Some(TaskStatus::Ready(error));
}
Some(TaskStatus::Depends(self.readiness()))
}
}