1use cadi_core::{CadiError, CadiResult, Manifest};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
8pub struct BuildConfig {
9 pub parallel_jobs: usize,
11 pub cache_dir: PathBuf,
13 pub use_remote_cache: bool,
15 pub fail_fast: bool,
17 pub verbose: bool,
19}
20
21impl Default for BuildConfig {
22 fn default() -> Self {
23 Self {
24 parallel_jobs: num_cpus::get(),
25 cache_dir: dirs::cache_dir()
26 .map(|d| d.join("cadi"))
27 .unwrap_or_else(|| PathBuf::from(".cadi-cache")),
28 use_remote_cache: true,
29 fail_fast: false,
30 verbose: false,
31 }
32 }
33}
34
35#[derive(Debug)]
37pub struct BuildResult {
38 pub built: Vec<String>,
40 pub cached: Vec<String>,
42 pub failed: Vec<BuildFailure>,
44 pub duration_ms: u64,
46}
47
48#[derive(Debug)]
50pub struct BuildFailure {
51 pub chunk_id: String,
52 pub error: String,
53}
54
55pub struct BuildEngine {
57 config: BuildConfig,
58 cache: super::BuildCache,
59}
60
61impl BuildEngine {
62 pub fn new(config: BuildConfig) -> Self {
64 let cache = super::BuildCache::new(config.cache_dir.clone());
65 Self { config, cache }
66 }
67
68 pub async fn build(&self, manifest: &Manifest, target: &str) -> CadiResult<BuildResult> {
70 let start = std::time::Instant::now();
71
72 let target_config = manifest.find_target(target)
73 .ok_or_else(|| CadiError::BuildFailed(format!("Target '{}' not found", target)))?;
74
75 tracing::info!("Building target '{}' for platform '{}'", target, target_config.platform);
76
77 let plan = super::BuildPlan::from_manifest(manifest, target)?;
79
80 if self.config.verbose {
81 tracing::debug!("Build plan: {} steps", plan.steps.len());
82 }
83
84 let mut built = Vec::new();
85 let mut cached = Vec::new();
86 let mut failed = Vec::new();
87
88 for step in &plan.steps {
90 if let Some(chunk_id) = &step.chunk_id {
92 if self.cache.has(chunk_id)? {
93 if self.config.verbose {
94 tracing::info!("Cache hit for {}", chunk_id);
95 }
96 println!(" {} Fetched {} from cache",
97 console::style("✓").green(),
98 console::style(&step.name).cyan());
99 cached.push(chunk_id.clone());
100 continue;
101 }
102 }
103
104 println!(" {} Building {}...",
106 console::style("→").cyan(),
107 console::style(&step.name).yellow());
108
109 match self.execute_step(step).await {
110 Ok(chunk_id) => {
111 tracing::debug!("Built {}", chunk_id);
112 built.push(chunk_id);
113 }
114 Err(e) => {
115 let failure = BuildFailure {
116 chunk_id: step.chunk_id.clone().unwrap_or_else(|| step.name.clone()),
117 error: e.to_string(),
118 };
119 failed.push(failure);
120
121 if self.config.fail_fast {
122 break;
123 }
124 }
125 }
126 }
127
128 let duration_ms = start.elapsed().as_millis() as u64;
129
130 Ok(BuildResult {
131 built,
132 cached,
133 failed,
134 duration_ms,
135 })
136 }
137
138 async fn execute_step(&self, step: &super::BuildStep) -> CadiResult<String> {
140 tracing::info!("Executing step: {}", step.name);
141
142 let mut prepared_inputs = Vec::new();
144 for input in &step.inputs {
145 let mut prepared = input.clone();
146 if self.cache.has(&input.chunk_id)? {
147 prepared.path = Some(self.cache.get_path(&input.chunk_id).to_string_lossy().to_string());
148 }
149 prepared_inputs.push(prepared);
150 }
151
152 let transformer = super::Transformer::new();
154 let result = transformer.transform(&step.transform, &prepared_inputs).await?;
155
156 if let Some(ref chunk_id) = step.chunk_id {
158 self.cache.store(chunk_id, &result)?;
159 }
160
161 Ok(step.chunk_id.clone().unwrap_or_else(|| step.name.clone()))
162 }
163
164 pub fn get_chunk_path(&self, chunk_id: &str) -> Option<PathBuf> {
166 if self.cache.has(chunk_id).unwrap_or(false) {
167 Some(self.cache.get_path(chunk_id))
168 } else {
169 None
170 }
171 }
172
173 pub fn cache_stats(&self) -> CadiResult<CacheStats> {
175 self.cache.stats()
176 }
177}
178
179#[derive(Debug, Default)]
181pub struct CacheStats {
182 pub total_entries: usize,
183 pub total_size_bytes: u64,
184 pub hit_rate: f64,
185}
186
187mod num_cpus {
189 pub fn get() -> usize {
190 std::thread::available_parallelism()
191 .map(|p| p.get())
192 .unwrap_or(1)
193 }
194}
195
196mod dirs {
198 use std::path::PathBuf;
199
200 pub fn cache_dir() -> Option<PathBuf> {
201 std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cache"))
202 }
203}