sagiegurari / cargo-make
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"]
13
mod runner_test;
14

15
use crate::command;
16
use crate::condition;
17
use crate::environment;
18
use crate::execution_plan::create as create_execution_plan;
19
use crate::functions;
20
use crate::installer;
21
use crate::logger;
22
use crate::profile;
23
use crate::scriptengine;
24
use crate::time_summary;
25
use crate::types::{
26
    CliArgs, Config, DeprecationInfo, EnvInfo, EnvValue, ExecutionPlan, FlowInfo, FlowState,
27
    RunTaskInfo, RunTaskName, RunTaskRoutingInfo, Step, Task, TaskWatchOptions,
28
};
29
use indexmap::IndexMap;
30
use std::env;
31
use std::thread;
32
use std::time::SystemTime;
33

34 8
fn do_in_task_working_directory<F>(step: &Step, mut action: F)
35
where
36
    F: FnMut(),
37
{
38 2
    let revert_directory = match step.config.cwd {
39 8
        Some(ref cwd) => {
40 2
            let expanded_cwd = environment::expand_value(cwd);
41

42 2
            if expanded_cwd.len() > 0 {
43 2
                let directory = envmnt::get_or("CARGO_MAKE_WORKING_DIRECTORY", "");
44

45 2
                environment::setup_cwd(Some(&expanded_cwd));
46

47 2
                directory
48 2
            } else {
49 0
                "".to_string()
50
            }
51 2
        }
52 8
        None => "".to_string(),
53
    };
54

55 8
    action();
56

57
    // revert to original cwd
58 2
    match step.config.cwd {
59 8
        Some(_) => {
60 2
            environment::setup_cwd(Some(&revert_directory));
61
        }
62
        _ => (),
63
    };
64 8
}
65

66 8
fn validate_condition(flow_info: &FlowInfo, step: &Step) -> bool {
67 8
    let mut valid = true;
68

69 8
    let do_validate = || {
70 8
        valid = condition::validate_condition_for_step(&flow_info, &step);
71 8
    };
72

73 8
    do_in_task_working_directory(&step, do_validate);
74

75 2
    valid
76 8
}
77

78 8
pub(crate) fn get_sub_task_info_for_routing_info(
79
    flow_info: &FlowInfo,
80
    routing_info: &Vec<RunTaskRoutingInfo>,
81
) -> (Option<Vec<String>>, bool, bool, Option<String>) {
82 8
    let mut task_name = None;
83

84 8
    let mut fork = false;
85 8
    let mut parallel = false;
86 8
    let mut cleanup_task = None;
87 8
    for routing_step in routing_info {
88 8
        let invoke = condition::validate_conditions(
89 8
            &flow_info,
90 8
            &routing_step.condition,
91 8
            &routing_step.condition_script,
92 8
            None,
93
        );
94

95 8
        if invoke {
96 8
            let task_name_values = match routing_step.name.clone() {
97 8
                RunTaskName::Single(name) => vec![name],
98 8
                RunTaskName::Multiple(names) => names,
99 2
            };
100 8
            task_name = Some(task_name_values);
101 8
            fork = routing_step.fork.unwrap_or(false);
102 8
            parallel = routing_step.parallel.unwrap_or(false);
103 8
            cleanup_task = routing_step.cleanup_task.clone();
104 6
            break;
105 8
        }
106
    }
107

108 8
    (task_name, fork, parallel, cleanup_task)
109 8
}
110

111 8
fn create_fork_step(flow_info: &FlowInfo) -> Step {
112 8
    let fork_task = create_proxy_task(&flow_info.task, true, true);
113

114 8
    Step {
115 8
        name: "cargo_make_run_fork".to_string(),
116 8
        config: fork_task,
117
    }
118 8
}
119

120 0
fn run_cleanup_task(flow_info: &FlowInfo, flow_state: &mut FlowState, task: &str) {
121 0
    match flow_info.config.tasks.get(task) {
122 0
        Some(cleanup_task_info) => run_task(
123 0
            &flow_info,
124 0
            flow_state,
125 0
            &Step {
126 0
                name: task.to_string(),
127 0
                config: cleanup_task_info.clone(),
128 0
            },
129 0
        ),
130 0
        None => error!("Cleanup task: {} not found.", &task),
131
    }
132 0
}
133

134 0
fn run_forked_task(
135
    flow_info: &FlowInfo,
136
    flow_state: &mut FlowState,
137
    cleanup_task: &Option<String>,
138
) {
139
    // run task as a sub process
140 0
    let step = create_fork_step(&flow_info);
141

142 0
    match cleanup_task {
143 0
        Some(cleanup_task_name) => {
144
            // run the forked task (forked tasks only run a command + args)
145
            let exit_code =
146 0
                command::run_command(&step.config.command.unwrap(), &step.config.args, false);
147

148 0
            if exit_code != 0 {
149 0
                run_cleanup_task(&flow_info, flow_state, &cleanup_task_name);
150 0
                command::validate_exit_code(exit_code);
151
            }
152
        }
153 0
        None => run_task(&flow_info, flow_state, &step),
154
    }
155 0
}
156

157
/// runs a sub task and returns true/false based if a sub task was actually invoked
158 2
fn run_sub_task_and_report(
159
    flow_info: &FlowInfo,
160
    flow_state: &mut FlowState,
161
    sub_task: &RunTaskInfo,
162
) -> bool {
163 2
    let (task_names, fork, parallel, cleanup_task) = match sub_task {
164 2
        RunTaskInfo::Name(ref name) => (Some(vec![name.to_string()]), false, false, None),
165 2
        RunTaskInfo::Details(ref details) => {
166 2
            let task_name_values = match details.name.clone() {
167 2
                RunTaskName::Single(name) => vec![name],
168 2
                RunTaskName::Multiple(names) => names,
169 0
            };
170 2
            (
171 2
                Some(task_name_values),
172 2
                details.fork.unwrap_or(false),
173 2
                details.parallel.unwrap_or(false),
174 2
                details.cleanup_task.clone(),
175 0
            )
176 2
        }
177 2
        RunTaskInfo::Routing(ref routing_info) => {
178 2
            get_sub_task_info_for_routing_info(&flow_info, routing_info)
179
        }
180
    };
181

182 2
    if task_names.is_some() {
183 2
        let names = task_names.unwrap();
184 2
        let mut threads = vec![];
185

186
        // clean up task only supported for forked tasks
187 2
        if !fork && cleanup_task.is_some() {
188 2
            error!("Invalid task, cannot use cleanup_task without fork.");
189
        }
190

191 2
        for name in names {
192 2
            let task_run_fn = move |flow_info: &FlowInfo,
193
                                    flow_state: &mut FlowState,
194
                                    fork: bool,
195
                                    cleanup_task: &Option<String>| {
196 2
                let mut sub_flow_info = flow_info.clone();
197 2
                sub_flow_info.task = name;
198

199 2
                if fork {
200 0
                    run_forked_task(&sub_flow_info, flow_state, cleanup_task);
201
                } else {
202 2
                    run_flow(&sub_flow_info, flow_state, true);
203
                }
204 2
            };
205

206 2
            if parallel {
207 0
                let run_flow_info = flow_info.clone();
208
                // we do not support merging changes back to parent
209 0
                let mut cloned_flow_state = flow_state.clone();
210 0
                let cloned_cleanup_task = cleanup_task.clone();
211 0
                threads.push(thread::spawn(move || {
212 0
                    task_run_fn(
213 0
                        &run_flow_info,
214 0
                        &mut cloned_flow_state,
215 0
                        fork,
216 0
                        &cloned_cleanup_task,
217
                    );
218 0
                }));
219 0
            } else {
220 2
                task_run_fn(&flow_info, flow_state, fork, &cleanup_task);
221
            }
222 2
        }
223

224 2
        if threads.len() > 0 {
225 0
            for task_thread in threads {
226 0
                task_thread.join().unwrap();
227 0
            }
228
        }
229

230 2
        if let Some(cleanup_task_name) = cleanup_task {
231 0
            run_cleanup_task(&flow_info, flow_state, &cleanup_task_name);
232 0
        }
233

234 2
        true
235 2
    } else {
236 2
        false
237
    }
238 2
}
239

240 2
fn run_sub_task(flow_info: &FlowInfo, flow_state: &mut FlowState, sub_task: &RunTaskInfo) {
241 2
    run_sub_task_and_report(&flow_info, flow_state, &sub_task);
242 2
}
243

244 8
fn create_watch_task_name(task: &str) -> String {
245 8
    let mut watch_task_name = "".to_string();
246 8
    watch_task_name.push_str(&task);
247 8
    watch_task_name.push_str("-watch");
248

249 2
    watch_task_name
250 4
}
251

252 8
fn create_watch_step(task: &str, options: Option<TaskWatchOptions>) -> Step {
253 8
    let watch_task = create_watch_task(&task, options);
254

255 8
    let watch_task_name = create_watch_task_name(&task);
256

257 8
    Step {
258 8
        name: watch_task_name,
259 8
        config: watch_task,
260
    }
261 8
}
262

263 0
fn watch_task(
264
    flow_info: &FlowInfo,
265
    flow_state: &mut FlowState,
266
    task: &str,
267
    options: Option<TaskWatchOptions>,
268
) {
269 0
    let step = create_watch_step(&task, options);
270

271 0
    run_task(&flow_info, flow_state, &step);
272 0
}
273

274 2
fn is_watch_enabled() -> bool {
275 2
    !envmnt::is_or("CARGO_MAKE_DISABLE_WATCH", false)
276 2
}
277

278 8
fn should_watch(task: &Task) -> bool {
279 8
    match task.watch {
280 8
        Some(ref watch_value) => match watch_value {
281 2
            TaskWatchOptions::Boolean(watch_bool) => {
282 2
                if *watch_bool {
283 2
                    is_watch_enabled()
284
                } else {
285 2
                    false
286
                }
287
            }
288 0
            TaskWatchOptions::Options(_) => is_watch_enabled(),
289
        },
290 8
        None => false,
291
    }
292 8
}
293

294 8
pub(crate) fn run_task(flow_info: &FlowInfo, flow_state: &mut FlowState, step: &Step) {
295 8
    let start_time = SystemTime::now();
296

297 8
    if step.config.is_actionable() {
298 2
        match step.config.env {
299 8
            Some(ref env) => environment::set_current_task_meta_info_env(env.clone()),
300
            None => (),
301
        };
302

303 8
        if validate_condition(&flow_info, &step) {
304 8
            if logger::should_reduce_output(&flow_info) && step.config.script.is_none() {
305 0
                debug!("Running Task: {}", &step.name);
306
            } else {
307 8
                info!("Running Task: {}", &step.name);
308
            }
309

310 8
            if !step.config.is_valid() {
311 2
                error!(
312 0
                    "Invalid task: {}, contains multiple actions.\n{:#?}",
313 2
                    &step.name, &step.config
314
                );
315
            }
316

317 8
            let deprecated_info = step.config.deprecated.clone();
318 2
            match deprecated_info {
319 8
                Some(deprecated) => match deprecated {
320 2
                    DeprecationInfo::Boolean(value) => {
321 2
                        if value {
322 2
                            warn!("Task: {} is deprecated.", &step.name);
323
                        }
324

325
                        ()
326
                    }
327 2
                    DeprecationInfo::Message(ref message) => {
328 2
                        warn!("Task: {} is deprecated - {}", &step.name, message);
329

330
                        ()
331
                    }
332 2
                },
333
                None => (),
334
            };
335

336
            //get profile
337 8
            let profile_name = profile::get();
338

339 2
            match step.config.env_files {
340 8
                Some(ref env_files) => environment::set_env_files(env_files.clone()),
341
                None => (),
342
            };
343 2
            match step.config.env {
344 8
                Some(ref env) => environment::set_env(env.clone()),
345
                None => (),
346
            };
347

348 8
            envmnt::set("CARGO_MAKE_CURRENT_TASK_NAME", &step.name);
349

350
            //make sure profile env is not overwritten
351 8
            profile::set(&profile_name);
352

353
            // modify step using env and functions
354 8
            let mut updated_step = functions::run(&step);
355 8
            updated_step = environment::expand_env(&updated_step);
356

357 8
            let watch = should_watch(&step.config);
358

359 8
            if watch {
360 0
                watch_task(
361 0
                    &flow_info,
362 0
                    flow_state,
363 0
                    &step.name,
364 0
                    step.config.watch.clone(),
365
                );
366
            } else {
367 8
                do_in_task_working_directory(&step, || {
368 8
                    installer::install(&updated_step.config, flow_info);
369 8
                });
370

371 8
                match step.config.run_task {
372 8
                    Some(ref sub_task) => {
373 2
                        time_summary::add(&mut flow_state.time_summary, &step.name, start_time);
374

375 2
                        run_sub_task(&flow_info, flow_state, sub_task);
376
                    }
377
                    None => {
378 8
                        do_in_task_working_directory(&step, || {
379
                            // run script
380
                            let script_runner_done =
381 8
                                scriptengine::invoke(&updated_step.config, flow_info);
382

383
                            // run command
384 8
                            if !script_runner_done {
385 2
                                command::run(&updated_step);
386
                            };
387 8
                        });
388

389 8
                        time_summary::add(&mut flow_state.time_summary, &step.name, start_time);
390
                    }
391
                };
392
            }
393 8
        } else {
394 0
            let fail_message = match step.config.condition {
395 0
                Some(ref condition) => match condition.fail_message {
396 0
                    Some(ref value) => value.to_string(),
397 0
                    None => "".to_string(),
398
                },
399 0
                None => "".to_string(),
400
            };
401

402 0
            if logger::should_reduce_output(&flow_info) && step.config.script.is_none() {
403 0
                debug!("Skipping Task: {} {}", &step.name, &fail_message);
404
            } else {
405 0
                info!("Skipping Task: {} {}", &step.name, &fail_message);
406
            }
407 0
        }
408
    } else {
409 2
        debug!("Ignoring Empty Task: {}", &step.name);
410
    }
411 8
}
412

413 2
fn run_task_flow(flow_info: &FlowInfo, flow_state: &mut FlowState, execution_plan: &ExecutionPlan) {
414 2
    for step in &execution_plan.steps {
415 2
        run_task(&flow_info, flow_state, &step);
416
    }
417 2
}
418

419 8
fn create_watch_task(task: &str, options: Option<TaskWatchOptions>) -> Task {
420 8
    let mut task_config = create_proxy_task(&task, true, true);
421

422 8
    let mut env_map = task_config.env.unwrap_or(IndexMap::new());
423 8
    env_map.insert(
424 8
        "CARGO_MAKE_DISABLE_WATCH".to_string(),
425 8
        EnvValue::Value("true".to_string()),
426 8
    );
427 8
    task_config.env = Some(env_map);
428

429 8
    let make_args = task_config.args.unwrap();
430 8
    let mut make_command = String::new();
431 8
    for make_arg in make_args {
432 8
        if make_arg.contains(" ") {
433 2
            make_command.push_str("\"");
434 2
            make_command.push_str(&make_arg);
435 2
            make_command.push_str("\"");
436
        } else {
437 8
            make_command.push_str(&make_arg);
438
        }
439

440 8
        make_command.push(' ');
441 8
    }
442 8
    make_command = make_command.trim().to_string();
443

444 8
    let mut watch_args = vec!["watch".to_string(), "-q".to_string()];
445

446 2
    match options {
447 8
        Some(task_watch_options) => match task_watch_options {
448 2
            TaskWatchOptions::Options(watch_options) => {
449 2
                let watch_version = match watch_options.version {
450 2
                    Some(value) => value.to_string(),
451 2
                    _ => "7.4.1".to_string(), // current version
452
                };
453 2
                task_config.install_crate_args = Some(vec!["--version".to_string(), watch_version]);
454

455 2
                match watch_options.postpone {
456 2
                    Some(value) => {
457 2
                        if value {
458 2
                            watch_args.push("--postpone".to_string());
459
                        }
460
                    }
461
                    _ => (),
462
                };
463

464
                match watch_options.ignore_pattern {
465 2
                    Some(value) => watch_args.extend_from_slice(&["-i".to_string(), value]),
466
                    _ => (),
467
                };
468

469 2
                match watch_options.no_git_ignore {
470 2
                    Some(value) => {
471 2
                        if value {
472 2
                            watch_args.push("--no-gitignore".to_string());
473
                        }
474
                    }
475
                    _ => (),
476
                };
477

478 2
                match watch_options.watch {
479 2
                    Some(paths) => {
480 2
                        for watch_path in paths {
481 2
                            watch_args.extend_from_slice(&["-w".to_string(), watch_path])
482 2
                        }
483
                    }
484
                    _ => (),
485
                };
486 2
            }
487 0
            _ => (),
488 2
        },
489
        _ => (),
490
    }
491

492 8
    watch_args.extend_from_slice(&["-x".to_string(), make_command.to_string()]);
493

494 8
    task_config.args = Some(watch_args);
495

496 8
    task_config
497 8
}
498

499 8
fn create_proxy_task(task: &str, allow_private: bool, skip_init_end_tasks: bool) -> Task {
500
    //get log level name
501 8
    let log_level = logger::get_log_level();
502

503 8
    let mut log_level_arg = "--loglevel=".to_string();
504 8
    log_level_arg.push_str(&log_level);
505

506
    //get profile
507 8
    let profile_name = profile::get();
508

509 8
    let mut profile_arg = "--profile=".to_string();
510 8
    profile_arg.push_str(&profile_name);
511

512
    //setup common args
513 8
    let mut args = vec![
514 8
        "make".to_string(),
515 8
        "--disable-check-for-updates".to_string(),
516 8
        "--no-on-error".to_string(),
517 8
        log_level_arg.to_string(),
518 8
        profile_arg.to_string(),
519
    ];
520

521 8
    if allow_private {
522 8
        args.push("--allow-private".to_string());
523
    }
524

525 8
    if skip_init_end_tasks {
526 8
        args.push("--skip-init-end-tasks".to_string());
527
    }
528

529
    //get makefile location
530 8
    match env::var("CARGO_MAKE_MAKEFILE_PATH") {
531 8
        Ok(makefile_path) => {
532 8
            if makefile_path.len() > 0 {
533 8
                args.push("--makefile".to_string());
534 8
                args.push(makefile_path);
535
            }
536 6
        }
537 2
        _ => (),
538 8
    };
539

540 8
    args.push(task.to_string());
541

542 8
    let mut proxy_task = Task::new();
543 8
    proxy_task.command = Some("cargo".to_string());
544 8
    proxy_task.args = Some(args);
545

546 8
    proxy_task.get_normalized_task()
547 2
}
548

549 2
fn run_flow(flow_info: &FlowInfo, flow_state: &mut FlowState, sub_flow: bool) {
550 2
    let allow_private = sub_flow || flow_info.allow_private;
551

552 2
    let execution_plan = create_execution_plan(
553 2
        &flow_info.config,
554 2
        &flow_info.task,
555 2
        flow_info.disable_workspace,
556 2
        allow_private,
557 0
        sub_flow,
558
    );
559 2
    debug!("Created execution plan: {:#?}", &execution_plan);
560

561 2
    run_task_flow(&flow_info, flow_state, &execution_plan);
562 2
}
563

564 2
fn run_protected_flow(flow_info: &FlowInfo, flow_state: &mut FlowState) {
565 2
    let proxy_task = create_proxy_task(
566 2
        &flow_info.task,
567 2
        flow_info.allow_private,
568 2
        flow_info.skip_init_end_tasks,
569
    );
570

571 2
    let exit_code = command::run_command(&proxy_task.command.unwrap(), &proxy_task.args, false);
572

573 2
    if exit_code != 0 {
574 2
        match flow_info.config.config.on_error_task {
575 2
            Some(ref on_error_task) => {
576 2
                let mut error_flow_info = flow_info.clone();
577 2
                error_flow_info.disable_on_error = true;
578 2
                error_flow_info.task = on_error_task.clone();
579

580 2
                run_flow(&error_flow_info, flow_state, false);
581 2
            }
582
            _ => (),
583
        };
584

585 2
        error!("Task error detected, exit code: {}", &exit_code);
586
    }
587 0
}
588

589
/// Runs the requested tasks.<br>
590
/// The flow is as follows:
591
///
592
/// * Create an execution plan based on the requested task and its dependencies
593
/// * Run all tasks defined in the execution plan
594 2
pub(crate) fn run(config: Config, task: &str, env_info: EnvInfo, cli_args: &CliArgs) {
595 2
    let start_time = SystemTime::now();
596

597 2
    time_summary::init(&config, &cli_args);
598

599 2
    let flow_info = FlowInfo {
600 2
        config,
601 2
        task: task.to_string(),
602 2
        env_info,
603 2
        disable_workspace: cli_args.disable_workspace,
604 2
        disable_on_error: cli_args.disable_on_error,
605 2
        allow_private: cli_args.allow_private,
606 2
        skip_init_end_tasks: cli_args.skip_init_end_tasks,
607 2
        cli_arguments: cli_args.arguments.clone(),
608 0
    };
609 2
    let mut flow_state = FlowState::new();
610

611 2
    if flow_info.disable_on_error || flow_info.config.config.on_error_task.is_none() {
612 2
        run_flow(&flow_info, &mut flow_state, false);
613
    } else {
614 2
        run_protected_flow(&flow_info, &mut flow_state);
615
    }
616

617 2
    let time_string = match start_time.elapsed() {
618 2
        Ok(elapsed) => {
619 2
            let mut string = "in ".to_string();
620 2
            string.push_str(&elapsed.as_secs().to_string());
621 2
            string.push_str(" seconds");
622

623 2
            string
624 0
        }
625 0
        _ => "".to_string(),
626
    };
627

628 2
    time_summary::print(&flow_state.time_summary);
629

630 2
    info!("Build Done {}.", &time_string);
631 2
}

Read our documentation on viewing source code .

Loading