1#[cfg(test)]
12#[path = "runner_test.rs"]
13mod runner_test;
14
15use std::cell::RefCell;
16use std::rc::Rc;
17use std::thread;
18use std::time::SystemTime;
19
20use indexmap::IndexMap;
21use regex::Regex;
22
23use crate::command;
24use crate::condition;
25use crate::environment;
26use crate::error::CargoMakeError;
27use crate::execution_plan::ExecutionPlanBuilder;
28use crate::functions;
29use crate::installer;
30use crate::logger;
31use crate::plugin::runner::run_task as run_task_plugin;
32use crate::profile;
33use crate::proxy_task::create_proxy_task;
34use crate::scriptengine;
35use crate::time_summary;
36use crate::types::{
37 CliArgs, Config, DeprecationInfo, EnvInfo, EnvValue, ExecutionPlan, FlowInfo, FlowState,
38 MaybeArray, RunTaskInfo, RunTaskName, RunTaskOptions, RunTaskRoutingInfo, Step, Task,
39 TaskWatchOptions,
40};
41
42fn do_in_task_working_directory<F>(step: &Step, mut action: F) -> Result<(), CargoMakeError>
43where
44 F: FnMut() -> Result<bool, CargoMakeError>,
45{
46 let revert_directory = match step.config.cwd {
47 Some(ref cwd) => {
48 let expanded_cwd = environment::expand_value(cwd);
49
50 if expanded_cwd.len() > 0 {
51 let directory = envmnt::get_or("CARGO_MAKE_WORKING_DIRECTORY", "");
52
53 environment::setup_cwd(Some(&expanded_cwd));
54
55 directory
56 } else {
57 "".to_string()
58 }
59 }
60 None => "".to_string(),
61 };
62
63 action()?;
64
65 match step.config.cwd {
67 Some(_) => {
68 environment::setup_cwd(Some(&revert_directory));
69 }
70 _ => (),
71 };
72 Ok(())
73}
74
75pub(crate) fn validate_condition(
76 flow_info: &FlowInfo,
77 step: &Step,
78) -> Result<bool, CargoMakeError> {
79 let mut valid = true;
80
81 let do_validate = || -> Result<bool, CargoMakeError> {
82 valid = condition::validate_condition_for_step(&flow_info, &step)?;
83 Ok(valid)
84 };
85
86 do_in_task_working_directory(&step, do_validate)?;
87
88 Ok(valid)
89}
90
91pub(crate) fn get_sub_task_info_for_routing_info(
92 flow_info: &FlowInfo,
93 routing_info: &Vec<RunTaskRoutingInfo>,
94) -> Result<(Option<Vec<String>>, bool, bool, Option<String>), CargoMakeError> {
95 let mut task_name = None;
96
97 let mut fork = false;
98 let mut parallel = false;
99 let mut cleanup_task = None;
100 for routing_step in routing_info {
101 let invoke = condition::validate_conditions(
102 &flow_info,
103 &routing_step.condition,
104 &routing_step.condition_script,
105 None,
106 routing_step.condition_script_runner_args.clone(),
107 )?;
108
109 if invoke {
110 let task_name_values = match routing_step.name.clone() {
111 RunTaskName::Single(name) => vec![name],
112 RunTaskName::Multiple(names) => names,
113 };
114 task_name = Some(task_name_values);
115 fork = routing_step.fork.unwrap_or(false);
116 parallel = routing_step.parallel.unwrap_or(false);
117 cleanup_task = routing_step.cleanup_task.clone();
118 break;
119 }
120 }
121
122 Ok((task_name, fork, parallel, cleanup_task))
123}
124
125fn create_fork_step(flow_info: &FlowInfo) -> Step {
126 let fork_task = create_proxy_task(
127 &flow_info.task,
128 true,
129 true,
130 None,
131 flow_info.cli_arguments.clone(),
132 );
133
134 Step {
135 name: "cargo_make_run_fork".to_string(),
136 config: fork_task,
137 }
138}
139
140fn run_cleanup_task(
141 flow_info: &FlowInfo,
142 flow_state: Rc<RefCell<FlowState>>,
143 task: &str,
144) -> Result<(), CargoMakeError> {
145 match flow_info.config.tasks.get(task) {
146 Some(cleanup_task_info) => run_task(
147 &flow_info,
148 flow_state,
149 &Step {
150 name: task.to_string(),
151 config: cleanup_task_info.clone(),
152 },
153 ),
154 None => Err(CargoMakeError::NotFound(format!(
155 "Cleanup task: {} not found.",
156 &task
157 ))),
158 }
159}
160
161fn run_forked_task(
162 flow_info: &FlowInfo,
163 flow_state: Rc<RefCell<FlowState>>,
164 cleanup_task: &Option<String>,
165) -> Result<(), CargoMakeError> {
166 let step = create_fork_step(&flow_info);
168
169 match cleanup_task {
170 Some(cleanup_task_name) => {
171 let exit_code =
173 command::run_command(&step.config.command.unwrap(), &step.config.args, false)?;
174
175 if exit_code != 0 {
176 run_cleanup_task(&flow_info, flow_state, &cleanup_task_name)?;
177 command::validate_exit_code(exit_code)
178 } else {
179 Ok(())
180 }
181 }
182 None => run_task(&flow_info, flow_state, &step),
183 }
184}
185
186fn run_sub_task_and_report(
188 flow_info: &FlowInfo,
189 flow_state: Rc<RefCell<FlowState>>,
190 sub_task: &RunTaskInfo,
191) -> Result<bool, CargoMakeError> {
192 let (task_names, fork, parallel, cleanup_task) = match sub_task {
193 RunTaskInfo::Name(ref name) => (Some(vec![name.to_string()]), false, false, None),
194 RunTaskInfo::Details(ref details) => {
195 let task_name_values = match details.name.clone() {
196 RunTaskName::Single(name) => vec![name],
197 RunTaskName::Multiple(names) => names,
198 };
199 (
200 Some(task_name_values),
201 details.fork.unwrap_or(false),
202 details.parallel.unwrap_or(false),
203 details.cleanup_task.clone(),
204 )
205 }
206 RunTaskInfo::Routing(ref routing_info) => {
207 get_sub_task_info_for_routing_info(&flow_info, routing_info)?
208 }
209 };
210
211 if task_names.is_some() {
212 let names = task_names.unwrap();
213 let mut threads = vec![];
214
215 if !fork && cleanup_task.is_some() {
217 error!("Invalid task, cannot use cleanup_task without fork.");
218 }
219
220 for name in names {
221 let task_run_fn = move |flow_info: &FlowInfo,
222 flow_state: Rc<RefCell<FlowState>>,
223 fork: bool,
224 cleanup_task: &Option<String>|
225 -> Result<(), CargoMakeError> {
226 let mut sub_flow_info = flow_info.clone();
227 sub_flow_info.task = name;
228
229 if fork {
230 run_forked_task(&sub_flow_info, flow_state, cleanup_task)
231 } else {
232 run_flow(&sub_flow_info, flow_state, true)
233 }
234 };
235
236 if parallel {
237 let run_flow_info = flow_info.clone();
238 let cloned_flow_state = flow_state.borrow().clone();
240 let cloned_cleanup_task = cleanup_task.clone();
241 threads.push(thread::spawn(move || -> Result<(), CargoMakeError> {
242 task_run_fn(
243 &run_flow_info,
244 Rc::new(RefCell::new(cloned_flow_state)),
245 fork,
246 &cloned_cleanup_task,
247 )
248 }));
249 } else {
250 task_run_fn(&flow_info, flow_state.clone(), fork, &cleanup_task)?;
251 }
252 }
253
254 if threads.len() > 0 {
255 for task_thread in threads {
256 task_thread.join().unwrap()?;
257 }
258 }
259
260 if let Some(cleanup_task_name) = cleanup_task {
261 run_cleanup_task(&flow_info, flow_state, &cleanup_task_name)?;
262 }
263
264 Ok(true)
265 } else {
266 Ok(false)
267 }
268}
269
270fn run_sub_task(
271 flow_info: &FlowInfo,
272 flow_state: Rc<RefCell<FlowState>>,
273 sub_task: &RunTaskInfo,
274) -> Result<bool, CargoMakeError> {
275 run_sub_task_and_report(&flow_info, flow_state, &sub_task)
276}
277
278fn create_watch_task_name(task: &str) -> String {
279 let mut watch_task_name = "".to_string();
280 watch_task_name.push_str(&task);
281 watch_task_name.push_str("-watch");
282
283 watch_task_name
284}
285
286fn create_watch_step(task: &str, options: Option<TaskWatchOptions>, flow_info: &FlowInfo) -> Step {
287 let watch_task = create_watch_task(&task, options, flow_info);
288
289 let watch_task_name = create_watch_task_name(&task);
290
291 Step {
292 name: watch_task_name,
293 config: watch_task,
294 }
295}
296
297fn watch_task(
298 flow_info: &FlowInfo,
299 flow_state: Rc<RefCell<FlowState>>,
300 task: &str,
301 options: Option<TaskWatchOptions>,
302) -> Result<(), CargoMakeError> {
303 let step = create_watch_step(&task, options, flow_info);
304
305 run_task(&flow_info, flow_state, &step)
306}
307
308fn is_watch_enabled() -> bool {
309 !envmnt::is_or("CARGO_MAKE_DISABLE_WATCH", false)
310}
311
312fn should_watch(task: &Task) -> bool {
313 match task.watch {
314 Some(ref watch_value) => match watch_value {
315 TaskWatchOptions::Boolean(watch_bool) => {
316 if *watch_bool {
317 is_watch_enabled()
318 } else {
319 false
320 }
321 }
322 TaskWatchOptions::Options(_) => is_watch_enabled(),
323 },
324 None => false,
325 }
326}
327
328pub(crate) fn run_task(
329 flow_info: &FlowInfo,
330 flow_state: Rc<RefCell<FlowState>>,
331 step: &Step,
332) -> Result<(), CargoMakeError> {
333 let options = RunTaskOptions {
334 plugins_enabled: true,
335 };
336
337 run_task_with_options(flow_info, flow_state, step, &options)
338}
339
340pub(crate) fn run_task_with_options(
341 flow_info: &FlowInfo,
342 flow_state: Rc<RefCell<FlowState>>,
343 step: &Step,
344 options: &RunTaskOptions,
345) -> Result<(), CargoMakeError> {
346 let start_time = SystemTime::now();
347
348 if run_task_plugin(flow_info, flow_state.clone(), step, options) {
350 time_summary::add(
351 &mut flow_state.borrow_mut().time_summary,
352 &step.name,
353 start_time,
354 );
355 return Ok(());
356 }
357
358 if step.config.is_actionable() {
359 match step.config.env {
360 Some(ref env) => environment::set_current_task_meta_info_env(env.clone()),
361 None => (),
362 };
363 envmnt::set("CARGO_MAKE_CURRENT_TASK_NAME", &step.name);
364
365 if validate_condition(
366 &flow_info,
367 &environment::expand_condition_script_runner_arguments(&step),
368 )? {
369 if logger::should_reduce_output(&flow_info) && step.config.script.is_none() {
370 debug!("Running Task: {}", &step.name);
371 } else {
372 info!("Running Task: {}", &step.name);
373 }
374
375 if !step.config.is_valid() {
376 error!(
377 "Invalid task: {}, contains multiple actions.\n{:#?}",
378 &step.name, &step.config
379 );
380 }
381
382 let deprecated_info = step.config.deprecated.clone();
383 match deprecated_info {
384 Some(deprecated) => match deprecated {
385 DeprecationInfo::Boolean(value) => {
386 if value {
387 warn!("Task: {} is deprecated.", &step.name);
388 }
389
390 ()
391 }
392 DeprecationInfo::Message(ref message) => {
393 warn!("Task: {} is deprecated - {}", &step.name, message);
394
395 ()
396 }
397 },
398 None => (),
399 };
400
401 let profile_name = profile::get();
403
404 match step.config.env_files {
405 Some(ref env_files) => environment::set_env_files(env_files.clone()),
406 None => (),
407 };
408 match step.config.env {
409 Some(ref env) => environment::set_env(env.clone()),
410 None => (),
411 };
412
413 profile::set(&profile_name);
415
416 let mut updated_step = functions::run(&step)?;
418 updated_step = environment::expand_env(&updated_step);
419
420 let watch = should_watch(&step.config);
421
422 if watch {
423 watch_task(
424 &flow_info,
425 flow_state,
426 &step.name,
427 step.config.watch.clone(),
428 )?;
429 } else {
430 do_in_task_working_directory(&step, || -> Result<bool, CargoMakeError> {
431 installer::install(&updated_step.config, flow_info, flow_state.clone())?;
432 Ok(true)
433 })?;
434
435 match step.config.run_task {
436 Some(ref sub_task) => {
437 time_summary::add(
438 &mut flow_state.borrow_mut().time_summary,
439 &step.name,
440 start_time,
441 );
442
443 run_sub_task(&flow_info, flow_state, sub_task)?;
444 }
445 None => {
446 do_in_task_working_directory(&step, || -> Result<bool, CargoMakeError> {
447 let script_runner_done = scriptengine::invoke(
449 &updated_step.config,
450 flow_info,
451 flow_state.clone(),
452 )?;
453
454 if !script_runner_done {
456 command::run(&updated_step)?;
457 };
458 Ok(true)
459 })?;
460
461 time_summary::add(
462 &mut flow_state.borrow_mut().time_summary,
463 &step.name,
464 start_time,
465 );
466 }
467 };
468 }
469 } else {
470 let fail_message = match step.config.condition {
471 Some(ref condition) => match condition.fail_message {
472 Some(ref value) => value.to_string(),
473 None => "".to_string(),
474 },
475 None => "".to_string(),
476 };
477
478 if logger::should_reduce_output(&flow_info) && !step.config.is_actionable() {
479 debug!("Skipping Task: {} {}", &step.name, &fail_message);
480 } else {
481 info!("Skipping Task: {} {}", &step.name, &fail_message);
482 }
483 }
484 } else {
485 debug!("Ignoring Empty Task: {}", &step.name);
486 }
487
488 Ok(())
489}
490
491fn run_task_flow(
492 flow_info: &FlowInfo,
493 flow_state: Rc<RefCell<FlowState>>,
494 execution_plan: &ExecutionPlan,
495) -> Result<(), CargoMakeError> {
496 for step in &execution_plan.steps {
497 run_task(&flow_info, flow_state.clone(), &step)?;
498 }
499 Ok(())
500}
501
502fn create_watch_task(task: &str, options: Option<TaskWatchOptions>, flow_info: &FlowInfo) -> Task {
503 let mut task_config =
504 create_proxy_task(&task, true, true, None, flow_info.cli_arguments.clone());
505
506 let mut env_map = task_config.env.unwrap_or(IndexMap::new());
507 env_map.insert(
508 "CARGO_MAKE_DISABLE_WATCH".to_string(),
509 EnvValue::Value("true".to_string()),
510 );
511 task_config.env = Some(env_map);
512
513 let make_args = task_config.args.unwrap();
514 let mut make_command = String::new();
515 for make_arg in make_args {
516 if make_arg.contains(" ") {
517 make_command.push_str("\"");
518 make_command.push_str(&make_arg);
519 make_command.push_str("\"");
520 } else {
521 make_command.push_str(&make_arg);
522 }
523
524 make_command.push(' ');
525 }
526 make_command = make_command.trim().to_string();
527
528 let mut watch_args = vec!["watch".to_string()];
529
530 match options {
531 Some(task_watch_options) => match task_watch_options {
532 TaskWatchOptions::Options(watch_options) => {
533 let watch_version = match watch_options.version {
534 Some(value) => value.to_string(),
535 _ => "8.4.1".to_string(), };
537 task_config.install_crate_args = Some(vec!["--version".to_string(), watch_version]);
538
539 match watch_options.why {
540 Some(option_value) => {
541 if option_value {
542 watch_args.push("--why".to_string());
543 } else {
544 watch_args.push("-q".to_string());
545 }
546 }
547 None => watch_args.push("-q".to_string()),
548 }
549
550 if let Some(option_value) = watch_options.postpone {
551 if option_value {
552 watch_args.push("--postpone".to_string());
553 }
554 }
555
556 match watch_options.ignore_pattern {
557 Some(MaybeArray::Single(value)) => {
558 watch_args.extend_from_slice(&["-i".to_string(), value])
559 }
560 Some(MaybeArray::Multiple(values)) => watch_args.extend(
561 values
562 .iter()
563 .flat_map(|value| ["-i".to_string(), value.to_string()])
564 .collect::<Vec<String>>(),
565 ),
566 _ => (),
567 };
568
569 if let Some(option_value) = watch_options.no_git_ignore {
570 if option_value {
571 watch_args.push("--no-gitignore".to_string());
572 }
573 }
574
575 match watch_options.watch {
576 Some(paths) => {
577 for watch_path in paths {
578 watch_args.extend_from_slice(&["-w".to_string(), watch_path])
579 }
580 }
581 _ => (),
582 };
583 }
584 _ => watch_args.push("-q".to_string()),
585 },
586 None => watch_args.push("-q".to_string()),
587 }
588
589 watch_args.extend_from_slice(&["-x".to_string(), make_command.to_string()]);
590
591 task_config.args = Some(watch_args);
592
593 task_config
594}
595
596pub(crate) fn run_flow(
597 flow_info: &FlowInfo,
598 flow_state: Rc<RefCell<FlowState>>,
599 sub_flow: bool,
600) -> Result<(), CargoMakeError> {
601 let allow_private = sub_flow || flow_info.allow_private;
602
603 let execution_plan = ExecutionPlanBuilder {
604 crate_info: Some(&flow_info.env_info.crate_info),
605 disable_workspace: flow_info.disable_workspace,
606 allow_private,
607 sub_flow,
608 skip_tasks_pattern: flow_info.skip_tasks_pattern.as_ref(),
609 skip_init_end_tasks: flow_info.skip_init_end_tasks,
610 ..ExecutionPlanBuilder::new(&flow_info.config, &flow_info.task)
611 }
612 .build()?;
613 debug!("Created execution plan: {:#?}", &execution_plan);
614
615 run_task_flow(&flow_info, flow_state, &execution_plan)?;
616
617 Ok(())
618}
619
620fn run_protected_flow(
621 flow_info: &FlowInfo,
622 flow_state: Rc<RefCell<FlowState>>,
623) -> Result<(), CargoMakeError> {
624 let proxy_task = create_proxy_task(
625 &flow_info.task,
626 flow_info.allow_private,
627 flow_info.skip_init_end_tasks,
628 None,
629 flow_info.cli_arguments.clone(),
630 );
631
632 let exit_code = command::run_command(&proxy_task.command.unwrap(), &proxy_task.args, false)?;
633
634 if exit_code != 0 {
635 match flow_info.config.config.on_error_task {
636 Some(ref on_error_task) => {
637 let mut error_flow_info = flow_info.clone();
638 error_flow_info.disable_on_error = true;
639 error_flow_info.task = on_error_task.clone();
640
641 run_flow(&error_flow_info, flow_state, false)?;
642 }
643 _ => (),
644 };
645
646 error!("Task error detected, exit code: {}", &exit_code);
647 }
648 Ok(())
649}
650
651pub fn run(
657 config: Config,
658 task: &str,
659 env_info: EnvInfo,
660 cli_args: &CliArgs,
661 start_time: SystemTime,
662 time_summary_vec: Vec<(String, u128)>,
663) -> Result<(), CargoMakeError> {
664 time_summary::init(&config, &cli_args);
665
666 let skip_tasks_pattern = match cli_args.skip_tasks_pattern {
667 Some(ref pattern) => match Regex::new(pattern) {
668 Ok(reg) => Some(reg),
669 Err(_) => {
670 warn!("Invalid skip tasks pattern provided: {}", pattern);
671 None
672 }
673 },
674 None => None,
675 };
676
677 let flow_info = FlowInfo {
678 config,
679 task: task.to_string(),
680 env_info,
681 disable_workspace: cli_args.disable_workspace,
682 disable_on_error: cli_args.disable_on_error,
683 allow_private: cli_args.allow_private,
684 skip_init_end_tasks: cli_args.skip_init_end_tasks,
685 skip_tasks_pattern,
686 cli_arguments: cli_args.arguments.clone(),
687 };
688 let mut flow_state = FlowState::new();
689 flow_state.time_summary = time_summary_vec;
690
691 let flow_state_rc = Rc::new(RefCell::new(flow_state));
692
693 if flow_info.disable_on_error || flow_info.config.config.on_error_task.is_none() {
694 run_flow(&flow_info, flow_state_rc.clone(), false)?;
695 } else {
696 run_protected_flow(&flow_info, flow_state_rc.clone())?;
697 }
698
699 let time_string = match start_time.elapsed() {
700 Ok(elapsed) => {
701 let time_millies = elapsed.as_millis() as f64 / 1000.0;
702 format!(" in {:.2} seconds", time_millies)
703 }
704 _ => "".to_string(),
705 };
706
707 time_summary::print(&flow_state_rc.borrow().time_summary);
708
709 info!("Build Done{}.", &time_string);
710
711 Ok(())
712}