cli/
runner.rs

1//! # runner
2//!
3//! Runs the requested tasks.<br>
4//! The flow is as follows:
5//!
6//! * Load env variables
7//! * Create an execution plan based on the requested task and its dependencies
8//! * Run all tasks defined in the execution plan
9//!
10
11#[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    // revert to original cwd
66    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    // run task as a sub process
167    let step = create_fork_step(&flow_info);
168
169    match cleanup_task {
170        Some(cleanup_task_name) => {
171            // run the forked task (forked tasks only run a command + args)
172            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
186/// runs a sub task and returns true/false based if a sub task was actually invoked
187fn 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        // clean up task only supported for forked tasks
216        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                // we do not support merging changes back to parent
239                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 a plugin is handling the task execution flow
349    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            //get profile
402            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            //make sure profile env is not overwritten
414            profile::set(&profile_name);
415
416            // modify step using env and functions
417            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                            // run script
448                            let script_runner_done = scriptengine::invoke(
449                                &updated_step.config,
450                                flow_info,
451                                flow_state.clone(),
452                            )?;
453
454                            // run command
455                            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(), // current version
536                };
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
651/// Runs the requested tasks.<br>
652/// The flow is as follows:
653///
654/// * Create an execution plan based on the requested task and its dependencies
655/// * Run all tasks defined in the execution plan
656pub 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}