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(),