1use cadi_core::{CadiError, CadiResult, Manifest};
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug)]
8pub struct BuildPlan {
9 pub steps: Vec<BuildStep>,
11 pub estimated_time_ms: u64,
13}
14
15#[derive(Debug)]
17pub struct BuildStep {
18 pub name: String,
20 pub chunk_id: Option<String>,
22 pub transform: super::TransformType,
24 pub inputs: Vec<super::TransformInput>,
26 pub depends_on: Vec<String>,
28}
29
30impl BuildPlan {
31 pub fn from_manifest(manifest: &Manifest, target: &str) -> CadiResult<Self> {
33 let target_config = manifest.find_target(target)
34 .ok_or_else(|| CadiError::BuildFailed(format!("Target '{}' not found", target)))?;
35
36 let mut steps = Vec::new();
37 let mut visited = HashSet::new();
38
39 let deps = build_dependency_graph(&manifest.build_graph);
41
42 let nodes_needed: HashSet<_> = if target_config.nodes.is_empty() {
44 manifest.build_graph.nodes.iter().map(|n| n.id.as_str()).collect()
45 } else {
46 target_config.nodes.iter().map(|n| n.id.as_str()).collect()
47 };
48
49 for node_id in &nodes_needed {
51 collect_steps(
52 &manifest.build_graph,
53 node_id,
54 &target_config.platform,
55 &deps,
56 &mut visited,
57 &mut steps,
58 )?;
59 }
60
61 let estimated_time_ms = steps.len() as u64 * 1000;
63
64 Ok(Self {
65 steps,
66 estimated_time_ms,
67 })
68 }
69
70 pub fn is_empty(&self) -> bool {
72 self.steps.is_empty()
73 }
74
75 pub fn len(&self) -> usize {
77 self.steps.len()
78 }
79}
80
81fn build_dependency_graph(build_graph: &cadi_core::BuildGraph) -> HashMap<String, Vec<String>> {
83 let mut deps: HashMap<String, Vec<String>> = HashMap::new();
84
85 for node in &build_graph.nodes {
86 deps.entry(node.id.clone()).or_default();
87 }
88
89 for edge in &build_graph.edges {
90 deps.entry(edge.from.clone())
91 .or_default()
92 .push(edge.to.clone());
93 }
94
95 deps
96}
97
98fn collect_steps(
100 build_graph: &cadi_core::BuildGraph,
101 node_id: &str,
102 platform: &str,
103 deps: &HashMap<String, Vec<String>>,
104 visited: &mut HashSet<String>,
105 steps: &mut Vec<BuildStep>,
106) -> CadiResult<()> {
107 if visited.contains(node_id) {
108 return Ok(());
109 }
110
111 if let Some(node_deps) = deps.get(node_id) {
113 for dep_id in node_deps {
114 collect_steps(build_graph, dep_id, platform, deps, visited, steps)?;
115 }
116 }
117
118 visited.insert(node_id.to_string());
119
120 let node = build_graph.nodes.iter()
122 .find(|n| n.id == node_id)
123 .ok_or_else(|| CadiError::BuildFailed(format!("Node '{}' not found", node_id)))?;
124
125 let repr = select_representation(node, platform);
127
128 let step = BuildStep {
130 name: node_id.to_string(),
131 chunk_id: repr.map(|r| r.chunk.clone()),
132 transform: determine_transform(node, platform),
133 inputs: build_inputs(node, deps),
134 depends_on: deps.get(node_id).cloned()
135 .unwrap_or_default(),
136 };
137
138 steps.push(step);
139
140 Ok(())
141}
142
143fn select_representation<'a>(
145 node: &'a cadi_core::GraphNode,
146 platform: &str,
147) -> Option<&'a cadi_core::Representation> {
148 if let Some(r) = node.representations.iter().find(|r| {
150 r.architecture.as_ref().map(|a| a == platform).unwrap_or(false)
151 }) {
152 return Some(r);
153 }
154
155 node.representations.first()
157}
158
159fn determine_transform(node: &cadi_core::GraphNode, platform: &str) -> super::TransformType {
161 if node.source_cadi.is_some() {
163 return super::TransformType::Compile {
164 target: platform.to_string(),
165 };
166 }
167
168 if node.ir_cadi.is_some() {
170 return super::TransformType::Compile {
171 target: platform.to_string(),
172 };
173 }
174
175 if node.container_cadi.is_some() {
177 return super::TransformType::Custom {
178 name: "fetch".to_string(),
179 args: Default::default(),
180 };
181 }
182
183 super::TransformType::Bundle {
185 format: "default".to_string(),
186 }
187}
188
189fn build_inputs(
191 node: &cadi_core::GraphNode,
192 deps: &HashMap<String, Vec<String>>,
193) -> Vec<super::TransformInput> {
194 let mut inputs = Vec::new();
195
196 if let Some(ref source_id) = node.source_cadi {
198 inputs.push(super::TransformInput {
199 chunk_id: source_id.clone(),
200 data: None,
201 role: "main".to_string(),
202 path: None,
203 });
204 } else if let Some(ref ir_id) = node.ir_cadi {
205 inputs.push(super::TransformInput {
206 chunk_id: ir_id.clone(),
207 data: None,
208 role: "main".to_string(),
209 path: None,
210 });
211 } else if let Some(ref blob_id) = node.blob_cadi {
212 inputs.push(super::TransformInput {
213 chunk_id: blob_id.clone(),
214 data: None,
215 role: "main".to_string(),
216 path: None,
217 });
218 }
219
220 if let Some(node_deps) = deps.get(&node.id) {
222 for dep_id in node_deps {
223 inputs.push(super::TransformInput {
224 chunk_id: format!("pending:{}", dep_id),
225 data: None,
226 role: "dependency".to_string(),
227 path: None,
228 });
229 }
230 }
231
232 inputs
233}