diff --git a/src/justfile.rs b/src/justfile.rs index 501134821e..ca112b77aa 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -1,3 +1,4 @@ +use parallel::Ran; use std::sync::Arc; use {super::*, serde::Serialize}; @@ -254,14 +255,10 @@ impl<'src> Justfile<'src> { search, }; - // let mut ran = BTreeSet::new(); + let ran = Ran::new(); parallel::task_scope(config.parallel, |scope| { for (recipe, arguments) in grouped { - scope.run(|| { - Self::run_recipe( - &context, recipe, arguments, &dotenv, search, /*&mut ran*/ - ) - })?; + scope.run(|| Self::run_recipe(&context, recipe, arguments, &dotenv, search, &ran))?; } Ok(()) })?; @@ -287,16 +284,16 @@ impl<'src> Justfile<'src> { arguments: &[&str], dotenv: &BTreeMap, search: &Search, - // ran: &mut BTreeSet>, + ran: &Ran, ) -> RunResult<'src, ()> { let mut invocation = vec![recipe.name().to_owned()]; for argument in arguments { invocation.push((*argument).to_string()); } - // if ran.contains(&invocation) { - // return Ok(()); - // } + if ran.contains(&invocation) { + return Ok(()); + } let (outer, positional) = Evaluator::evaluate_parameters( context.config, @@ -327,7 +324,7 @@ impl<'src> Justfile<'src> { &arguments.iter().map(String::as_ref).collect::>(), dotenv, search, - // ran, + ran, ) })?; } @@ -337,7 +334,7 @@ impl<'src> Justfile<'src> { recipe.run(context, dotenv, scope.child(), search, &positional)?; { - // let mut ran = BTreeSet::new(); + let ran = Ran::new(); parallel::task_scope(context.config.parallel, |scope| { for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) { @@ -351,22 +348,25 @@ impl<'src> Justfile<'src> { ); } - scope.run(move || { - Self::run_recipe( - context, - recipe, - &evaluated.iter().map(String::as_ref).collect::>(), - dotenv, - search, - // &mut ran, - ) + scope.run({ + let ran = ran.clone(); + move || { + Self::run_recipe( + context, + recipe, + &evaluated.iter().map(String::as_ref).collect::>(), + dotenv, + search, + &ran, + ) + } })?; } Ok(()) })?; } - // ran.insert(invocation); + ran.insert(invocation); Ok(()) } diff --git a/src/parallel.rs b/src/parallel.rs index d38990e8c7..a30e91d518 100644 --- a/src/parallel.rs +++ b/src/parallel.rs @@ -1,5 +1,7 @@ use crate::RunResult; use crossbeam::thread; +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; type ScopeResult<'src> = RunResult<'src, ()>; @@ -10,6 +12,7 @@ pub(crate) struct TaskScope<'env, 'src, 'inner_scope> { } impl<'env, 'src, 'inner_scope> TaskScope<'env, 'src, 'inner_scope> { + /// run the given task, either directly synchronously or spawned in a background thread. pub(crate) fn run<'scope, F>(&'scope mut self, f: F) -> ScopeResult<'src> where 'src: 'env, @@ -31,6 +34,9 @@ impl<'env, 'src, 'inner_scope> TaskScope<'env, 'src, 'inner_scope> { /// run. The first error will be returned as result of this `task_scope`. /// /// Only works for tasks with an `RunResult<'src, ()>` result type. +/// +/// When `parallel` is set to `false`, the tasks are directly executed +/// when calling `run`. pub(crate) fn task_scope<'env, 'src, F>(parallel: bool, f: F) -> ScopeResult<'src> where F: for<'inner_scope> FnOnce(&mut TaskScope<'env, 'src, 'inner_scope>) -> ScopeResult<'src>, @@ -51,3 +57,41 @@ where }) .expect("could not join thread") } + +/// track which tasks were already run, across all running threads. +#[derive(Clone)] +pub(crate) struct Ran(Arc>>>); + +impl Ran { + pub(crate) fn new() -> Self { + Self(Arc::new(Mutex::new(HashSet::new()))) + } + + pub(crate) fn insert(&self, args: Vec) { + let mut ran = self.0.lock().unwrap(); + ran.insert(args); + } + + pub(crate) fn contains(&self, args: &Vec) -> bool { + let ran = self.0.lock().unwrap(); + ran.contains(args) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ran_empty() { + let r = Ran::new(); + assert!(!r.contains(&vec![])); + } + + #[test] + fn test_ran_insert_contains() { + let r = Ran::new(); + r.insert(vec!["1".into(), "2".into(), "3".into()]); + assert!(r.contains(&vec!["1".into(), "2".into(), "3".into()])); + } +}