Skip to main content

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    run_task_by_name(context, self.resolve_name())
24  }
25
26  pub fn resolve_name(&self) -> &str {
27    match self {
28      TaskDependency::String(name) => name,
29      TaskDependency::TaskDependency(args) => &args.name,
30    }
31  }
32}
33
34impl TaskDependencyArgs {
35  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
36    run_task_by_name(context, &self.name)
37  }
38}
39
40pub fn run_task_by_name(context: &TaskContext, task_name: &str) -> anyhow::Result<()> {
41  assert!(!task_name.is_empty());
42
43  if context.is_task_completed(task_name)? {
44    log::trace!("Skipping completed task: {}", task_name);
45    return Ok(());
46  }
47
48  if context.is_task_active(task_name)? {
49    anyhow::bail!("Circular dependency detected - {}", task_name);
50  }
51
52  let task = context
53    .task_root
54    .tasks
55    .get(task_name)
56    .ok_or_else(|| anyhow::anyhow!("Task not found - {}", task_name))?;
57
58  log::trace!("Task: {:?}", task);
59
60  context.mark_task_active(task_name)?;
61
62  let result = {
63    let mut child_context = TaskContext::from_context(context);
64    child_context.set_current_task_name(task_name);
65    child_context.emit_event(&serde_json::json!({
66      "event": "task_started",
67      "task": task_name,
68    }))?;
69    task.run(&mut child_context)
70  };
71
72  context.unmark_task_active(task_name)?;
73
74  if result.is_ok() {
75    context.mark_task_complete(task_name)?;
76    context.emit_event(&serde_json::json!({
77      "event": "task_finished",
78      "task": task_name,
79      "success": true,
80    }))?;
81  } else {
82    context.emit_event(&serde_json::json!({
83      "event": "task_finished",
84      "task": task_name,
85      "success": false,
86    }))?;
87  }
88
89  result
90}
91
92#[cfg(test)]
93mod test {
94  use hashbrown::HashMap;
95  use std::sync::Arc;
96
97  use crate::schema::{
98    Task,
99    TaskRoot,
100  };
101
102  use super::*;
103
104  #[test]
105  fn test_task_dependency_1() -> anyhow::Result<()> {
106    let yaml = "
107      name: task1
108    ";
109
110    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
111    if let TaskDependency::TaskDependency(args) = task_dependency {
112      assert_eq!(args.name, "task1");
113    } else {
114      panic!("Expected TaskDependency::TaskDependency");
115    }
116
117    Ok(())
118  }
119
120  #[test]
121  fn test_task_dependency_2() -> anyhow::Result<()> {
122    let yaml = "\"task1\"";
123
124    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
125    if let TaskDependency::String(name) = task_dependency {
126      assert_eq!(name, "task1");
127    } else {
128      panic!("Expected TaskDependency::TaskString");
129    }
130
131    Ok(())
132  }
133
134  #[test]
135  fn test_task_dependency_3() -> anyhow::Result<()> {
136    let yaml = "\"\"";
137
138    let task_dependency = serde_yaml::from_str::<TaskDependency>(yaml)?;
139    if let TaskDependency::String(name) = task_dependency {
140      assert_eq!(name, "");
141    } else {
142      panic!("Expected TaskDependency::TaskString");
143    }
144
145    Ok(())
146  }
147
148  #[test]
149  fn test_task_dependency_4() -> anyhow::Result<()> {
150    let yaml = "
151      name:
152    ";
153
154    let task_dependency = serde_yaml::from_str::<TaskDependencyArgs>(yaml)?;
155    assert_eq!(task_dependency.name, "");
156
157    Ok(())
158  }
159
160  #[test]
161  fn test_task_dependency_5() -> anyhow::Result<()> {
162    let yaml = "
163      name: task_a
164    ";
165
166    let task_yaml = "
167      commands:
168        - command: echo 1
169          verbose: false
170    ";
171
172    let task = serde_yaml::from_str::<Task>(task_yaml)?;
173    let mut hm = HashMap::new();
174    hm.insert("task_a".into(), task);
175
176    let root = Arc::new(TaskRoot::from_hashmap(hm));
177    let task_dependency = serde_yaml::from_str::<TaskDependencyArgs>(yaml)?;
178    let result = task_dependency.execute(&TaskContext::empty_with_root(root));
179    assert!(result.is_ok());
180
181    Ok(())
182  }
183
184  #[test]
185  fn test_task_dependency_6_shared_dependency_is_not_a_cycle() -> anyhow::Result<()> {
186    let root_yaml = "
187      tasks:
188        root:
189          commands:
190            - command: echo root
191              verbose: false
192          depends_on:
193            - left
194            - right
195        left:
196          commands:
197            - command: echo left
198              verbose: false
199          depends_on:
200            - shared
201        right:
202          commands:
203            - command: echo right
204              verbose: false
205          depends_on:
206            - shared
207        shared:
208          commands:
209            - command: echo shared
210              verbose: false
211    ";
212
213    let root = Arc::new(serde_yaml::from_str::<TaskRoot>(root_yaml)?);
214    let context = TaskContext::empty_with_root(root);
215    let result = run_task_by_name(&context, "root");
216    assert!(result.is_ok());
217    assert!(context.is_task_completed("shared")?);
218
219    Ok(())
220  }
221}