cadi_builder/
engine.rs

1//! Build engine for CADI
2
3use cadi_core::{CadiError, CadiResult, Manifest};
4use std::path::PathBuf;
5
6/// Build engine configuration
7#[derive(Debug, Clone)]
8pub struct BuildConfig {
9    /// Maximum parallel jobs
10    pub parallel_jobs: usize,
11    /// Cache directory
12    pub cache_dir: PathBuf,
13    /// Whether to use remote cache
14    pub use_remote_cache: bool,
15    /// Whether to fail fast on first error
16    pub fail_fast: bool,
17    /// Verbosity level
18    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/// Build result
36#[derive(Debug)]
37pub struct BuildResult {
38    /// Successfully built chunks
39    pub built: Vec<String>,
40    /// Chunks retrieved from cache
41    pub cached: Vec<String>,
42    /// Failed builds
43    pub failed: Vec<BuildFailure>,
44    /// Total build time in milliseconds
45    pub duration_ms: u64,
46}
47
48/// A build failure
49#[derive(Debug)]
50pub struct BuildFailure {
51    pub chunk_id: String,
52    pub error: String,
53}
54
55/// Build engine
56pub struct BuildEngine {
57    config: BuildConfig,
58    cache: super::BuildCache,
59}
60
61impl BuildEngine {
62    /// Create a new build engine
63    pub fn new(config: BuildConfig) -> Self {
64        let cache = super::BuildCache::new(config.cache_dir.clone());
65        Self { config, cache }
66    }
67
68    /// Build a manifest for a given target
69    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        // Create build plan
78        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        // Execute build plan
89        for step in &plan.steps {
90            // Check cache first
91            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            // Execute transformation
105            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    /// Execute a single build step
139    async fn execute_step(&self, step: &super::BuildStep) -> CadiResult<String> {
140        tracing::info!("Executing step: {}", step.name);
141        
142        // Prepare inputs with paths
143        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        // Execute the transformation
153        let transformer = super::Transformer::new();
154        let result = transformer.transform(&step.transform, &prepared_inputs).await?;
155        
156        // Store in cache
157        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    /// Get the path to a cached chunk
165    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    /// Get cache statistics
174    pub fn cache_stats(&self) -> CadiResult<CacheStats> {
175        self.cache.stats()
176    }
177}
178
179/// Cache statistics
180#[derive(Debug, Default)]
181pub struct CacheStats {
182    pub total_entries: usize,
183    pub total_size_bytes: u64,
184    pub hit_rate: f64,
185}
186
187// Placeholder for num_cpus - in actual impl would use num_cpus crate
188mod num_cpus {
189    pub fn get() -> usize {
190        std::thread::available_parallelism()
191            .map(|p| p.get())
192            .unwrap_or(1)
193    }
194}
195
196// Placeholder for dirs - in actual impl would use directories crate
197mod 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}