diff --git a/Cargo.lock b/Cargo.lock
index 600a0b5dca..3930da8a04 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -31,6 +31,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -109,6 +115,73 @@ dependencies = [
"rustversion",
]
+[[package]]
+name = "crossbeam"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -324,6 +397,7 @@ dependencies = [
"camino",
"clap",
"cradle",
+ "crossbeam",
"ctrlc",
"derivative",
"dotenvy",
@@ -396,6 +470,15 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "nix"
version = "0.27.1"
@@ -574,6 +657,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
[[package]]
name = "semver"
version = "1.0.20"
diff --git a/Cargo.toml b/Cargo.toml
index f636509ae5..e8c33bfcbe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ atty = "0.2.0"
camino = "1.0.4"
clap = { version = "2.33.0", features = ["wrap_help"] }
ctrlc = { version = "3.1.1", features = ["termination"] }
+crossbeam = "0.8.2"
derivative = "2.0.0"
dotenvy = "0.15"
edit-distance = "2.0.0"
diff --git a/README.md b/README.md
index d070567c37..b01b2fc2df 100644
--- a/README.md
+++ b/README.md
@@ -1255,6 +1255,7 @@ Recipes may be annotated with attributes that change their behavior.
| `[macos]`1.8.0 | Enable recipe on MacOS. |
| `[no-cd]`1.9.0 | Don't change directory before executing recipe. |
| `[no-exit-message]`1.7.0 | Don't print an error message if recipe fails. |
+| `[parallel]`master | See [Parallel execution](#parallel-execution). |
| `[private]`1.10.0 | See [Private Recipes](#private-recipes). |
| `[unix]`1.8.0 | Enable recipe on Unixes. (Includes MacOS). |
| `[windows]`1.8.0 | Enable recipe on Windows. |
@@ -2143,6 +2144,32 @@ Available recipes:
This is useful for helper recipes which are only meant to be used as dependencies of other recipes.
+### Parallel execution
+
+`just` has two separate ways to enable parallel execution of tasks.
+
+Run the given recipes on the command line in parallel:
+
+```sh
+$ just --parallel recipe_1 recipe_2 recipe_3
+[...]
+```
+
+And using the `[parallel]` attribute, task dependencies are allowed to run in
+parallel:
+
+```just
+recipe_1:
+ sleep 1
+
+recipe_2:
+ sleep 2
+
+[parallel]
+foo: recipe_1 recipe_2
+ echo hello
+```
+
### Quiet Recipes
A recipe name may be prefixed with `@` to invert the meaning of `@` before each line:
diff --git a/completions/just.bash b/completions/just.bash
index 4647ceb05e..d7c5d79e68 100644
--- a/completions/just.bash
+++ b/completions/just.bash
@@ -20,7 +20,7 @@ _just() {
case "${cmd}" in
just)
- opts=" -n -q -u -v -e -l -h -V -f -d -c -s --check --yes --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --command-color --dump-format --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path ... "
+ opts=" -n -p -q -u -v -e -l -h -V -f -d -c -s --check --yes --dry-run --highlight --no-dotenv --no-highlight --parallel --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --command-color --dump-format --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path ... "
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
diff --git a/completions/just.elvish b/completions/just.elvish
index 51176476da..e8adb31391 100644
--- a/completions/just.elvish
+++ b/completions/just.elvish
@@ -41,6 +41,8 @@ edit:completion:arg-completer[just] = [@words]{
cand --highlight 'Highlight echoed recipe lines in bold'
cand --no-dotenv 'Don''t load `.env` file'
cand --no-highlight 'Don''t highlight echoed recipe lines in bold'
+ cand -p 'run given tasks in parallel'
+ cand --parallel 'run given tasks in parallel'
cand -q 'Suppress all output'
cand --quiet 'Suppress all output'
cand --shell-command 'Invoke with the shell used to run recipe lines and backticks'
diff --git a/completions/just.fish b/completions/just.fish
index aa126bdd7c..ebc42108e3 100644
--- a/completions/just.fish
+++ b/completions/just.fish
@@ -57,6 +57,7 @@ complete -c just -n "__fish_use_subcommand" -s n -l dry-run -d 'Print what just
complete -c just -n "__fish_use_subcommand" -l highlight -d 'Highlight echoed recipe lines in bold'
complete -c just -n "__fish_use_subcommand" -l no-dotenv -d 'Don\'t load `.env` file'
complete -c just -n "__fish_use_subcommand" -l no-highlight -d 'Don\'t highlight echoed recipe lines in bold'
+complete -c just -n "__fish_use_subcommand" -s p -l parallel -d 'run given tasks in parallel'
complete -c just -n "__fish_use_subcommand" -s q -l quiet -d 'Suppress all output'
complete -c just -n "__fish_use_subcommand" -l shell-command -d 'Invoke with the shell used to run recipe lines and backticks'
complete -c just -n "__fish_use_subcommand" -l clear-shell-args -d 'Clear shell arguments'
diff --git a/completions/just.powershell b/completions/just.powershell
index d2907e3c98..8d58f1ba38 100644
--- a/completions/just.powershell
+++ b/completions/just.powershell
@@ -46,6 +46,8 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--highlight', 'highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold')
[CompletionResult]::new('--no-dotenv', 'no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file')
[CompletionResult]::new('--no-highlight', 'no-highlight', [CompletionResultType]::ParameterName, 'Don''t highlight echoed recipe lines in bold')
+ [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'run given tasks in parallel')
+ [CompletionResult]::new('--parallel', 'parallel', [CompletionResultType]::ParameterName, 'run given tasks in parallel')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Suppress all output')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Suppress all output')
[CompletionResult]::new('--shell-command', 'shell-command', [CompletionResultType]::ParameterName, 'Invoke with the shell used to run recipe lines and backticks')
diff --git a/completions/just.zsh b/completions/just.zsh
index b2ee3d6591..d1bfd67efa 100644
--- a/completions/just.zsh
+++ b/completions/just.zsh
@@ -42,6 +42,8 @@ _just() {
'--highlight[Highlight echoed recipe lines in bold]' \
'--no-dotenv[Don'\''t load `.env` file]' \
'--no-highlight[Don'\''t highlight echoed recipe lines in bold]' \
+'-p[run given tasks in parallel]' \
+'--parallel[run given tasks in parallel]' \
'(-n --dry-run)-q[Suppress all output]' \
'(-n --dry-run)--quiet[Suppress all output]' \
'--shell-command[Invoke with the shell used to run recipe lines and backticks]' \
diff --git a/justfile b/justfile
index 37b442bc4b..eca360791c 100755
--- a/justfile
+++ b/justfile
@@ -225,4 +225,3 @@ pwd:
# Local Variables:
# mode: makefile
# End:
-# vim: set ft=make :
diff --git a/src/alias.rs b/src/alias.rs
index 0c19fe5a5e..cc8f3a5f61 100644
--- a/src/alias.rs
+++ b/src/alias.rs
@@ -1,8 +1,9 @@
use super::*;
+use std::sync::Arc;
/// An alias, e.g. `name := target`
#[derive(Debug, PartialEq, Clone, Serialize)]
-pub(crate) struct Alias<'src, T = Rc>> {
+pub(crate) struct Alias<'src, T = Arc>> {
pub(crate) attributes: BTreeSet,
pub(crate) name: Name<'src>,
#[serde(
@@ -17,7 +18,7 @@ impl<'src> Alias<'src, Name<'src>> {
self.name.line
}
- pub(crate) fn resolve(self, target: Rc>) -> Alias<'src> {
+ pub(crate) fn resolve(self, target: Arc>) -> Alias<'src> {
assert_eq!(self.target.lexeme(), target.name.lexeme());
Alias {
diff --git a/src/analyzer.rs b/src/analyzer.rs
index ff4051c38b..913cc8421b 100644
--- a/src/analyzer.rs
+++ b/src/analyzer.rs
@@ -1,3 +1,4 @@
+use std::sync::Arc;
use {super::*, CompileErrorKind::*};
#[derive(Default)]
@@ -87,11 +88,11 @@ impl<'src> Analyzer<'src> {
first: recipes
.values()
.fold(None, |accumulator, next| match accumulator {
- None => Some(Rc::clone(next)),
+ None => Some(Arc::clone(next)),
Some(previous) => Some(if previous.line_number() < next.line_number() {
previous
} else {
- Rc::clone(next)
+ Arc::clone(next)
}),
}),
aliases,
@@ -190,7 +191,7 @@ impl<'src> Analyzer<'src> {
}
fn resolve_alias(
- recipes: &Table<'src, Rc>>,
+ recipes: &Table<'src, Arc>>,
alias: Alias<'src, Name<'src>>,
) -> CompileResult<'src, Alias<'src>> {
let token = alias.name.token();
@@ -204,7 +205,7 @@ impl<'src> Analyzer<'src> {
// Make sure the target recipe exists
match recipes.get(alias.target.lexeme()) {
- Some(target) => Ok(alias.resolve(Rc::clone(target))),
+ Some(target) => Ok(alias.resolve(Arc::clone(target))),
None => Err(token.error(UnknownAliasTarget {
alias: alias.name.lexeme(),
target: alias.target.lexeme(),
diff --git a/src/attribute.rs b/src/attribute.rs
index b4c71665e7..3118fe5547 100644
--- a/src/attribute.rs
+++ b/src/attribute.rs
@@ -12,6 +12,7 @@ pub(crate) enum Attribute {
NoCd,
NoExitMessage,
Private,
+ Parallel,
Unix,
Windows,
}
diff --git a/src/config.rs b/src/config.rs
index 9e8b7b9256..b074ecf934 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -25,6 +25,7 @@ pub(crate) struct Config {
pub(crate) list_heading: String,
pub(crate) list_prefix: String,
pub(crate) load_dotenv: bool,
+ pub(crate) parallel: bool,
pub(crate) search_config: SearchConfig,
pub(crate) shell: Option,
pub(crate) shell_args: Option>,
@@ -98,6 +99,7 @@ mod arg {
pub(crate) const LIST_PREFIX: &str = "LIST-PREFIX";
pub(crate) const NO_DOTENV: &str = "NO-DOTENV";
pub(crate) const NO_HIGHLIGHT: &str = "NO-HIGHLIGHT";
+ pub(crate) const PARALLEL: &str = "PARALLEL";
pub(crate) const QUIET: &str = "QUIET";
pub(crate) const SET: &str = "SET";
pub(crate) const SHELL: &str = "SHELL";
@@ -225,6 +227,12 @@ impl Config {
.takes_value(true)
.help("Use as justfile"),
)
+ .arg(
+ Arg::with_name(arg::PARALLEL)
+ .short("p")
+ .long("parallel")
+ .help("run given tasks in parallel")
+ )
.arg(
Arg::with_name(arg::QUIET)
.short("q")
@@ -630,6 +638,7 @@ impl Config {
dotenv_filename: matches.value_of(arg::DOTENV_FILENAME).map(str::to_owned),
dotenv_path: matches.value_of(arg::DOTENV_PATH).map(PathBuf::from),
dry_run: matches.is_present(arg::DRY_RUN),
+ parallel: matches.is_present(arg::PARALLEL),
dump_format: Self::dump_format_from_matches(matches)?,
highlight: !matches.is_present(arg::NO_HIGHLIGHT),
invocation_directory,
@@ -685,6 +694,7 @@ mod tests {
args: [$($arg:expr),*],
$(color: $color:expr,)?
$(dry_run: $dry_run:expr,)?
+ $(parallel: $parallel:expr,)?
$(dump_format: $dump_format:expr,)?
$(highlight: $highlight:expr,)?
$(search_config: $search_config:expr,)?
@@ -704,6 +714,7 @@ mod tests {
let want = Config {
$(color: $color,)?
$(dry_run: $dry_run,)?
+ $(parallel: $parallel,)?
$(dump_format: $dump_format,)?
$(highlight: $highlight,)?
$(search_config: $search_config,)?
@@ -840,6 +851,18 @@ mod tests {
dry_run: true,
}
+ test! {
+ name: parallel_long,
+ args: ["--parallel"],
+ parallel: true,
+ }
+
+ test! {
+ name: parallel_short,
+ args: ["-p"],
+ parallel: true,
+ }
+
error! {
name: dry_run_quiet,
args: ["--dry-run", "--quiet"],
diff --git a/src/dependency.rs b/src/dependency.rs
index 2d1da1173d..f9de949972 100644
--- a/src/dependency.rs
+++ b/src/dependency.rs
@@ -1,10 +1,11 @@
use super::*;
+use std::sync::Arc;
#[derive(PartialEq, Debug, Serialize)]
pub(crate) struct Dependency<'src> {
pub(crate) arguments: Vec>,
#[serde(serialize_with = "keyed::serialize")]
- pub(crate) recipe: Rc>,
+ pub(crate) recipe: Arc>,
}
impl<'src> Display for Dependency<'src> {
diff --git a/src/justfile.rs b/src/justfile.rs
index 3648c769e5..dc0f5d199f 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -1,3 +1,5 @@
+use parallel::Ran;
+use std::sync::Arc;
use {super::*, serde::Serialize};
#[derive(Debug, PartialEq, Serialize)]
@@ -5,8 +7,8 @@ pub(crate) struct Justfile<'src> {
pub(crate) aliases: Table<'src, Alias<'src>>,
pub(crate) assignments: Table<'src, Assignment<'src>>,
#[serde(serialize_with = "keyed::serialize_option")]
- pub(crate) first: Option>>,
- pub(crate) recipes: Table<'src, Rc>>,
+ pub(crate) first: Option>>,
+ pub(crate) recipes: Table<'src, Arc>>,
pub(crate) settings: Settings<'src>,
pub(crate) warnings: Vec,
}
@@ -253,10 +255,13 @@ impl<'src> Justfile<'src> {
search,
};
- let mut ran = BTreeSet::new();
- for (recipe, arguments) in grouped {
- Self::run_recipe(&context, recipe, arguments, &dotenv, search, &mut ran)?;
- }
+ 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, &ran))?;
+ }
+ Ok(())
+ })?;
Ok(())
}
@@ -269,7 +274,7 @@ impl<'src> Justfile<'src> {
self
.recipes
.get(name)
- .map(Rc::as_ref)
+ .map(Arc::as_ref)
.or_else(|| self.aliases.get(name).map(|alias| alias.target.as_ref()))
}
@@ -279,7 +284,7 @@ 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 {
@@ -311,43 +316,61 @@ impl<'src> Justfile<'src> {
let mut evaluator =
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
- for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
- let arguments = arguments
- .iter()
- .map(|argument| evaluator.evaluate_expression(argument))
- .collect::>>()?;
-
- Self::run_recipe(
- context,
- recipe,
- &arguments.iter().map(String::as_ref).collect::>(),
- dotenv,
- search,
- ran,
- )?;
- }
+ let run_dependencies_in_parallel = recipe.attributes.contains(&Attribute::Parallel);
+ parallel::task_scope(run_dependencies_in_parallel, |scope| {
+ for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
+ let arguments = arguments
+ .iter()
+ .map(|argument| evaluator.evaluate_expression(argument))
+ .collect::>>()?;
+
+ scope.run(move || {
+ Self::run_recipe(
+ context,
+ recipe,
+ &arguments.iter().map(String::as_ref).collect::>(),
+ dotenv,
+ search,
+ ran,
+ )
+ })?;
+ }
+ Ok(())
+ })?;
recipe.run(context, dotenv, scope.child(), search, &positional)?;
{
- let mut ran = BTreeSet::new();
+ let ran = Ran::new();
- for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) {
- let mut evaluated = Vec::new();
+ parallel::task_scope(run_dependencies_in_parallel, |scope| {
+ for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) {
+ let mut evaluated = Vec::new();
- for argument in arguments {
- evaluated.push(evaluator.evaluate_expression(argument)?);
- }
+ for argument in arguments {
+ evaluated.push(
+ evaluator
+ .evaluate_expression(argument)
+ .expect("error evaluating expression"),
+ );
+ }
- 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);
diff --git a/src/keyed.rs b/src/keyed.rs
index e171451eb8..42bb34f8c4 100644
--- a/src/keyed.rs
+++ b/src/keyed.rs
@@ -1,10 +1,11 @@
use super::*;
+use std::sync::Arc;
pub(crate) trait Keyed<'key> {
fn key(&self) -> &'key str;
}
-impl<'key, T: Keyed<'key>> Keyed<'key> for Rc {
+impl<'key, T: Keyed<'key>> Keyed<'key> for Arc {
fn key(&self) -> &'key str {
self.as_ref().key()
}
diff --git a/src/lib.rs b/src/lib.rs
index bd4b83355b..a40974a3cf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -45,7 +45,6 @@ pub(crate) use {
ops::{Index, Range, RangeInclusive},
path::{self, Path, PathBuf},
process::{self, Command, ExitStatus, Stdio},
- rc::Rc,
str::{self, Chars},
sync::{Mutex, MutexGuard},
vec,
@@ -147,6 +146,7 @@ mod name;
mod ordinal;
mod output;
mod output_error;
+mod parallel;
mod parameter;
mod parameter_kind;
mod parser;
diff --git a/src/parallel.rs b/src/parallel.rs
new file mode 100644
index 0000000000..5fe3f46d22
--- /dev/null
+++ b/src/parallel.rs
@@ -0,0 +1,97 @@
+use crate::RunResult;
+use crossbeam::thread;
+use std::collections::HashSet;
+use std::sync::{Arc, Mutex};
+
+type ScopeResult<'src> = RunResult<'src, ()>;
+
+pub(crate) struct TaskScope<'env, 'src, 'inner_scope> {
+ inner: &'inner_scope thread::Scope<'env>,
+ join_handles: Vec>>,
+ parallel: bool,
+}
+
+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,
+ F: FnOnce() -> ScopeResult<'src>,
+ F: Send + 'env,
+ {
+ if self.parallel {
+ self.join_handles.push(self.inner.spawn(|_scope| f()));
+ Ok(())
+ } else {
+ f()
+ }
+ }
+}
+
+/// task runner scope, based on `crossbeam::thread::scope`.
+///
+/// The `scope` object can be used to `.run` new tasks to be
+/// executed. Depending on the `parallel` parameter, these are
+/// directly run, or spawned in a background thread.
+///
+/// The first error will be returned as result of this `task_scope`.
+///
+/// Only works for tasks with an `RunResult<'src, ()>` result type.
+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>,
+{
+ thread::scope(|scope| {
+ let mut task_scope = TaskScope {
+ parallel,
+ inner: scope,
+ join_handles: Vec::new(),
+ };
+
+ f(&mut task_scope)?;
+
+ for handle in task_scope.join_handles {
+ handle.join().expect("could not join thread")?;
+ }
+ Ok(())
+ })
+ .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()]));
+ }
+}
diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs
index 2715acc240..2873fa34b0 100644
--- a/src/recipe_resolver.rs
+++ b/src/recipe_resolver.rs
@@ -1,8 +1,9 @@
+use std::sync::Arc;
use {super::*, CompileErrorKind::*};
pub(crate) struct RecipeResolver<'src: 'run, 'run> {
unresolved_recipes: Table<'src, UnresolvedRecipe<'src>>,
- resolved_recipes: Table<'src, Rc>>,
+ resolved_recipes: Table<'src, Arc>>,
assignments: &'run Table<'src, Assignment<'src>>,
}
@@ -10,7 +11,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
pub(crate) fn resolve_recipes(
unresolved_recipes: Table<'src, UnresolvedRecipe<'src>>,
assignments: &Table<'src, Assignment<'src>>,
- ) -> CompileResult<'src, Table<'src, Rc>>> {
+ ) -> CompileResult<'src, Table<'src, Arc>>> {
let mut resolver = RecipeResolver {
resolved_recipes: Table::new(),
unresolved_recipes,
@@ -72,20 +73,20 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
&mut self,
stack: &mut Vec<&'src str>,
recipe: UnresolvedRecipe<'src>,
- ) -> CompileResult<'src, Rc>> {
+ ) -> CompileResult<'src, Arc>> {
if let Some(resolved) = self.resolved_recipes.get(recipe.name()) {
- return Ok(Rc::clone(resolved));
+ return Ok(Arc::clone(resolved));
}
stack.push(recipe.name());
- let mut dependencies: Vec> = Vec::new();
+ let mut dependencies: Vec> = Vec::new();
for dependency in &recipe.dependencies {
let name = dependency.recipe.lexeme();
if let Some(resolved) = self.resolved_recipes.get(name) {
// dependency already resolved
- dependencies.push(Rc::clone(resolved));
+ dependencies.push(Arc::clone(resolved));
} else if stack.contains(&name) {
let first = stack[0];
stack.push(first);
@@ -113,8 +114,8 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
stack.pop();
- let resolved = Rc::new(recipe.resolve(dependencies)?);
- self.resolved_recipes.insert(Rc::clone(&resolved));
+ let resolved = Arc::new(recipe.resolve(dependencies)?);
+ self.resolved_recipes.insert(Arc::clone(&resolved));
Ok(resolved)
}
}
diff --git a/src/unresolved_recipe.rs b/src/unresolved_recipe.rs
index 0a49443de8..0af5c666c8 100644
--- a/src/unresolved_recipe.rs
+++ b/src/unresolved_recipe.rs
@@ -1,11 +1,12 @@
use super::*;
+use std::sync::Arc;
pub(crate) type UnresolvedRecipe<'src> = Recipe<'src, UnresolvedDependency<'src>>;
impl<'src> UnresolvedRecipe<'src> {
pub(crate) fn resolve(
self,
- resolved: Vec>>,
+ resolved: Vec>>,
) -> CompileResult<'src, Recipe<'src>> {
assert_eq!(
self.dependencies.len(),