mk_lib/schema/
task_dependency.rs1use serde::Deserialize;
2
3use super::TaskContext;
4
5#[derive(Debug, Deserialize)]
9pub struct TaskDependencyArgs {
10 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}