1#[cfg(test)]
10#[path = "mod_test.rs"]
11mod mod_test;
12
13mod cargo_alias;
14pub(crate) mod descriptor_deserializer;
15mod env;
16mod makefiles;
17
18use crate::descriptor::env::{merge_env, merge_env_files, merge_env_scripts};
19use crate::environment;
20use crate::error::CargoMakeError;
21use crate::plugin::descriptor::merge_plugins_config;
22use crate::types::{
23 Config, ConfigSection, EnvFile, EnvFileInfo, EnvValue, Extend, ExternalConfig, ModifyConfig,
24 Task,
25};
26use crate::{io, scriptengine, version};
27use fsio::path::as_path::AsPath;
28use fsio::path::from_path::FromPath;
29use indexmap::IndexMap;
30use std::path::{Path, PathBuf};
31
32#[derive(Debug)]
33enum RelativeTo {
34 Makefile,
35 GitRoot,
36 CrateRoot,
37 WorkspaceRoot,
38}
39
40fn merge_tasks(
41 base: &mut IndexMap<String, Task>,
42 extended: &mut IndexMap<String, Task>,
43 merge_task_env: bool,
44) -> IndexMap<String, Task> {
45 let mut merged = IndexMap::<String, Task>::new();
46
47 for (key, value) in base.iter() {
48 let key_str = key.to_string();
49 merged.insert(key_str, value.clone());
50 }
51
52 for (key, value) in extended.iter() {
53 let key_str = key.to_string();
54 let mut task = value.clone();
55
56 task = match base.get(key) {
57 Some(ref value) => {
58 let mut merged_task = Task::new();
59
60 merged_task.extend(value);
61 merged_task.extend(&task);
62
63 if merge_task_env && value.env.is_some() && task.env.is_some() {
64 let extended_env = task.env.clone().unwrap();
65 let clear = task.clear.clone().unwrap_or(false);
66 if extended_env.len() == 2
67 && extended_env.contains_key("CARGO_MAKE_CURRENT_TASK_INITIAL_MAKEFILE")
68 && extended_env
69 .contains_key("CARGO_MAKE_CURRENT_TASK_INITIAL_MAKEFILE_DIRECTORY")
70 && !clear
71 {
72 let base_env = value.env.clone().unwrap();
73 merged_task.env = Some(base_env);
74 }
75 }
76
77 merged_task
78 }
79 _ => task,
80 };
81
82 merged.insert(key_str, task);
83 }
84
85 merged
86}
87
88fn add_file_location_info(
89 mut external_config: ExternalConfig,
90 file_path_string: &str,
91) -> ExternalConfig {
92 let file_path = file_path_string.as_path();
93 let base_directory = match file_path.parent() {
94 Some(directory) => FromPath::from_path(directory),
95 None => "".to_string(),
96 };
97
98 match external_config.env_files {
99 Some(env_files) => {
100 let mut modified_env_files = vec![];
101
102 for env_file in env_files {
103 match env_file {
104 EnvFile::Path(path) => {
105 let mut info = EnvFileInfo::new(path);
106 info.base_path = Some(base_directory.clone());
107
108 modified_env_files.push(EnvFile::Info(info));
109 }
110 EnvFile::Info(mut info) => {
111 if info.base_path.is_none() {
112 info.base_path = Some(base_directory.clone());
113 }
114
115 modified_env_files.push(EnvFile::Info(info));
116 }
117 }
118 }
119
120 external_config.env_files = Some(modified_env_files);
121 }
122 None => (),
123 };
124
125 let mut tasks_map = IndexMap::new();
126 if let Some(tasks) = external_config.tasks.clone() {
127 for (task_name, task) in tasks {
128 let mut env = match task.env.clone() {
129 Some(env) => env,
130 None => IndexMap::new(),
131 };
132
133 env.insert(
134 "CARGO_MAKE_CURRENT_TASK_INITIAL_MAKEFILE".to_string(),
135 EnvValue::Value(file_path_string.to_string()),
136 );
137 env.insert(
138 "CARGO_MAKE_CURRENT_TASK_INITIAL_MAKEFILE_DIRECTORY".to_string(),
139 EnvValue::Value(base_directory.to_string()),
140 );
141
142 let mut updated_task = task.clone();
143 updated_task.env = Some(env);
144 tasks_map.insert(task_name, updated_task);
145 }
146
147 external_config.tasks = Some(tasks_map);
148 };
149
150 external_config
151}
152
153fn run_load_script(external_config: &ExternalConfig) -> Result<bool, CargoMakeError> {
154 match external_config.config {
155 Some(ref config) => {
156 let load_script = config.get_load_script();
157
158 match load_script {
159 Some(ref script) => {
160 debug!("Load script found.");
161
162 scriptengine::invoke_script_pre_flow(script, None, None, None, true, &vec![])?;
163
164 Ok(true)
165 }
166 None => {
167 debug!("No load script defined.");
168 Ok(false)
169 }
170 }
171 }
172 None => {
173 debug!("No load script defined.");
174 Ok(false)
175 }
176 }
177}
178
179fn merge_external_configs(
180 config: ExternalConfig,
181 parent_config: ExternalConfig,
182) -> Result<ExternalConfig, CargoMakeError> {
183 let mut parent_env_files = match parent_config.env_files {
185 Some(env_files) => env_files,
186 None => vec![],
187 };
188 let mut extended_env_files = match config.env_files {
189 Some(env_files) => env_files,
190 None => vec![],
191 };
192 let all_env_files = merge_env_files(&mut parent_env_files, &mut extended_env_files);
193
194 let mut parent_env = match parent_config.env {
196 Some(env) => env,
197 None => IndexMap::new(),
198 };
199 let mut extended_env = match config.env {
200 Some(env) => env,
201 None => IndexMap::new(),
202 };
203 let all_env = merge_env(&mut parent_env, &mut extended_env)?;
204
205 let mut parent_env_scripts = match parent_config.env_scripts {
207 Some(env_scripts) => env_scripts,
208 None => vec![],
209 };
210 let mut extended_env_scripts = match config.env_scripts {
211 Some(env_scripts) => env_scripts,
212 None => vec![],
213 };
214 let all_env_scripts = merge_env_scripts(&mut parent_env_scripts, &mut extended_env_scripts);
215
216 let mut parent_tasks = match parent_config.tasks {
218 Some(tasks) => tasks,
219 None => IndexMap::new(),
220 };
221 let mut extended_tasks = match config.tasks {
222 Some(tasks) => tasks,
223 None => IndexMap::new(),
224 };
225 let all_tasks = merge_tasks(&mut parent_tasks, &mut extended_tasks, false);
226
227 let mut config_section = ConfigSection::new();
228 if parent_config.config.is_some() {
229 let mut config_section_data = parent_config.config.unwrap();
230 debug!("Adding parent config section: {:#?}", &config_section_data);
231 config_section.extend(&mut config_section_data);
232 }
233 if config.config.is_some() {
234 let mut config_section_data = config.config.unwrap();
235 debug!("Adding config section: {:#?}", &config_section_data);
236 config_section.extend(&mut config_section_data);
237 }
238
239 let plugins = merge_plugins_config(parent_config.plugins, config.plugins);
240
241 let config = ExternalConfig {
242 extend: None,
243 config: Some(config_section),
244 env_files: Some(all_env_files),
245 env: Some(all_env),
246 env_scripts: Some(all_env_scripts),
247 tasks: Some(all_tasks),
248 plugins,
249 };
250
251 Ok(config)
252}
253
254fn load_descriptor_extended_makefiles(
255 parent_path: &str,
256 extend_struct: &Extend,
257) -> Result<ExternalConfig, CargoMakeError> {
258 match extend_struct {
259 Extend::Path(base_file) => {
260 load_external_descriptor(parent_path, &base_file, true, false, RelativeTo::Makefile)
261 }
262 Extend::Options(extend_options) => {
263 let force = !extend_options.optional.unwrap_or(false);
264 let relative_to_str = extend_options
265 .relative
266 .clone()
267 .unwrap_or("makefile".to_string());
268 let relative_to = match relative_to_str.as_str() {
269 "git" => RelativeTo::GitRoot,
270 "crate" => RelativeTo::CrateRoot,
271 "workspace" => RelativeTo::WorkspaceRoot,
272 "makefile" => RelativeTo::Makefile,
273 _ => {
274 warn!(
275 "Unknown relative-to value: {}, defaulting to makefile",
276 &relative_to_str
277 );
278 RelativeTo::Makefile
279 }
280 };
281 load_external_descriptor(parent_path, &extend_options.path, force, false, relative_to)
282 }
283 Extend::List(extend_list) => {
284 let mut ordered_list_config = ExternalConfig::new();
285
286 for entry in extend_list.iter() {
287 let extend_options = entry.clone();
288 let entry_config = load_descriptor_extended_makefiles(
289 parent_path,
290 &Extend::Options(extend_options),
291 )?;
292
293 ordered_list_config = merge_external_configs(entry_config, ordered_list_config)?;
295 }
296
297 Ok(ordered_list_config)
298 }
299 }
300}
301
302fn check_makefile_min_version(external_descriptor: &str) -> Result<(), CargoMakeError> {
305 let value: toml::Value = match toml::from_str(&external_descriptor) {
306 Ok(value) => value,
307 Err(_) => return Ok(()),
310 };
311
312 let min_version = value
313 .get("config")
314 .and_then(|config| config.get("min_version"))
315 .and_then(|min_ver| min_ver.as_str());
316
317 if let Some(ref min_version) = min_version {
318 if version::is_newer_found(&min_version) {
319 return Err(CargoMakeError::VersionTooOld(min_version.to_string()));
320 }
321 }
322
323 Ok(())
324}
325
326fn load_external_descriptor(
327 base_path: &str,
328 file_name: &str,
329 force: bool,
330 set_env: bool,
331 relative_to: RelativeTo,
332) -> Result<ExternalConfig, CargoMakeError> {
333 debug!(
334 "Loading tasks from file: {} base directory: {}, relative to: {:#?}",
335 &file_name, &base_path, &relative_to,
336 );
337
338 let descriptor_dir = match relative_to {
339 RelativeTo::Makefile => base_path.to_string(),
340 RelativeTo::GitRoot => {
341 let git_root = environment::find_git_root(&PathBuf::from(base_path));
342 debug!("git root: {:#?}", &git_root);
343 match git_root {
344 Some(git_root_dir) => git_root_dir.clone(),
345 None => base_path.to_string(),
346 }
347 }
348 RelativeTo::CrateRoot => {
349 let project_root = environment::get_project_root_for_path(&PathBuf::from(base_path));
350 debug!("project root: {:#?}", &project_root);
351 match project_root {
352 Some(crate_dir) => crate_dir.clone(),
353 None => base_path.to_string(),
354 }
355 }
356 RelativeTo::WorkspaceRoot => {
357 let base_path_buf = PathBuf::from(base_path);
358 let project_root = environment::get_project_root_for_path(&base_path_buf);
359 debug!("project root: {:#?}", &project_root);
360 match project_root {
361 Some(crate_dir) => {
362 let crate_parent_path = PathBuf::from(&crate_dir).join("..");
363 let workspace_root = environment::get_project_root_for_path(&crate_parent_path);
364 debug!("workspace root: {:#?}", &workspace_root);
365 match workspace_root {
366 Some(workspace_dir) => workspace_dir.clone(),
367 None => crate_dir.clone(),
368 }
369 }
370 None => base_path.to_string(),
371 }
372 }
373 };
374 let file_path = Path::new(&descriptor_dir).join(file_name);
375
376 if file_path.exists() && file_path.is_file() {
377 let file_path_string: String = FromPath::from_path(&file_path);
378 let absolute_file_path = io::canonicalize_to_string(&file_path_string);
379
380 if set_env {
381 envmnt::set("CARGO_MAKE_MAKEFILE_PATH", &absolute_file_path);
382 }
383
384 let external_descriptor = io::read_text_file(&file_path)?;
385
386 check_makefile_min_version(&external_descriptor)?;
387
388 let mut file_config =
389 descriptor_deserializer::load_external_config(&external_descriptor, &file_path_string)?;
390 debug!("Loaded external config: {:#?}", &file_config);
391
392 file_config = add_file_location_info(file_config, &absolute_file_path);
393
394 run_load_script(&file_config)?;
395
396 match file_config.extend {
397 Some(ref extend_struct) => {
398 let parent_path_buf = Path::new(&file_path_string).join("..");
399 let parent_path = file_path
400 .parent()
401 .unwrap_or(&parent_path_buf)
402 .to_str()
403 .unwrap_or(".");
404 debug!("External config parent path: {}", &parent_path);
405
406 let base_file_config =
407 load_descriptor_extended_makefiles(&parent_path, extend_struct)?;
408
409 merge_external_configs(file_config.clone(), base_file_config)
410 }
411 None => Ok(file_config),
412 }
413 } else if force {
414 error!("Descriptor file: {:#?} not found.", &file_path);
415 Err(CargoMakeError::NotFound(format!(
416 "Descriptor file: {:#?} not found.",
417 &file_path
418 )))
419 } else {
420 debug!("External file not found or is not a file, skipping.");
421
422 Ok(ExternalConfig::new())
423 }
424}
425
426pub(crate) fn load_internal_descriptors(
427 stable: bool,
428 experimental: bool,
429 modify_config: Option<ModifyConfig>,
430) -> Result<Config, CargoMakeError> {
431 debug!("Loading base tasks.");
432
433 let base_descriptor = if stable {
434 makefiles::STABLE
435 } else {
436 makefiles::BASE
437 };
438
439 let mut base_config = descriptor_deserializer::load_config(&base_descriptor, false)?;
440 debug!("Loaded base config: {:#?}", &base_config);
441
442 if experimental {
443 debug!("Loading experimental tasks.");
444 let experimental_descriptor = makefiles::BETA;
445
446 let experimental_config =
447 descriptor_deserializer::load_config(&experimental_descriptor, false)?;
448 debug!("Loaded experimental config: {:#?}", &experimental_config);
449
450 let mut base_tasks = base_config.tasks;
451 let mut experimental_tasks = experimental_config.tasks;
452 let all_tasks = merge_tasks(&mut base_tasks, &mut experimental_tasks, false);
453
454 base_config.tasks = all_tasks;
455 }
456
457 envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE", "");
459 envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE_PREFIX", "");
460
461 match modify_config {
462 Some(props) => {
463 base_config.apply(&props);
464
465 match props.namespace {
466 Some(ref namespace) => {
467 let prefix = props.get_namespace_prefix();
468
469 envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE", &namespace);
470 envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE_PREFIX", &prefix);
471 }
472 None => (),
473 };
474 }
475 None => (),
476 };
477
478 Ok(base_config)
479}
480
481fn merge_base_config_and_external_config(
482 base_config: Config,
483 external_config: ExternalConfig,
484 env_map: Option<Vec<String>>,
485 late_merge: bool,
486) -> Result<Config, CargoMakeError> {
487 let mut external_tasks = match external_config.tasks {
488 Some(tasks) => tasks,
489 None => IndexMap::new(),
490 };
491 let mut base_tasks = base_config.tasks;
492
493 let env_files = match external_config.env_files {
494 Some(env_files) => env_files,
495 None => vec![],
496 };
497
498 let env_scripts = match external_config.env_scripts {
499 Some(env_scripts) => env_scripts,
500 None => vec![],
501 };
502
503 let mut external_env = match external_config.env {
504 Some(env) => env,
505 None => IndexMap::new(),
506 };
507 let mut base_env = base_config.env;
508
509 let mut all_env = merge_env(&mut base_env, &mut external_env)?;
511 all_env = match env_map {
512 Some(values) => {
513 let mut cli_env = IndexMap::new();
514
515 for env_pair in &values {
516 debug!("Checking env pair: {}", &env_pair);
517 let env_parts: Option<(&str, &str)> = split_once(env_pair, '=');
518
519 if let Some((key, value)) = env_parts {
520 cli_env.insert(key.to_string(), EnvValue::Value(value.to_string()));
521 }
522 }
523
524 for (key, value) in all_env.iter() {
527 let key_str = key.to_string();
528
529 if !cli_env.contains_key(&key_str) {
530 cli_env.insert(key_str, value.clone());
531 }
532 }
533 cli_env
534 }
535 None => all_env,
536 };
537
538 let all_tasks = merge_tasks(&mut base_tasks, &mut external_tasks, late_merge);
539
540 let mut config_section = base_config.config.clone();
541 config_section.extend(&mut external_config.config.unwrap_or(ConfigSection::new()));
542
543 let plugins = merge_plugins_config(base_config.plugins, external_config.plugins);
544
545 let config = Config {
546 config: config_section,
547 env_files,
548 env: all_env,
549 env_scripts,
550 tasks: all_tasks,
551 plugins,
552 };
553
554 Ok(config)
555}
556
557fn split_once(value: &str, delimiter: char) -> Option<(&str, &str)> {
558 let mut parts = value.splitn(2, delimiter);
559 let part1 = parts.next()?;
560 let part2 = parts.next()?;
561 Some((part1, part2))
562}
563
564fn load_descriptors(
572 file_name: &str,
573 force: bool,
574 env_map: Option<Vec<String>>,
575 stable: bool,
576 experimental: bool,
577 modify_core_tasks: Option<ModifyConfig>,
578) -> Result<Config, CargoMakeError> {
579 let default_config = load_internal_descriptors(stable, experimental, modify_core_tasks)?;
580
581 let mut external_config =
582 load_external_descriptor(".", file_name, force, true, RelativeTo::Makefile)?;
583
584 external_config = match std::env::var("CARGO_MAKE_WORKSPACE_MAKEFILE") {
585 Ok(workspace_makefile) => {
586 let mut pathbuf = PathBuf::from(workspace_makefile);
587 match pathbuf.clone().file_name() {
588 Some(workspace_file_name) => match workspace_file_name.to_str() {
589 Some(workspace_file_name_str) => {
590 pathbuf.pop();
591
592 match pathbuf.to_str() {
593 Some(directory) => {
594 let workspace_config = load_external_descriptor(
595 directory,
596 workspace_file_name_str,
597 false,
598 false,
599 RelativeTo::Makefile,
600 )?;
601 merge_external_configs(external_config, workspace_config)?
602 }
603 _ => external_config,
604 }
605 }
606 _ => external_config,
607 },
608 _ => external_config,
609 }
610 }
611 _ => external_config,
612 };
613
614 let config =
615 merge_base_config_and_external_config(default_config, external_config, env_map, false)?;
616
617 debug!("Loaded merged config: {:#?}", &config);
618
619 Ok(config)
620}
621
622fn load_cargo_aliases(config: &mut Config) -> Result<(), CargoMakeError> {
623 if let Some(load_cargo_aliases) = config.config.load_cargo_aliases {
624 if load_cargo_aliases {
625 let alias_tasks = cargo_alias::load()?;
626 for (name, task) in alias_tasks {
627 match config.tasks.get(&name) {
628 None => {
629 debug!("Creating cargo alias task: {}", &name);
630 config.tasks.insert(name, task);
631 }
632 Some(_) => debug!("Ignoring cargo alias task: {}", &name),
633 }
634 }
635 }
636 }
637 Ok(())
638}
639
640pub fn load(
648 file_name: &str,
649 force: bool,
650 env_map: Option<Vec<String>>,
651 experimental: bool,
652) -> Result<Config, CargoMakeError> {
653 let mut config = load_descriptors(&file_name, force, env_map.clone(), false, false, None)?;
655
656 if !config.config.skip_core_tasks.unwrap_or(false) {
658 let modify_core_tasks = config.config.modify_core_tasks.clone();
659
660 match modify_core_tasks {
661 Some(modify_config) => {
662 if modify_config.is_modifications_defined() {
663 config = load_descriptors(
665 &file_name,
666 force,
667 env_map.clone(),
668 true,
669 experimental,
670 Some(modify_config),
671 )?;
672 }
673 }
674 None => {
675 let core_config = load_internal_descriptors(true, experimental, modify_core_tasks)?;
676 let external_config = ExternalConfig {
677 extend: None,
678 config: Some(config.config),
679 env_files: Some(config.env_files),
680 env: Some(config.env),
681 env_scripts: Some(config.env_scripts),
682 tasks: Some(config.tasks),
683 plugins: config.plugins,
684 };
685
686 config = merge_base_config_and_external_config(
687 core_config,
688 external_config,
689 env_map.clone(),
690 true,
691 )?;
692 }
693 };
694 }
695
696 load_cargo_aliases(&mut config)?;
697
698 if let Some(unstable_features) = &config.config.unstable_features {
699 for feature in unstable_features {
700 config
701 .env
702 .insert(feature.to_env_name(), EnvValue::Boolean(true));
703 }
704 }
705
706 Ok(config)
707}