1use std::sync::atomic;
2
3use anyhow::Result;
4use clap::{Parser, ValueHint};
5use rayon::prelude::*;
6
7use crate::cli::GardenOptions;
8use crate::{cmd, constants, errors, model, query};
9
10#[derive(Parser, Clone, Debug)]
12#[command(author, about, long_about)]
13pub struct ExecOptions {
14 #[arg(long, short, default_value = "*")]
16 pub(crate) trees: String,
17 #[arg(long, short = 'N', short_alias = 'n')]
19 pub(crate) dry_run: bool,
20 #[arg(long = "jobs", short = 'j', value_name = "JOBS")]
22 pub(crate) num_jobs: Option<usize>,
23 #[arg(short, long)]
25 pub(crate) quiet: bool,
26 #[arg(short, long, action = clap::ArgAction::Count)]
28 pub(crate) verbose: u8,
29 #[arg(value_hint=ValueHint::Other)]
31 pub(crate) query: String,
32 #[arg(allow_hyphen_values = true, trailing_var_arg = true, required = true, value_hint=ValueHint::CommandWithArguments)]
34 pub(crate) command: Vec<String>,
35}
36
37pub fn main(app_context: &model::ApplicationContext, exec_options: &mut ExecOptions) -> Result<()> {
39 exec_options.verbose += app_context.options.verbose;
40 if app_context.options.debug_level(constants::DEBUG_LEVEL_EXEC) > 0 {
41 debug!("query: {}", exec_options.query);
42 debug!("command: {:?}", exec_options.command);
43 }
44 exec_options.quiet |= app_context.options.quiet;
45 exec(app_context, exec_options)
46}
47
48fn exec(app_context: &model::ApplicationContext, exec_options: &ExecOptions) -> Result<()> {
50 let quiet = exec_options.quiet;
51 let verbose = exec_options.verbose;
52 let dry_run = exec_options.dry_run;
53 let query = &exec_options.query;
54 let tree_pattern = &exec_options.trees;
55 let command = &exec_options.command;
56 cmd::initialize_threads_option(exec_options.num_jobs)?;
66
67 let config = app_context.get_root_config_mut();
69 let contexts = query::resolve_trees(app_context, config, None, query);
70 let pattern = glob::Pattern::new(tree_pattern).unwrap_or_default();
71 let exit_status = atomic::AtomicI32::new(errors::EX_OK);
72
73 if exec_options.num_jobs.is_some() {
76 contexts.par_iter().for_each(|context| {
77 let app_context_clone = app_context.clone();
78 let app_context = &app_context_clone;
79 if !model::is_valid_context(app_context, &pattern, context) {
80 return;
81 }
82 if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
84 app_context,
85 app_context.get_root_config(),
86 context,
87 quiet,
88 verbose,
89 dry_run,
90 command,
91 ) {
92 exit_status.store(status, atomic::Ordering::Release);
93 }
94 });
95 } else {
96 for context in &contexts {
97 if !model::is_valid_context(app_context, &pattern, context) {
98 continue;
99 }
100 if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
102 app_context,
103 config,
104 context,
105 quiet,
106 verbose,
107 dry_run,
108 command,
109 ) {
110 exit_status.store(status, atomic::Ordering::Release);
111 }
112 }
113 }
114
115 errors::exit_status_into_result(exit_status.load(atomic::Ordering::Acquire))
117}