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)
135 .map(|d| d.clone())
136 .unwrap_or_default(),
137 };
138
139 steps.push(step);
140
141 Ok(())
142}
143
144fn select_representation<'a>(
146 node: &'a cadi_core::GraphNode,
147 platform: &str,
148) -> Option<&'a cadi_core::Representation> {
149 if let Some(r) = node.representations.iter().find(|r| {
151 r.architecture.as_ref().map(|a| a == platform).unwrap_or(false)
152 }) {
153 return Some(r);
154 }
155
156 node.representations.first()
158}
159
160fn determine_transform(node: &cadi_core::GraphNode, platform: &str) -> super::TransformType {
162 if node.source_cadi.is_some() {
164 return super::TransformType::Compile {
165 target: platform.to_string(),
166 };
167 }
168
169 if node.ir_cadi.is_some() {
171 return super::TransformType::Compile {
172 target: platform.to_string(),
173 };
174 }
175
176 if node.container_cadi.is_some() {
178 return super::TransformType::Custom {
179 name: "fetch".to_string(),
180 args: Default::default(),
181 };
182 }
183
184 super::TransformType::Bundle {
186 format: "default".to_string(),
187 }
188}
189
190fn build_inputs(
192 node: &cadi_core::GraphNode,
193 deps: &HashMap<String, Vec<String>>,
194) -> Vec<super::TransformInput> {
195 let mut inputs = Vec::new();
196
197 if let Some(ref source_id) = node.source_cadi {
199 inputs.push(super::TransformInput {
200 chunk_id: source_id.clone(),
201 data: None,
202 role: "main".to_string(),
203 path: None,
204 });
205 } else if let Some(ref ir_id) = node.ir_cadi {
206 inputs.push(super::TransformInput {
207 chunk_id: ir_id.clone(),
208 data: None,
209 role: "main".to_string(),
210 path: None,
211 });
212 } else if let Some(ref blob_id) = node.blob_cadi {
213 inputs.push(super::TransformInput {
214 chunk_id: blob_id.clone(),
215 data: None,
216 role: "main".to_string(),
217 path: None,
218 });
219 }
220
221 if let Some(node_deps) = deps.get(&node.id) {
223 for dep_id in node_deps {
224 inputs.push(super::TransformInput {
225 chunk_id: format!("pending:{}", dep_id),
226 data: None,
227 role: "dependency".to_string(),
228 path: None,
229 });
230 }
231 }
232
233 inputs
234}