mk_lib/schema/
task_dependency.rs

1use serde::Deserialize;
2
3use super::TaskContext;
4
5/// This struct represents a task dependency. A task can depend on other tasks.
6/// If a task depends on another task, the dependent task must be executed before
7/// the dependent task.
8#[derive(Debug, Deserialize)]
9pub struct TaskDependencyArgs {
10  /// The name of the task to depend on
11  pub name: String,
12}
13
14#[derive(Debug, Deserialize)]
15#[serde(untagged)]
16pub enum TaskDependency {
17  String(String),
18  TaskDependency(Box<TaskDependencyArgs>),
19}
20
21impl TaskDependency {
22  pub fn run(&self, context: &TaskContext) -> anyhow::Result<()> {
23    match self {
24      TaskDependency::String(name) => self.execute(context, name),
25      TaskDependency::TaskDependency(args) => args.execute(context),
26    }
27  }
28
29  fn execute(&self, context: &TaskContext, task_name: &str) -> anyhow::Result<()> {
30    assert!(!task_name.is_empty());
31
32    let task = context
33      .task_root
34      .tasks
35      .get(task_name)
36      .ok_or_else(|| anyhow::anyhow!("Task not found"))?;
37
38    log::trace!("Task: {:?}", task);
39
40    {
41      let mut stack = context
42        .execution_stack
43        .lock()
44        .map_err(|e| anyhow::anyhow!("Failed to lock execution stack - {}", e))?;
45
46      if stack.contains(task_name) {
47        anyhow::bail!("Circular dependency detected - {}", task_name);
48      }
49
50      stack.insert(task_name.into());
51    }
52
53    let mut context = TaskContext::from_context(context);
54    task.run(&mut context)?;
55
56    Ok(())
57  }
58}
59
60impl TaskDependencyArgs {
61  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
62    assert!(!self.name.is_empty());
63
64    let task_name: &str = &self.name;
65    let task = context
66      .task_root
67      .tasks
68      .get(task_name)
69      .ok_or_else(|| anyhow::anyhow!("Task not found"))?;
70
71    log::trace!("Task: {:?}", task);
72
73    {
74      let mut stack = context
75        .execution_stack
76        .lock()
77        .map_err(|e| anyhow::anyhow!("Failed to lock execution stack - {}", e))?;
78
79      if stack.contains(task_name) {
80        anyhow::bail!("Circular dependency detected - {}", task_name);
81      }
82
83      stack.insert(task_name.into());
84    }
85
86    let mut context = TaskContext::from_context(context);
87    task.run(&mut context)?;
88
89    Ok(())
90  }
91}
92
93#[cfg(test)]
94mod test {
95  use hashbrown::HashMap;
96  use std::sync::Arc;
97
98  use crate::schema::{
99    Task,
100    TaskRoot,
101  };
102
103  use super::*;
104
105  #[test]
106  fn test_task_dependency_1() -> anyhow::Result<()> {
107    let yaml = "
108      name: task1
109    ";
110
111    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
112    if let TaskDependency::TaskDependency(args) = task_dependency {
113      assert_eq!(args.name, "task1");
114    } else {
115      panic!("Expected TaskDependency::TaskDependency");
116    }
117
118    Ok(())
119  }
120
121  #[test]
122  fn test_task_dependency_2() -> anyhow::Result<()> {
123    let yaml = "\"task1\"";
124
125    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
126    if let TaskDependency::String(name) = task_dependency {
127      assert_eq!(name, "task1");
128    } else {
129      panic!("Expected TaskDependency::TaskString");
130    }
131
132    Ok(())
133  }
134
135  #[test]
136  fn test_task_dependency_3() -> anyhow::Result<()> {
137    let yaml = "\"\"";
138
139    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
140    if let TaskDependency::String(name) = task_dependency {
141      assert_eq!(name, "");
142    } else {
143      panic!("Expected TaskDependency::TaskString");
144    }
145
146    Ok(())
147  }
148
149  #[test]
150  fn test_task_dependency_4() -> anyhow::Result<()> {
151    let yaml = "
152      name:
153    ";
154
155    let task_dependency = serde_yaml::from_str::<TaskDependencyArgs>(yaml)?;
156    assert_eq!(task_dependency.name, "");
157
158    Ok(())
159  }
160
161  #[test]
162  fn test_task_dependency_5() -> anyhow::Result<()> {
163    let yaml = "
164      name: task_a
165    ";
166
167    let task_yaml = "
168      commands:
169        - command: echo 1
170          verbose: false
171    ";
172
173    let task = serde_yaml::from_str::<Task>(task_yaml)?;
174    let mut hm = HashMap::new();
175    hm.insert("task_a".into(), task);
176
177    let root = Arc::new(TaskRoot::from_hashmap(hm));
178    let task_dependency = serde_yaml::from_str::<TaskDependencyArgs>(yaml)?;
179    let result = task_dependency.execute(&TaskContext::empty_with_root(root));
180    assert!(result.is_ok());
181
182    Ok(())
183  }
184}