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 trees: String,
17 #[arg(long, short = 'N', short_alias = 'n')]
19 dry_run: bool,
20 #[arg(long = "jobs", short = 'j', value_name = "JOBS")]
22 num_jobs: Option<usize>,
23 #[arg(short, long)]
25 quiet: bool,
26 #[arg(short, long, action = clap::ArgAction::Count)]
28 verbose: u8,
29 #[arg(value_hint=ValueHint::Other)]
31 query: String,
32 #[arg(allow_hyphen_values = true, trailing_var_arg = true, required = true, value_hint=ValueHint::CommandWithArguments)]
34 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 is_valid_context(
50 app_context: &model::ApplicationContext,
51 pattern: &glob::Pattern,
52 context: &model::TreeContext,
53) -> bool {
54 if !pattern.matches(&context.tree) {
55 return false;
56 }
57 let tree_opt = match context.config {
58 Some(graft_id) => app_context.get_config(graft_id).trees.get(&context.tree),
59 None => app_context.get_root_config().trees.get(&context.tree),
60 };
61 let tree = match tree_opt {
62 Some(tree) => tree,
63 None => return false,
64 };
65 if tree.is_symlink {
67 return false;
68 }
69
70 true
71}
72
73fn exec(app_context: &model::ApplicationContext, exec_options: &ExecOptions) -> Result<()> {
75 let quiet = exec_options.quiet;
76 let verbose = exec_options.verbose;
77 let dry_run = exec_options.dry_run;
78 let query = &exec_options.query;
79 let tree_pattern = &exec_options.trees;
80 let command = &exec_options.command;
81 cmd::initialize_threads_option(exec_options.num_jobs)?;
91
92 let config = app_context.get_root_config_mut();
94 let contexts = query::resolve_trees(app_context, config, None, query);
95 let pattern = glob::Pattern::new(tree_pattern).unwrap_or_default();
96 let exit_status = atomic::AtomicI32::new(errors::EX_OK);
97
98 if exec_options.num_jobs.is_some() {
101 contexts.par_iter().for_each(|context| {
102 let app_context_clone = app_context.clone();
103 let app_context = &app_context_clone;
104 if !is_valid_context(app_context, &pattern, context) {
105 return;
106 }
107 if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
109 app_context,
110 app_context.get_root_config(),
111 context,
112 quiet,
113 verbose,
114 dry_run,
115 command,
116 ) {
117 exit_status.store(status, atomic::Ordering::Release);
118 }
119 });
120 } else {
121 for context in &contexts {
122 if !is_valid_context(app_context, &pattern, context) {
123 continue;
124 }
125 if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
127 app_context,
128 config,
129 context,
130 quiet,
131 verbose,
132 dry_run,
133 command,
134 ) {
135 exit_status.store(status, atomic::Ordering::Release);
136 }
137 }
138 }
139
140 errors::exit_status_into_result(exit_status.load(atomic::Ordering::Acquire))
142}