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