use cuenv_core::tasks::{TaskGraph, TaskGraphNode};
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct DagExport {
pub tasks: Vec<TaskNode>,
pub execution_order: Vec<String>,
pub parallel_groups: Vec<Vec<String>>,
}
#[derive(Debug, Serialize)]
pub struct TaskNode {
pub name: String,
pub dependencies: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl DagExport {
pub fn from_task_graph(graph: &TaskGraph) -> Result<Self, cuenv_core::Error> {
let sorted_nodes = graph.topological_sort().map_err(|e| {
cuenv_core::Error::configuration(format!("Failed to sort task graph: {e}"))
})?;
let tasks: Vec<TaskNode> = sorted_nodes
.iter()
.map(|node| TaskNode {
name: node.name.clone(),
dependencies: node
.task
.depends_on
.iter()
.map(|d| d.task_name().to_string())
.collect(),
command: Some(node.task.command.clone()),
description: node.task.description.clone(),
})
.collect();
let execution_order: Vec<String> = sorted_nodes.iter().map(|n| n.name.clone()).collect();
let parallel_groups = build_parallel_groups(&sorted_nodes);
Ok(Self {
tasks,
execution_order,
parallel_groups,
})
}
}
fn build_parallel_groups(sorted_nodes: &[TaskGraphNode]) -> Vec<Vec<String>> {
use std::collections::HashMap;
if sorted_nodes.is_empty() {
return Vec::new();
}
let mut levels: HashMap<String, usize> = HashMap::new();
for node in sorted_nodes {
let max_dep_level = node
.task
.depends_on
.iter()
.filter_map(|dep| levels.get(dep.task_name()).copied())
.max()
.unwrap_or(0);
let level = if node.task.depends_on.is_empty() {
0
} else {
max_dep_level + 1
};
levels.insert(node.name.clone(), level);
}
let max_level = levels.values().copied().max().unwrap_or(0);
let mut groups: Vec<Vec<String>> = vec![Vec::new(); max_level + 1];
for node in sorted_nodes {
if let Some(&level) = levels.get(&node.name) {
groups[level].push(node.name.clone());
}
}
groups.into_iter().filter(|g| !g.is_empty()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dag_export_serialization() {
let export = DagExport {
tasks: vec![
TaskNode {
name: "build".to_string(),
dependencies: vec![],
command: Some("cargo build".to_string()),
description: Some("Build the project".to_string()),
},
TaskNode {
name: "test".to_string(),
dependencies: vec!["build".to_string()],
command: Some("cargo test".to_string()),
description: None,
},
],
execution_order: vec!["build".to_string(), "test".to_string()],
parallel_groups: vec![vec!["build".to_string()], vec!["test".to_string()]],
};
let json = serde_json::to_string_pretty(&export).unwrap();
assert!(json.contains("\"name\": \"build\""));
assert!(json.contains("\"execution_order\""));
assert!(json.contains("\"parallel_groups\""));
}
}