1#[cfg(test)]
7#[path = "runner_test.rs"]
8mod runner_test;
9
10use crate::environment;
11use crate::plugin::sdk;
12use crate::plugin::types::Plugin;
13use crate::scriptengine::duck_script;
14use crate::types::{Config, DeprecationInfo, FlowInfo, FlowState, RunTaskOptions, Step};
15use duckscript::runner::run_script;
16use duckscript::types::command::Commands;
17use duckscript::types::error::ScriptError;
18use duckscript::types::runtime::Context;
19use indexmap::IndexMap;
20use serde_json::json;
21use std::cell::RefCell;
22use std::env;
23use std::rc::Rc;
24
25fn get_plugin_name_recursive(
27 aliases: &IndexMap<String, String>,
28 name: &str,
29 seen: &mut Vec<String>,
30) -> String {
31 let name_string = name.to_string();
32 if seen.contains(&name_string) {
33 error!("Detected cycle while resolving plugin alias: {}", name);
34 }
35 seen.push(name_string);
36
37 match aliases.get(name) {
38 Some(target_name) => get_plugin_name_recursive(aliases, target_name, seen),
39 None => name.to_string(),
40 }
41}
42
43fn get_plugin(config: &Config, plugin_name: &str) -> Option<(String, Plugin)> {
44 match &config.plugins {
45 Some(plugins_config) => {
46 let normalized_plugin_name = match plugins_config.aliases {
47 Some(ref aliases) => {
48 let mut seen = vec![];
49 get_plugin_name_recursive(aliases, plugin_name, &mut seen)
50 }
51 None => plugin_name.to_string(),
52 };
53
54 match plugins_config.plugins.get(&normalized_plugin_name) {
55 Some(plugin) => Some((normalized_plugin_name, plugin.clone())),
56 None => None,
57 }
58 }
59 None => None,
60 }
61}
62
63fn run_plugin(
64 flow_info: &FlowInfo,
65 flow_state: Rc<RefCell<FlowState>>,
66 step: &Step,
67 plugin: Plugin,
68 impl_plugin_name: String,
69) {
70 debug!(
71 "Running Task: {} via plugin: {} script:\n{}",
72 &step.name, &impl_plugin_name, &plugin.script
73 );
74
75 let cli_arguments = match flow_info.cli_arguments.clone() {
76 Some(cli_arguments) => cli_arguments.clone(),
77 None => vec![],
78 };
79
80 let mut context = duck_script::create_common_context(&cli_arguments);
81
82 let mut script_text = "exit_on_error true\n".to_string();
83 setup_script_globals(
84 &mut context,
85 flow_info,
86 step,
87 &impl_plugin_name,
88 &mut script_text,
89 );
90 script_text.push_str(&plugin.script);
91
92 match load_sdk(flow_info, flow_state, step, &mut context.commands) {
93 Ok(_) => {
94 let directory = env::current_dir();
95
96 match run_script(&script_text, context, None) {
97 Ok(_) => (),
98 Err(error) => error!("Error while running plugin: {}", error),
99 };
100
101 if let Ok(directory_path) = directory {
103 let path = directory_path.to_string_lossy().into_owned();
104 environment::setup_cwd(Some(&path));
105 }
106 }
107 Err(error) => error!("Unable to load duckscript SDK: {}", error),
108 };
109}
110
111fn setup_script_globals(
112 context: &mut Context,
113 flow_info: &FlowInfo,
114 step: &Step,
115 impl_plugin_name: &str,
116 script: &mut String,
117) {
118 context
119 .variables
120 .insert("flow.task.name".to_string(), flow_info.task.to_string());
121 script.push_str("flow.cli.args = array\n");
122 if let Some(ref cli_arguments) = flow_info.cli_arguments {
123 for arg in cli_arguments {
124 script.push_str("array_push ${flow.cli.args} \"");
125 script.push_str(&arg.replace("$", "\\$"));
126 script.push_str("\"\n");
127 }
128 }
129 context
130 .variables
131 .insert("plugin.impl.name".to_string(), impl_plugin_name.to_string());
132
133 setup_script_globals_for_task(context, step, script);
134}
135
136fn setup_script_globals_for_task(context: &mut Context, step: &Step, script: &mut String) {
137 let task = step.config.clone();
139 let json_string = json!(task);
140 context
141 .variables
142 .insert("task.as_json".to_string(), json_string.to_string());
143
144 context.variables.insert(
146 "task.has_condition".to_string(),
147 (task.condition.is_some() || task.condition_script.is_some()).to_string(),
148 );
149 context.variables.insert(
150 "task.has_env".to_string(),
151 (task.env_files.is_some() || task.env.is_some()).to_string(),
152 );
153 context.variables.insert(
154 "task.has_install_instructions".to_string(),
155 (task.install_crate.is_some()
156 || task.install_crate_args.is_some()
157 || task.install_script.is_some())
158 .to_string(),
159 );
160 context.variables.insert(
161 "task.has_command".to_string(),
162 task.command.is_some().to_string(),
163 );
164 context.variables.insert(
165 "task.has_script".to_string(),
166 task.script.is_some().to_string(),
167 );
168 context.variables.insert(
169 "task.has_run_task".to_string(),
170 task.run_task.is_some().to_string(),
171 );
172 context.variables.insert(
173 "task.has_dependencies".to_string(),
174 task.dependencies.is_some().to_string(),
175 );
176 context.variables.insert(
177 "task.has_toolchain_specifier".to_string(),
178 task.toolchain.is_some().to_string(),
179 );
180
181 context
182 .variables
183 .insert("task.name".to_string(), step.name.clone());
184
185 context.variables.insert(
186 "task.description".to_string(),
187 task.description.unwrap_or("".to_string()),
188 );
189 context.variables.insert(
190 "task.category".to_string(),
191 task.category.unwrap_or("".to_string()),
192 );
193 context.variables.insert(
194 "task.disabled".to_string(),
195 task.disabled.unwrap_or(false).to_string(),
196 );
197 context.variables.insert(
198 "task.private".to_string(),
199 task.private.unwrap_or(false).to_string(),
200 );
201 let deprecated = match task.deprecated {
202 Some(value) => match value {
203 DeprecationInfo::Boolean(value) => value,
204 DeprecationInfo::Message(_) => true,
205 },
206 None => false,
207 };
208 context
209 .variables
210 .insert("task.deprecated".to_string(), deprecated.to_string());
211 context.variables.insert(
212 "task.workspace".to_string(),
213 task.workspace.unwrap_or(false).to_string(),
214 );
215 context.variables.insert(
216 "task.plugin.name".to_string(),
217 task.plugin.unwrap_or("".to_string()),
218 );
219 context
220 .variables
221 .insert("task.watch".to_string(), task.watch.is_some().to_string());
222 context.variables.insert(
223 "task.ignore_errors".to_string(),
224 task.ignore_errors.unwrap_or(false).to_string(),
225 );
226 context.variables.insert(
227 "task.cwd".to_string(),
228 task.cwd.unwrap_or("".to_string()).to_string(),
229 );
230 context.variables.insert(
231 "task.command".to_string(),
232 task.command.unwrap_or("".to_string()).to_string(),
233 );
234 script.push_str("task.args = array\n");
235 if let Some(args) = task.args {
236 for arg in &args {
237 script.push_str("array_push ${task.args} \"");
238 script.push_str(&arg.replace("$", "\\$"));
239 script.push_str("\"\n");
240 }
241 }
242 context.variables.insert(
243 "task.script_runner".to_string(),
244 task.script_runner.unwrap_or("".to_string()).to_string(),
245 );
246 script.push_str("task.script_runner_args = array\n");
247 if let Some(args) = task.script_runner_args {
248 for arg in &args {
249 script.push_str("array_push ${task.script_runner_args} \"");
250 script.push_str(arg);
251 script.push_str("\"\n");
252 }
253 }
254 context.variables.insert(
255 "task.script_extension".to_string(),
256 task.script_extension.unwrap_or("".to_string()).to_string(),
257 );
258}
259
260fn load_sdk(
261 flow_info: &FlowInfo,
262 flow_state: Rc<RefCell<FlowState>>,
263 step: &Step,
264 commands: &mut Commands,
265) -> Result<(), ScriptError> {
266 duck_script::load_sdk(commands, Some(flow_info), Some(flow_state.clone()))?;
267 sdk::load(flow_info, flow_state, step, commands)?;
268
269 Ok(())
270}
271
272pub(crate) fn run_task(
273 flow_info: &FlowInfo,
274 flow_state: Rc<RefCell<FlowState>>,
275 step: &Step,
276 options: &RunTaskOptions,
277) -> bool {
278 if !options.plugins_enabled {
279 false
280 } else {
281 let plugin_name_option = match flow_state.borrow().forced_plugin {
282 Some(ref value) => Some(value.clone()),
283 None => match step.config.plugin {
284 Some(ref value) => Some(value.clone()),
285 None => None,
286 },
287 };
288
289 match plugin_name_option {
290 Some(ref plugin_name) => match get_plugin(&flow_info.config, plugin_name) {
291 Some((normalized_plugin_name, plugin)) => {
292 debug!(
293 "Running Task: {} via plugin: {}",
294 &step.name, &normalized_plugin_name
295 );
296
297 run_plugin(flow_info, flow_state, step, plugin, normalized_plugin_name);
298
299 true
300 }
301 None => {
302 error!(
303 "Invalid task: {}, unknown plugin: {}",
304 &step.name, plugin_name
305 );
306 false
307 }
308 },
309 None => false,
310 }
311 }
312}