sagiegurari / cargo-make
1
//! # execution_plan
2
//!
3
//! Creates execution plan for a given task and makefile.
4
//!
5

6
#[cfg(test)]
7
#[path = "./execution_plan_test.rs"]
8
mod execution_plan_test;
9

10
use crate::environment;
11
use crate::logger;
12
use crate::profile;
13
use crate::types::{
14
    Config, CrateInfo, EnvValue, ExecutionPlan, ScriptValue, Step, Task, Workspace,
15
};
16
use envmnt;
17
use glob::Pattern;
18
use indexmap::IndexMap;
19
use std::collections::HashSet;
20
use std::env;
21
use std::path;
22
use std::path::Path;
23
use std::vec::Vec;
24

25
/// Resolve aliases to different tasks, checking for cycles
26 8
fn get_task_name_recursive(config: &Config, name: &str, seen: &mut Vec<String>) -> Option<String> {
27 8
    seen.push(name.to_string());
28

29 8
    match config.tasks.get(name) {
30 8
        Some(task_config) => {
31 8
            let alias = task_config.get_alias();
32

33 8
            match alias {
34 8
                Some(ref alias) if seen.contains(alias) => {
35 8
                    let chain = seen.join(" -> ");
36 8
                    error!("Detected cycle while resolving alias {}: {}", &name, chain);
37 0
                    panic!("Detected cycle while resolving alias {}: {}", &name, chain);
38 8
                }
39 8
                Some(ref alias) => get_task_name_recursive(config, alias, seen),
40 8
                _ => Some(name.to_string()),
41
            }
42 8
        }
43 8
        None => None,
44
    }
45 8
}
46

47
/// Returns the actual task name to invoke as tasks may have aliases
48 8
fn get_task_name(config: &Config, name: &str) -> Option<String> {
49 8
    let mut seen = Vec::new();
50

51 8
    get_task_name_recursive(config, name, &mut seen)
52 8
}
53

54 8
pub(crate) fn get_normalized_task(config: &Config, name: &str, support_alias: bool) -> Task {
55 8
    match get_optional_normalized_task(config, name, support_alias) {
56 8
        Some(task) => task,
57
        None => {
58 8
            error!("Task {} not found", &name);
59 6
            panic!("Task {} not found", &name);
60
        }
61
    }
62 8
}
63

64 8
fn get_optional_normalized_task(config: &Config, name: &str, support_alias: bool) -> Option<Task> {
65 8
    let actual_task_name_option = if support_alias {
66 8
        get_task_name(config, name)
67
    } else {
68 8
        Some(name.to_string())
69
    };
70

71 8
    match actual_task_name_option {
72 8
        Some(actual_task_name) => match config.tasks.get(&actual_task_name) {
73 8
            Some(task_config) => {
74 8
                let mut clone_task = task_config.clone();
75 8
                let mut normalized_task = clone_task.get_normalized_task();
76

77 8
                normalized_task = match normalized_task.extend {
78 8
                    Some(ref extended_task_name) => {
79
                        let mut extended_task =
80 8
                            get_normalized_task(config, extended_task_name, support_alias);
81

82 8
                        extended_task.extend(&normalized_task);
83

84 8
                        extended_task
85 0
                    }
86 8
                    None => normalized_task,
87
                };
88

89 8
                Some(normalized_task)
90 8
            }
91 0
            None => None,
92 8
        },
93 8
        None => None,
94
    }
95 8
}
96

97 8
fn get_workspace_members_config(members_config: String) -> HashSet<String> {
98 8
    let mut members = HashSet::new();
99

100 8
    let members_list: Vec<&str> = members_config.split(';').collect();
101

102 8
    for member in members_list.iter() {
103 8
        if member.len() > 0 {
104 8
            members.insert(member.to_string());
105
        }
106
    }
107

108 8
    members
109 8
}
110

111 8
fn is_workspace_member_found(member: &str, members_map: &HashSet<String>) -> bool {
112 8
    if members_map.contains(member) {
113 8
        true
114
    } else {
115
        // search for globs
116 8
        let mut found = false;
117

118 8
        for member_iter in members_map {
119 8
            if member_iter.contains("*") {
120 8
                found = match Pattern::new(member_iter) {
121 8
                    Ok(pattern) => pattern.matches(&member),
122 0
                    _ => false,
123 8
                };
124

125 8
                if found {
126 6
                    break;
127
                }
128
            }
129
        }
130

131 8
        found
132
    }
133 8
}
134

135 8
fn should_skip_workspace_member(member: &str, skipped_members: &HashSet<String>) -> bool {
136 8
    is_workspace_member_found(member, skipped_members)
137 8
}
138

139 2
fn should_include_workspace_member(member: &str, include_members: &HashSet<String>) -> bool {
140 2
    if include_members.is_empty() {
141 2
        true
142
    } else {
143 2
        is_workspace_member_found(member, include_members)
144
    }
145 2
}
146

147 8
fn update_member_path(member: &str) -> String {
148 8
    let os_separator = path::MAIN_SEPARATOR.to_string();
149

150
    //convert to OS path separators
151 8
    let mut member_path = str::replace(&member, "\\", &os_separator);
152 8
    member_path = str::replace(&member_path, "/", &os_separator);
153

154 2
    member_path
155 8
}
156

157 8
fn filter_workspace_members(members: &Vec<String>) -> Vec<String> {
158 8
    let skip_members_config = envmnt::get_or("CARGO_MAKE_WORKSPACE_SKIP_MEMBERS", "");
159 8
    let skip_members = get_workspace_members_config(skip_members_config);
160

161 8
    let include_members_config = envmnt::get_or("CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS", "");
162 8
    let include_members = get_workspace_members_config(include_members_config);
163

164 8
    let mut filtered_members = vec![];
165 8
    for member in members {
166 2
        if !should_skip_workspace_member(&member, &skip_members)
167 2
            && should_include_workspace_member(&member, &include_members)
168
        {
169 2
            filtered_members.push(member.to_string());
170
        } else {
171 2
            debug!("Skipping Member: {}.", &member);
172
        }
173
    }
174

175 2
    filtered_members
176 8
}
177

178 8
fn create_workspace_task(crate_info: CrateInfo, task: &str) -> Task {
179 8
    let set_workspace_emulation = crate_info.workspace.is_none()
180 0
        && envmnt::is("CARGO_MAKE_WORKSPACE_EMULATION")
181 0
        && !envmnt::exists("CARGO_MAKE_WORKSPACE_EMULATION_ROOT_DIRECTORY");
182 8
    if set_workspace_emulation {
183 0
        environment::search_and_set_workspace_cwd();
184 0
        let root_directory = envmnt::get_or_panic("CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY");
185 0
        envmnt::set(
186
            "CARGO_MAKE_WORKSPACE_EMULATION_ROOT_DIRECTORY",
187
            &root_directory,
188
        );
189 0
    }
190

191 8
    let members = if crate_info.workspace.is_some() {
192 8
        let workspace = crate_info.workspace.unwrap_or(Workspace::new());
193 8
        workspace.members.unwrap_or(vec![])
194 8
    } else {
195 0
        envmnt::get_list("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap_or(vec![])
196
    };
197

198 8
    let log_level = logger::get_log_level();
199

200 8
    let profile_name = if envmnt::is_or("CARGO_MAKE_USE_WORKSPACE_PROFILE", true) {
201 8
        profile::get()
202
    } else {
203 2
        profile::DEFAULT_PROFILE.to_string()
204
    };
205

206 8
    let filtered_members = filter_workspace_members(&members);
207

208 8
    let cargo_make_command = "cargo make";
209

210 8
    let mut script_lines = vec![];
211 8
    for member in &filtered_members {
212
        //convert to OS path separators
213 2
        let member_path = update_member_path(&member);
214

215 0
        let mut cd_line = if cfg!(windows) {
216 0
            "PUSHD ".to_string()
217
        } else {
218 2
            "cd ./".to_string()
219
        };
220 2
        cd_line.push_str(&member_path);
221 2
        script_lines.push(cd_line);
222

223
        //get member name
224 2
        let member_name = match Path::new(&member_path).file_name() {
225 2
            Some(name) => String::from(name.to_string_lossy()),
226 0
            None => member_path.clone(),
227
        };
228

229 2
        debug!("Adding Member: {} Path: {}", &member_name, &member_path);
230

231 2
        let mut make_line = cargo_make_command.to_string();
232 2
        make_line
233
            .push_str(" --disable-check-for-updates --allow-private --no-on-error --loglevel=");
234 2
        make_line.push_str(&log_level);
235 2
        make_line.push_str(" --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=");
236 2
        make_line.push_str(&member_name);
237 2
        make_line.push_str(" --profile ");
238 2
        make_line.push_str(&profile_name);
239 2
        make_line.push_str(" -- ");
240 2
        make_line.push_str(&task);
241

242 2
        if let Some(args) = envmnt::get_list("CARGO_MAKE_TASK_ARGS") {
243 2
            for arg in args {
244 2
                make_line.push_str(" ");
245 2
                make_line.push_str(&arg);
246 2
            }
247 2
        }
248

249 2
        script_lines.push(make_line);
250

251 0
        if cfg!(windows) {
252 0
            script_lines.push("if %errorlevel% neq 0 exit /b %errorlevel%".to_string());
253 0
            script_lines.push("POPD".to_string());
254
        } else {
255 2
            script_lines.push("cd -".to_string());
256
        };
257 2
    }
258

259
    //only if environment variable is set
260 8
    let task_env = if envmnt::is_or("CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE", false) {
261 2
        match env::var("CARGO_MAKE_MAKEFILE_PATH") {
262 2
            Ok(makefile) => {
263 2
                let mut env_map = IndexMap::new();
264 2
                env_map.insert(
265 2
                    "CARGO_MAKE_WORKSPACE_MAKEFILE".to_string(),
266 2
                    EnvValue::Value(makefile.to_string()),
267 2
                );
268

269 2
                Some(env_map)
270 2
            }
271 0
            _ => None,
272
        }
273 2
    } else {
274 8
        None
275
    };
276

277 8
    debug!("Workspace Task Script: {:#?}", &script_lines);
278

279 8
    let mut workspace_task = Task::new();
280 8
    workspace_task.script = Some(ScriptValue::Text(script_lines));
281 8
    workspace_task.env = task_env;
282

283 8
    workspace_task
284 8
}
285

286 8
fn is_workspace_flow(
287
    config: &Config,
288
    task: &str,
289
    disable_workspace: bool,
290
    crate_info: &CrateInfo,
291
    sub_flow: bool,
292
) -> bool {
293
    // determine if workspace flow is explicitly set and enabled in the requested task
294 8
    let (task_set_workspace, task_enable_workspace) =
295 8
        match get_optional_normalized_task(config, task, true) {
296 8
            Some(normalized_task) => match normalized_task.workspace {
297 8
                Some(enable_workspace) => (true, enable_workspace),
298 8
                None => (false, false),
299 8
            },
300 8
            None => (false, false),
301 2
        };
302

303
    // if project is not a workspace or if workspace is disabled via cli, return no workspace flow
304 8
    if disable_workspace
305 8
        || (sub_flow && !task_enable_workspace)
306 8
        || (crate_info.workspace.is_none() && !envmnt::is("CARGO_MAKE_WORKSPACE_EMULATION"))
307 8
        || envmnt::exists("CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER")
308
    {
309 8
        false
310
    } else {
311
        // project is a workspace and wasn't disabled via cli, need to check requested task
312

313
        // use requested task's workspace flag if set
314 8
        if task_set_workspace {
315 8
            task_enable_workspace
316
        } else {
317
            // use configured default workspace flag if set
318 8
            config.config.default_to_workspace.unwrap_or(true)
319
        }
320
    }
321 8
}
322

323
/// Creates an execution plan for the given step based on existing execution plan data
324 8
fn create_for_step(
325
    config: &Config,
326
    task: &str,
327
    steps: &mut Vec<Step>,
328
    task_names: &mut HashSet<String>,
329
    root: bool,
330
    allow_private: bool,
331
) {
332 8
    let task_config = get_normalized_task(config, task, true);
333

334 8
    debug!("Normalized Task: {} config: {:#?}", &task, &task_config);
335

336 8
    let is_private = match task_config.private {
337 8
        Some(value) => value,
338 8
        None => false,
339
    };
340

341 8
    if allow_private || !is_private {
342 8
        let add = !task_config.disabled.unwrap_or(false);
343

344 8
        if add {
345 8
            match task_config.dependencies {
346 8
                Some(ref dependencies) => {
347 8
                    for dependency in dependencies {
348 8
                        create_for_step(&config, &dependency, steps, task_names, false, true);
349
                    }
350
                }
351 8
                _ => debug!("No dependencies found for task: {}", &task),
352
            };
353

354 8
            if !task_names.contains(task) {
355 8
                steps.push(Step {
356 8
                    name: task.to_string(),
357 8
                    config: task_config,
358
                });
359 8
                task_names.insert(task.to_string());
360 2
            } else if root {
361 0
                error!("Circular reference found for task: {}", &task);
362
            }
363
        }
364
    } else {
365 8
        error!("Task {} is private", &task);
366 0
        panic!("Task {} is private", &task);
367
    }
368 8
}
369

370
/// Creates the full execution plan
371 8
pub(crate) fn create(
372
    config: &Config,
373
    task: &str,
374
    disable_workspace: bool,
375
    allow_private: bool,
376
    sub_flow: bool,
377
) -> ExecutionPlan {
378 8
    let mut task_names = HashSet::new();
379 8
    let mut steps = Vec::new();
380

381 8
    if !sub_flow {
382 8
        match config.config.init_task {
383 8
            Some(ref task) => {
384 8
                let task_config = get_normalized_task(config, task, false);
385 8
                let add = !task_config.disabled.unwrap_or(false);
386

387 8
                if add {
388 8
                    steps.push(Step {
389 8
                        name: task.to_string(),
390 8
                        config: task_config,
391
                    });
392
                }
393 8
            }
394 8
            None => debug!("Init task not defined."),
395
        };
396
    }
397

398
    // load crate info and look for workspace info
399 8
    let crate_info = environment::crateinfo::load();
400

401 8
    let workspace_flow =
402 8
        is_workspace_flow(&config, &task, disable_workspace, &crate_info, sub_flow);
403

404 8
    if workspace_flow {
405 2
        let workspace_task = create_workspace_task(crate_info, task);
406

407 2
        steps.push(Step {
408 2
            name: "workspace".to_string(),
409 2
            config: workspace_task,
410
        });
411 2
    } else {
412 8
        create_for_step(
413 8
            &config,
414 8
            &task,
415
            &mut steps,
416
            &mut task_names,
417
            true,
418 2
            allow_private,
419
        );
420
    }
421

422 8
    if !sub_flow {
423
        // always add end task even if already executed due to some depedency
424 8
        match config.config.end_task {
425 8
            Some(ref task) => {
426 8
                let task_config = get_normalized_task(config, task, false);
427 8
                let add = !task_config.disabled.unwrap_or(false);
428

429 8
                if add {
430 8
                    steps.push(Step {
431 8
                        name: task.to_string(),
432 8
                        config: task_config,
433
                    });
434
                }
435 8
            }
436 8
            None => debug!("End task not defined."),
437
        };
438
    }
439

440 8
    ExecutionPlan { steps }
441 8
}

Read our documentation on viewing source code .

Loading