cli/environment/
mod.rs

1//! # env
2//!
3//! Sets up the env vars before running the tasks.
4//!
5
6pub(crate) mod crateinfo;
7
8#[cfg(test)]
9#[path = "mod_test.rs"]
10mod mod_test;
11
12use crate::command;
13use crate::condition;
14use crate::error::CargoMakeError;
15use crate::io;
16use crate::profile;
17use crate::scriptengine;
18use crate::time_summary;
19use crate::types::{
20    CliArgs, Config, CrateInfo, EnvFile, EnvInfo, EnvValue, EnvValueConditioned, EnvValueDecode,
21    EnvValuePathGlob, EnvValueScript, PackageInfo, ScriptValue, Step, Task, Workspace,
22};
23use ci_info::types::CiInfo;
24use envmnt::{ExpandOptions, ExpansionType};
25use fsio::path::from_path::FromPath;
26use git_info::types::GitInfo;
27use indexmap::IndexMap;
28use rust_info::types::{RustChannel, RustInfo};
29use std::env;
30use std::path::{Path, PathBuf};
31use std::time::SystemTime;
32
33fn evaluate_env_value(key: &str, env_value: &EnvValueScript) -> String {
34    match command::run_script_get_output(&env_value.script, None, &vec![], true, Some(false)) {
35        Ok(output) => {
36            let exit_code = output.0;
37            let stdout = output.1;
38            let stderr = output.2;
39
40            if exit_code != 0 {
41                error!(
42                    concat!(
43                        "Error while evaluating script for env: {}, exit code: {}\n",
44                        "Script:\n{:#?}\n",
45                        "Stdout:\n{}\n",
46                        "Stderr:\n{}\n"
47                    ),
48                    key, exit_code, env_value.script, &stdout, &stderr
49                );
50            }
51
52            debug!("Env script stdout:\n{}", &stdout);
53
54            let multi_line = match env_value.multi_line {
55                Some(bool_value) => bool_value,
56                None => false,
57            };
58
59            if multi_line {
60                stdout.to_string()
61            } else {
62                let mut lines: Vec<&str> = stdout.split("\n").collect();
63                lines.retain(|&line| line.len() > 0);
64
65                if lines.len() > 0 {
66                    let line = lines[lines.len() - 1].to_string();
67
68                    let line_str = str::replace(&line, "\r", "");
69
70                    line_str.to_string()
71                } else {
72                    "".to_string()
73                }
74            }
75        }
76        _ => "".to_string(),
77    }
78}
79
80pub(crate) fn expand_value(value: &str) -> String {
81    let mut options = ExpandOptions::new();
82    options.expansion_type = Some(ExpansionType::UnixBracketsWithDefaults);
83    options.default_to_empty = false;
84
85    envmnt::expand(&value, Some(options))
86}
87
88fn evaluate_and_set_env(key: &str, value: &str) {
89    let env_value = expand_value(&value);
90
91    debug!("Setting Env: {} Value: {}", &key, &env_value);
92    envmnt::set(&key, &env_value);
93}
94
95fn set_env_for_bool(key: &str, value: bool) {
96    debug!("Setting Env: {} Value: {}", &key, &value);
97    envmnt::set_bool(&key, value);
98}
99
100fn set_env_for_list(key: &str, list: &Vec<String>) {
101    let mut expanded_list = vec![];
102
103    for value in list {
104        let env_value = expand_value(&value);
105        expanded_list.push(env_value);
106    }
107
108    envmnt::set_list(&key, &expanded_list);
109}
110
111fn set_env_for_script(key: &str, env_value: &EnvValueScript) {
112    let valid = match env_value.condition {
113        Some(ref condition) => condition::validate_conditions_without_context(condition.clone()),
114        None => true,
115    };
116
117    if valid {
118        let value = evaluate_env_value(&key, &env_value);
119
120        evaluate_and_set_env(&key, &value);
121    }
122}
123
124fn set_env_for_decode_info(key: &str, decode_info: &EnvValueDecode) {
125    let valid = match decode_info.condition {
126        Some(ref condition) => condition::validate_conditions_without_context(condition.clone()),
127        None => true,
128    };
129
130    if valid {
131        let source_value = expand_value(&decode_info.source);
132
133        let mapped_value = match decode_info.mapping.get(&source_value) {
134            Some(value) => value.to_string(),
135            None => match decode_info.default_value {
136                Some(ref value) => value.clone().to_string(),
137                None => source_value.clone(),
138            },
139        };
140
141        evaluate_and_set_env(&key, &mapped_value);
142    }
143}
144
145fn set_env_for_conditional_value(key: &str, conditional_value: &EnvValueConditioned) {
146    let valid = match conditional_value.condition {
147        Some(ref condition) => condition::validate_conditions_without_context(condition.clone()),
148        None => true,
149    };
150
151    if valid {
152        let value = expand_value(&conditional_value.value);
153
154        evaluate_and_set_env(&key, &value);
155    }
156}
157
158fn set_env_for_path_glob(key: &str, path_glob: &EnvValuePathGlob) {
159    let path_list = io::get_path_list(
160        &path_glob.glob,
161        path_glob.include_files.unwrap_or(true),
162        path_glob.include_dirs.unwrap_or(true),
163        path_glob.ignore_type.clone(),
164    );
165
166    set_env_for_list(key, &path_list);
167}
168
169fn set_env_for_profile(
170    profile_name: &str,
171    sub_env: &IndexMap<String, EnvValue>,
172    additional_profiles: Option<&Vec<String>>,
173) {
174    let current_profile_name = profile::get();
175    let profile_name_string = profile_name.to_string();
176
177    let found = match additional_profiles {
178        Some(profiles) => profiles.contains(&profile_name_string),
179        None => false,
180    };
181
182    if current_profile_name == profile_name_string || found {
183        debug!("Setting Up Profile: {} Env.", &profile_name);
184
185        set_env_for_config(sub_env.clone(), None, false);
186    }
187}
188
189/// Updates the env based on the provided data
190pub(crate) fn set_env(env: IndexMap<String, EnvValue>) {
191    set_env_for_config(env, None, true)
192}
193
194fn unset_env(key: &str) {
195    envmnt::remove(key);
196}
197
198/// Updates the env based on the provided data
199pub(crate) fn set_env_for_config(
200    env: IndexMap<String, EnvValue>,
201    additional_profiles: Option<&Vec<String>>,
202    allow_sub_env: bool,
203) {
204    debug!("Setting Up Env.");
205
206    for (key, env_value) in &env {
207        debug!("Setting env: {} = {:#?}", &key, &env_value);
208
209        match *env_value {
210            EnvValue::Value(ref value) => evaluate_and_set_env(&key, value),
211            EnvValue::Boolean(value) => set_env_for_bool(&key, value),
212            EnvValue::Number(value) => evaluate_and_set_env(&key, &value.to_string()),
213            EnvValue::List(ref value) => set_env_for_list(&key, value),
214            EnvValue::Script(ref script_info) => set_env_for_script(&key, script_info),
215            EnvValue::Decode(ref decode_info) => set_env_for_decode_info(&key, decode_info),
216            EnvValue::Conditional(ref conditioned_value) => {
217                set_env_for_conditional_value(&key, conditioned_value)
218            }
219            EnvValue::PathGlob(ref path_glob_info) => set_env_for_path_glob(&key, path_glob_info),
220            EnvValue::Profile(ref sub_env) => {
221                if allow_sub_env {
222                    set_env_for_profile(&key, sub_env, additional_profiles)
223                }
224            }
225            EnvValue::Unset(ref value) => {
226                if value.unset {
227                    unset_env(&key);
228                }
229            }
230        };
231    }
232
233    if allow_sub_env {
234        let profile_name = profile::get();
235
236        if env.contains_key(&profile_name) {
237            match env.get(&profile_name) {
238                Some(ref env_value) => {
239                    match *env_value {
240                        EnvValue::Profile(ref sub_env) => {
241                            set_env_for_profile(&profile_name, sub_env, None)
242                        }
243                        _ => (),
244                    };
245                }
246                None => (),
247            };
248        }
249    }
250}
251
252pub(crate) fn set_env_files(env_files: Vec<EnvFile>) {
253    set_env_files_for_config(env_files, None);
254}
255
256fn set_env_files_for_config(
257    env_files: Vec<EnvFile>,
258    additional_profiles: Option<&Vec<String>>,
259) -> bool {
260    let mut all_loaded = true;
261    for env_file in env_files {
262        let loaded = match env_file {
263            EnvFile::Path(file) => load_env_file(Some(file)),
264            EnvFile::Info(info) => {
265                let is_valid_profile = match info.profile {
266                    Some(profile_name) => {
267                        let current_profile_name = profile::get();
268
269                        let found = match additional_profiles {
270                            Some(profiles) => profiles.contains(&profile_name),
271                            None => false,
272                        };
273
274                        current_profile_name == profile_name || found
275                    }
276                    None => true,
277                };
278
279                if is_valid_profile {
280                    load_env_file_with_base_directory(
281                        Some(info.path),
282                        info.base_path,
283                        info.defaults_only.unwrap_or(false),
284                    )
285                } else {
286                    false
287                }
288            }
289        };
290
291        all_loaded = all_loaded && loaded;
292    }
293
294    all_loaded
295}
296
297fn set_env_scripts(
298    env_scripts: Vec<String>,
299    cli_arguments: &Vec<String>,
300) -> Result<(), CargoMakeError> {
301    for env_script in env_scripts {
302        if !env_script.is_empty() {
303            scriptengine::invoke_script_pre_flow(
304                &ScriptValue::Text(vec![env_script]),
305                None,
306                None,
307                None,
308                true,
309                cli_arguments,
310            )?;
311        }
312    }
313    Ok(())
314}
315
316pub(crate) fn set_current_task_meta_info_env(env: IndexMap<String, EnvValue>) {
317    debug!("Setting Up Env.");
318
319    for (key, env_value) in &env {
320        if key.starts_with("CARGO_MAKE_CURRENT_TASK_") {
321            debug!("Setting env: {} = {:#?}", &key, &env_value);
322
323            match *env_value {
324                EnvValue::Value(ref value) => evaluate_and_set_env(&key, value),
325                _ => (),
326            };
327        }
328    }
329}
330
331/// Updates the env for the current execution based on the descriptor.
332fn initialize_env(config: &Config, cli_args: &Vec<String>) -> Result<(), CargoMakeError> {
333    debug!("Initializing Env.");
334
335    let additional_profiles = match config.config.additional_profiles {
336        Some(ref profiles) => Some(profiles),
337        None => None,
338    };
339
340    set_env_files_for_config(config.env_files.clone(), additional_profiles);
341
342    set_env_for_config(config.env.clone(), additional_profiles, true);
343
344    set_env_scripts(config.env_scripts.clone(), cli_args)
345}
346
347fn setup_env_for_duckscript() {
348    let mut version = duckscript::version();
349    envmnt::set("CARGO_MAKE_DUCKSCRIPT_VERSION", version);
350
351    version = duckscriptsdk::version();
352    envmnt::set("CARGO_MAKE_DUCKSCRIPT_SDK_VERSION", version);
353}
354
355fn setup_env_for_crate(home: Option<PathBuf>) -> Result<CrateInfo, CargoMakeError> {
356    let crate_info = crateinfo::load()?;
357    let crate_info_clone = crate_info.clone();
358
359    let package_info = crate_info.package.unwrap_or(PackageInfo::new());
360
361    if package_info.name.is_some() {
362        let crate_name = package_info.name.unwrap();
363        envmnt::set("CARGO_MAKE_CRATE_NAME", &crate_name);
364
365        let crate_fs_name = str::replace(&crate_name, "-", "_");
366        envmnt::set("CARGO_MAKE_CRATE_FS_NAME", &crate_fs_name);
367    }
368
369    envmnt::set_optional("CARGO_MAKE_CRATE_VERSION", &package_info.version);
370    envmnt::set_optional("CARGO_MAKE_CRATE_DESCRIPTION", &package_info.description);
371    envmnt::set_optional("CARGO_MAKE_CRATE_LICENSE", &package_info.license);
372    envmnt::set_optional(
373        "CARGO_MAKE_CRATE_DOCUMENTATION",
374        &package_info.documentation,
375    );
376    envmnt::set_optional("CARGO_MAKE_CRATE_HOMEPAGE", &package_info.homepage);
377    envmnt::set_optional("CARGO_MAKE_CRATE_REPOSITORY", &package_info.repository);
378
379    let has_dependencies = match crate_info.dependencies {
380        Some(ref dependencies) => dependencies.len() > 0,
381        None => crate_info.workspace.is_some(),
382    };
383
384    envmnt::set_bool("CARGO_MAKE_CRATE_HAS_DEPENDENCIES", has_dependencies);
385
386    let is_workspace = !crate_info.workspace.is_none();
387    envmnt::set_bool("CARGO_MAKE_CRATE_IS_WORKSPACE", is_workspace);
388    if is_workspace {
389        envmnt::set_bool("CARGO_MAKE_USE_WORKSPACE_PROFILE", true);
390    } else if !envmnt::exists("CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER")
391        || envmnt::exists("CARGO_MAKE_WORKSPACE_EMULATION_ROOT_DIRECTORY")
392    {
393        // in case we started the build directly from a workspace member
394        // or we are running in a workspace emulation mode, lets
395        // search for the workspace root (if any)
396        search_and_set_workspace_cwd();
397    }
398
399    let workspace = crate_info.workspace.unwrap_or(Workspace::new());
400    let members = workspace.members.unwrap_or(vec![]);
401    let mut env_options = envmnt::ListOptions::new();
402    env_options.separator = Some(",".to_string());
403    envmnt::set_list_with_options("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", &members, &env_options);
404
405    if let Some(package) = workspace.package.as_ref() {
406        envmnt::set_optional("CARGO_MAKE_WORKSPACE_PACKAGE_NAME", &package.name);
407        envmnt::set_optional("CARGO_MAKE_WORKSPACE_PACKAGE_VERSION", &package.version);
408        envmnt::set_optional(
409            "CARGO_MAKE_WORKSPACE_PACKAGE_DESCRIPTION",
410            &package.description,
411        );
412        envmnt::set_optional("CARGO_MAKE_WORKSPACE_PACKAGE_LICENSE", &package.license);
413        envmnt::set_optional(
414            "CARGO_MAKE_WORKSPACE_PACKAGE_DOCUMENTATION",
415            &package.documentation,
416        );
417        envmnt::set_optional("CARGO_MAKE_WORKSPACE_PACKAGE_HOMEPAGE", &package.homepage);
418        envmnt::set_optional(
419            "CARGO_MAKE_WORKSPACE_PACKAGE_REPOSITORY",
420            &package.repository,
421        );
422    }
423
424    // check if Cargo.lock file exists in working directory
425    let lock_file = Path::new("Cargo.lock");
426    let lock_file_exists = lock_file.exists();
427    envmnt::set_bool("CARGO_MAKE_CRATE_LOCK_FILE_EXISTS", lock_file_exists);
428
429    let crate_target_dirs = crateinfo::crate_target_dirs(home);
430    envmnt::set("CARGO_MAKE_CRATE_TARGET_DIRECTORY", &crate_target_dirs.host);
431    match crate_target_dirs.custom {
432        Some(ref value) => envmnt::set("CARGO_MAKE_CRATE_CUSTOM_TRIPLE_TARGET_DIRECTORY", value),
433        None => envmnt::set(
434            "CARGO_MAKE_CRATE_CUSTOM_TRIPLE_TARGET_DIRECTORY",
435            &crate_target_dirs.host,
436        ),
437    }
438
439    Ok(crate_info_clone)
440}
441
442fn setup_env_for_git_repo() -> GitInfo {
443    let info = git_info::get();
444    let git_info_clone = info.clone();
445
446    envmnt::set_optional("CARGO_MAKE_GIT_BRANCH", &info.current_branch);
447    envmnt::set_optional("CARGO_MAKE_GIT_USER_NAME", &info.user_name);
448    envmnt::set_optional("CARGO_MAKE_GIT_USER_EMAIL", &info.user_email);
449    envmnt::set_optional(
450        "CARGO_MAKE_GIT_HEAD_LAST_COMMIT_HASH",
451        &info.head.last_commit_hash,
452    );
453    envmnt::set_optional(
454        "CARGO_MAKE_GIT_HEAD_LAST_COMMIT_HASH_PREFIX",
455        &info.head.last_commit_hash_short,
456    );
457
458    git_info_clone
459}
460
461fn setup_env_for_rust(home: Option<PathBuf>) -> RustInfo {
462    let rustinfo = rust_info::get();
463    let rust_info_clone = rustinfo.clone();
464
465    envmnt::set_optional("CARGO_MAKE_RUST_VERSION", &rustinfo.version);
466
467    if let Some(channel_option) = rustinfo.channel {
468        let channel = match channel_option {
469            RustChannel::Stable => "stable",
470            RustChannel::Beta => "beta",
471            RustChannel::Nightly => "nightly",
472        };
473
474        envmnt::set("CARGO_MAKE_RUST_CHANNEL", channel);
475    }
476
477    envmnt::set(
478        "CARGO_MAKE_RUST_TARGET_ARCH",
479        &rustinfo.target_arch.unwrap_or("unknown".to_string()),
480    );
481    envmnt::set(
482        "CARGO_MAKE_RUST_TARGET_ENV",
483        &rustinfo.target_env.unwrap_or("unknown".to_string()),
484    );
485    envmnt::set(
486        "CARGO_MAKE_RUST_TARGET_OS",
487        &rustinfo.target_os.unwrap_or("unknown".to_string()),
488    );
489    envmnt::set(
490        "CARGO_MAKE_RUST_TARGET_POINTER_WIDTH",
491        &rustinfo
492            .target_pointer_width
493            .unwrap_or("unknown".to_string()),
494    );
495    envmnt::set(
496        "CARGO_MAKE_RUST_TARGET_VENDOR",
497        &rustinfo.target_vendor.unwrap_or("unknown".to_string()),
498    );
499    envmnt::set_optional("CARGO_MAKE_RUST_TARGET_TRIPLE", &rustinfo.target_triple);
500    envmnt::set_or_remove(
501        "CARGO_MAKE_CRATE_TARGET_TRIPLE",
502        &crateinfo::crate_target_triple(rustinfo.target_triple, home.clone()),
503    );
504
505    rust_info_clone
506}
507
508fn setup_env_for_ci() -> CiInfo {
509    let ci_info_struct = ci_info::get();
510
511    envmnt::set_bool("CARGO_MAKE_CI", ci_info_struct.ci);
512    envmnt::set_bool("CARGO_MAKE_PR", ci_info_struct.pr.unwrap_or(false));
513    envmnt::set_optional("CARGO_MAKE_CI_BRANCH_NAME", &ci_info_struct.branch_name);
514    envmnt::set_optional("CARGO_MAKE_CI_VENDOR", &ci_info_struct.name);
515
516    ci_info_struct
517}
518
519fn get_base_directory_name() -> Option<String> {
520    match env::current_dir() {
521        Ok(path) => match path.file_name() {
522            Some(name) => Some(name.to_string_lossy().into_owned()),
523            None => None,
524        },
525        _ => None,
526    }
527}
528
529fn setup_env_for_project(config: &Config, crate_info: &CrateInfo) -> Result<(), CargoMakeError> {
530    let project_name = match crate_info.package {
531        Some(ref package) => match package.name {
532            Some(ref name) => Some(name.to_string()),
533            None => get_base_directory_name(),
534        },
535        None => get_base_directory_name(),
536    };
537
538    envmnt::set_or_remove("CARGO_MAKE_PROJECT_NAME", &project_name);
539
540    let project_version = match crate_info.workspace {
541        Some(_) => {
542            let main_member = match config.config.main_project_member {
543                Some(ref name) => Some(name.to_string()),
544                None => match project_name {
545                    Some(name) => Some(name),
546                    None => None,
547                },
548            };
549
550            envmnt::set_or_remove("CARGO_MAKE_PROJECT_VERSION_MEMBER", &main_member);
551
552            match main_member {
553                Some(member) => {
554                    let mut path = PathBuf::new();
555                    path.push(member);
556                    path.push("Cargo.toml");
557                    let member_crate_info = crateinfo::load_from(path)?;
558
559                    match member_crate_info.package {
560                        Some(package) => package.version,
561                        None => None,
562                    }
563                }
564                None => None,
565            }
566        }
567        None => match crate_info.package {
568            Some(ref package) => package.version.clone(),
569            None => None,
570        },
571    };
572
573    envmnt::set_or_remove("CARGO_MAKE_PROJECT_VERSION", &project_version);
574
575    Ok(())
576}
577
578/// Sets up the env before the tasks execution.
579pub(crate) fn setup_env(
580    cli_args: &CliArgs,
581    config: &Config,
582    task: &str,
583    home: Option<PathBuf>,
584    time_summary_vec: &mut Vec<(String, u128)>,
585) -> Result<EnvInfo, CargoMakeError> {
586    envmnt::set_bool("CARGO_MAKE", true);
587    envmnt::set("CARGO_MAKE_TASK", &task);
588
589    envmnt::set("CARGO_MAKE_COMMAND", &cli_args.command);
590
591    let task_arguments = match cli_args.arguments.clone() {
592        Some(args) => args,
593        None => vec![],
594    };
595    envmnt::set_list("CARGO_MAKE_TASK_ARGS", &task_arguments);
596
597    // load duckscript_info
598    let mut now = SystemTime::now();
599    setup_env_for_duckscript();
600    time_summary::add(time_summary_vec, "[Setup Env - Duckscript]", now);
601
602    // load crate info
603    now = SystemTime::now();
604    let crate_info = if config.config.skip_crate_env_info.unwrap_or(false) {
605        CrateInfo::new()
606    } else {
607        setup_env_for_crate(home.clone())?
608    };
609    time_summary::add(time_summary_vec, "[Setup Env - Crate Info]", now);
610
611    // load git info
612    now = SystemTime::now();
613    let gitinfo = if config.config.skip_git_env_info.unwrap_or(false) {
614        GitInfo::new()
615    } else {
616        setup_env_for_git_repo()
617    };
618    time_summary::add(time_summary_vec, "[Setup Env - Git]", now);
619
620    // load rust info
621    now = SystemTime::now();
622    let rustinfo = if config.config.skip_rust_env_info.unwrap_or(false) {
623        RustInfo::new()
624    } else {
625        setup_env_for_rust(home)
626    };
627    time_summary::add(time_summary_vec, "[Setup Env - Rust]", now);
628
629    // load CI info
630    now = SystemTime::now();
631    let ci_info_struct = setup_env_for_ci();
632    time_summary::add(time_summary_vec, "[Setup Env - CI]", now);
633
634    // setup project info
635    now = SystemTime::now();
636    setup_env_for_project(config, &crate_info)?;
637    time_summary::add(time_summary_vec, "[Setup Env - Project]", now);
638
639    // load env vars
640    now = SystemTime::now();
641    initialize_env(config, &cli_args.arguments.clone().unwrap_or(vec![]))?;
642    time_summary::add(time_summary_vec, "[Setup Env - Vars]", now);
643
644    Ok(EnvInfo {
645        rust_info: rustinfo,
646        crate_info,
647        git_info: gitinfo,
648        ci_info: ci_info_struct,
649    })
650}
651
652fn set_workspace_cwd(directory_path: &Path, force: bool) {
653    if force || !envmnt::exists("CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY") {
654        let directory_path_string: String = FromPath::from_path(directory_path);
655
656        envmnt::set(
657            "CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY",
658            directory_path_string,
659        );
660    }
661}
662
663pub(crate) fn search_and_set_workspace_cwd() {
664    match crateinfo::search_workspace_root() {
665        Some(root_directory) => {
666            let root_directory_path_buf = get_directory_path(Some(&root_directory));
667            let root_directory_path = root_directory_path_buf.as_path();
668            set_workspace_cwd(&root_directory_path, true);
669        }
670        None => (),
671    }
672}
673
674fn get_directory_path(path_option: Option<&str>) -> PathBuf {
675    let cwd_str = path_option.unwrap_or(".");
676    let directory = expand_value(cwd_str);
677
678    let directory_path_string = io::canonicalize_to_string(&directory);
679    PathBuf::from(directory_path_string)
680}
681
682pub(crate) fn setup_cwd(cwd: Option<&str>) -> Option<PathBuf> {
683    let directory_path_buf = get_directory_path(cwd);
684    let directory_path = directory_path_buf.as_path();
685
686    debug!(
687        "Changing working directory to: {}",
688        directory_path.display()
689    );
690
691    match env::set_current_dir(&directory_path) {
692        Err(error) => {
693            error!(
694                "Unable to set current working directory to: {} {:#?}",
695                directory_path.display(),
696                error
697            );
698            None
699        }
700        _ => {
701            envmnt::set("CARGO_MAKE_WORKING_DIRECTORY", &directory_path);
702
703            set_workspace_cwd(&directory_path, false);
704
705            debug!("Working directory changed to: {}", directory_path.display());
706
707            let home = home::cargo_home_with_cwd(directory_path).ok();
708
709            envmnt::set_optional("CARGO_MAKE_CARGO_HOME", &home);
710            home
711        }
712    }
713}
714
715pub(crate) fn load_env_file(env_file: Option<String>) -> bool {
716    load_env_file_with_base_directory(env_file, None, false)
717}
718
719fn load_env_file_with_base_directory(
720    env_file: Option<String>,
721    base_directory: Option<String>,
722    defaults_only: bool,
723) -> bool {
724    match env_file {
725        Some(file_name) => {
726            let expanded_file_name = expand_value(&file_name);
727
728            let file_path = if expanded_file_name.starts_with(".") {
729                let (base_path, check_relative_path) = match base_directory {
730                    Some(file) => (file, true),
731                    None => (envmnt::get_or("CARGO_MAKE_WORKING_DIRECTORY", "."), false),
732                };
733
734                if check_relative_path && base_path.starts_with(".") {
735                    Path::new(&envmnt::get_or("CARGO_MAKE_WORKING_DIRECTORY", "."))
736                        .join(&base_path)
737                        .join(expanded_file_name)
738                } else {
739                    Path::new(&base_path).join(expanded_file_name)
740                }
741            } else {
742                Path::new(&expanded_file_name).to_path_buf()
743            };
744
745            match file_path.to_str() {
746                Some(file_path_str) => {
747                    let evaluate_env_var = |key: String, value: String| {
748                        let skip = if defaults_only {
749                            envmnt::exists(&key)
750                        } else {
751                            false
752                        };
753
754                        if skip {
755                            None
756                        } else {
757                            Some((key, expand_value(&value)))
758                        }
759                    };
760
761                    match envmnt::evaluate_and_load_file(file_path_str, evaluate_env_var) {
762                        Err(error) => {
763                            error!(
764                                "Unable to load env file: {} Error: {:#?}",
765                                &file_path_str, error
766                            );
767                            false
768                        }
769                        _ => {
770                            debug!("Loaded env file: {}", &file_path_str);
771                            true
772                        }
773                    }
774                }
775                None => false,
776            }
777        }
778        None => false,
779    }
780}
781
782fn current_dir_or(fallback: &PathBuf) -> PathBuf {
783    match env::current_dir() {
784        Ok(value) => value.clone(),
785        _ => fallback.clone(),
786    }
787}
788
789pub(crate) fn find_git_root(directory: &PathBuf) -> Option<String> {
790    let from_dir = if directory.to_str().unwrap_or(".") == "." {
791        current_dir_or(directory)
792    } else {
793        directory.to_path_buf()
794    };
795    debug!("Looking for git root from directory: {:?}", &from_dir);
796    let file_path = Path::new(&from_dir).join(".git");
797
798    if file_path.exists() {
799        match from_dir.to_str() {
800            Some(directory_string) => Some(directory_string.to_string()),
801            _ => None,
802        }
803    } else {
804        match from_dir.parent() {
805            Some(parent_directory) => {
806                let parent_directory_path = parent_directory.to_path_buf();
807                find_git_root(&parent_directory_path)
808            }
809            None => None,
810        }
811    }
812}
813
814pub(crate) fn get_project_root_for_path(directory: &PathBuf) -> Option<String> {
815    let from_dir = if directory.to_str().unwrap_or(".") == "." {
816        current_dir_or(directory)
817    } else {
818        directory.to_path_buf()
819    };
820    debug!("Looking for project root from directory: {:?}", &from_dir);
821    let file_path = Path::new(&from_dir).join("Cargo.toml");
822
823    if file_path.exists() {
824        match from_dir.to_str() {
825            Some(directory_string) => Some(directory_string.to_string()),
826            _ => None,
827        }
828    } else {
829        match from_dir.parent() {
830            Some(parent_directory) => {
831                let parent_directory_path = parent_directory.to_path_buf();
832                get_project_root_for_path(&parent_directory_path)
833            }
834            None => None,
835        }
836    }
837}
838
839pub(crate) fn get_project_root() -> Option<String> {
840    let directory = PathBuf::from(".");
841    get_project_root_for_path(&directory)
842}
843
844fn expand_env_for_script_runner_arguments(task: &mut Task) {
845    let updated_args = match task.script_runner_args {
846        Some(ref args) => {
847            let mut expanded_args = vec![];
848
849            for index in 0..args.len() {
850                expanded_args.push(expand_value(&args[index]));
851            }
852
853            Some(expanded_args)
854        }
855        None => None,
856    };
857
858    task.script_runner_args = updated_args;
859}
860
861fn expand_env_for_arguments(task: &mut Task) {
862    // update args by replacing any env vars
863    let updated_args = match task.args {
864        Some(ref args) => {
865            let mut expanded_args = vec![];
866
867            let task_args = match envmnt::get_list("CARGO_MAKE_TASK_ARGS") {
868                Some(list) => list,
869                None => vec![],
870            };
871
872            for index in 0..args.len() {
873                if args[index].contains("${@}") {
874                    if task_args.len() > 0 {
875                        if args[index] == "${@}" {
876                            for arg_index in 0..task_args.len() {
877                                expanded_args.push(task_args[arg_index].clone());
878                            }
879                        } else {
880                            for arg_index in 0..task_args.len() {
881                                let value_string =
882                                    str::replace(&args[index], "${@}", &task_args[arg_index]);
883                                expanded_args.push(value_string);
884                            }
885                        }
886                    }
887                } else {
888                    expanded_args.push(args[index].clone());
889                }
890            }
891
892            for index in 0..expanded_args.len() {
893                expanded_args[index] = expand_value(&expanded_args[index]);
894            }
895
896            Some(expanded_args)
897        }
898        None => None,
899    };
900
901    task.args = updated_args;
902}
903
904pub(crate) fn expand_env(step: &Step) -> Step {
905    //clone data before modify
906    let mut config = step.config.clone();
907
908    //update command by replacing any env vars
909    match config.command {
910        Some(value) => {
911            config.command = Some(expand_value(&value));
912        }
913        None => {}
914    };
915
916    //update args by replacing any env vars
917    expand_env_for_arguments(&mut config);
918    expand_env_for_script_runner_arguments(&mut config);
919
920    Step {
921        name: step.name.clone(),
922        config,
923    }
924}
925
926pub(crate) fn expand_condition_script_runner_arguments(step: &Step) -> Step {
927    let mut modified_step = step.clone();
928
929    modified_step.config.condition_script_runner_args = step
930        .config
931        .condition_script_runner_args
932        .as_ref()
933        .map(|args| args.iter().map(|arg| expand_value(arg)).collect());
934    modified_step
935}