Skip to content

Commit

Permalink
exec: add support for parallel execution
Browse files Browse the repository at this point in the history
Add the `-j# | --jobs=#` option to `garden exec` to enable parallel
execution of one-off commands across multiple trees.

Closes: #43
  • Loading branch information
davvid committed Sep 29, 2024
1 parent 1a7be68 commit c97004d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 18 deletions.
7 changes: 6 additions & 1 deletion doc/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Upcoming

**Features**:

- `garden exec` can now run commands in parallel using the `-j# | --jobs=#` option.
([#43](https://github.com/garden-rs/garden/issues/43))

**Packaging**:

- Garden's Nix flake was improved and using Garden with Nix home-manager was documented.
Expand All @@ -21,7 +26,7 @@

**Features**:

- `garden cmd` and custom commands now have a `--jobs=# | -j#` option for
- `garden cmd` and custom commands now have a `-j# | --jobs=#` option for
[running commands in parallel](https://garden-rs.gitlab.io/commands.html#parallel-execution).
Use `-j0 | --jobs=0` to use all available cores.
([#43](https://github.com/garden-rs/garden/issues/43))
Expand Down
64 changes: 47 additions & 17 deletions src/cmds/exec.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::sync::atomic;

use anyhow::Result;
use clap::{Parser, ValueHint};
use rayon::prelude::*;

use crate::{cmd, constants, errors, model, query};

Expand All @@ -13,6 +16,9 @@ pub struct ExecOptions {
/// Perform a trial run without executing any commands
#[arg(long, short = 'N', short_alias = 'n')]
dry_run: bool,
/// Run commands in parallel using the specified number of jobs.
#[arg(long = "jobs", short = 'j', value_name = "JOBS")]
num_jobs: Option<usize>,
/// Be quiet
#[arg(short, long)]
quiet: bool,
Expand Down Expand Up @@ -80,33 +86,57 @@ fn exec(app_context: &model::ApplicationContext, exec_options: &ExecOptions) ->
//
// If the names resolve to trees, each tree is processed independently
// with no garden context.
cmd::initialize_threads_option(exec_options.num_jobs)?;

// Resolve the tree query into a vector of tree contexts.
let config = app_context.get_root_config_mut();
let contexts = query::resolve_trees(app_context, config, None, query);
let pattern = glob::Pattern::new(tree_pattern).unwrap_or_default();
let mut exit_status: i32 = 0;
let exit_status = atomic::AtomicI32::new(errors::EX_OK);

// Loop over each context, evaluate the tree environment,
// and run the command.
for context in &contexts {
if !is_valid_context(app_context, &pattern, context) {
continue;
}
// Run the command in the current context.
if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
app_context,
config,
context,
quiet,
verbose,
dry_run,
command,
) {
exit_status = status;
if exec_options.num_jobs.is_some() {
contexts.par_iter().for_each(|context| {
let app_context_clone = app_context.clone();
let app_context = &app_context_clone;
if !is_valid_context(app_context, &pattern, context) {
return;
}
// Run the command in the current context.
if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
app_context,
app_context.get_root_config(),
context,
quiet,
verbose,
dry_run,
command,
) {
exit_status.store(status, atomic::Ordering::Relaxed);
}
});
} else {
for context in &contexts {
if !is_valid_context(app_context, &pattern, context) {
continue;
}
// Run the command in the current context.
if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
app_context,
config,
context,
quiet,
verbose,
dry_run,
command,
) {
exit_status.store(status, atomic::Ordering::Relaxed);
}
}
}

// Return the last non-zero exit status.
cmd::result_from_exit_status(exit_status).map_err(|err| err.into())
cmd::result_from_exit_status(exit_status.load(atomic::Ordering::SeqCst))
.map_err(|err| err.into())
}

0 comments on commit c97004d

Please sign in to comment.