1#[cfg(feature = "alloc")]
7use alloc::{
8 collections::BTreeMap,
9 string::{String, ToString},
10 vec,
11 vec::Vec,
12};
13
14use crate::error::KernelError;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum BuildStage {
19 Fetch,
21 Verify,
23 Extract,
25 Patch,
27 Configure,
29 Compile,
31 Test,
33 Install,
35 Package,
37 Done,
39 Failed,
41}
42
43#[cfg(feature = "alloc")]
45#[derive(Debug, Clone)]
46pub struct BuildConfig {
47 pub name: String,
48 pub version: String,
49 pub source_url: String,
50 pub checksum_sha256: String,
51 pub build_type: super::ports::BuildType,
52 pub dependencies: Vec<String>,
53 pub build_dependencies: Vec<String>,
54 pub configure_flags: Vec<String>,
55 pub make_flags: Vec<String>,
56 pub patches: Vec<String>,
57 pub install_prefix: String,
58}
59
60#[cfg(feature = "alloc")]
61impl BuildConfig {
62 pub fn new(name: &str, version: &str) -> Self {
63 Self {
64 name: name.to_string(),
65 version: version.to_string(),
66 source_url: String::new(),
67 checksum_sha256: String::new(),
68 build_type: super::ports::BuildType::Autotools,
69 dependencies: Vec::new(),
70 build_dependencies: Vec::new(),
71 configure_flags: Vec::new(),
72 make_flags: Vec::new(),
73 patches: Vec::new(),
74 install_prefix: String::from("/usr"),
75 }
76 }
77}
78
79#[cfg(feature = "alloc")]
81#[derive(Debug, Clone)]
82pub struct BuildJob {
83 pub config: BuildConfig,
84 pub stage: BuildStage,
85 pub log: Vec<String>,
86 pub exit_code: Option<i32>,
87 pub staging_dir: String,
88 pub build_dir: String,
89}
90
91#[cfg(feature = "alloc")]
92impl BuildJob {
93 pub fn new(config: BuildConfig) -> Self {
94 let staging = alloc::format!("/tmp/build/{}-{}/staging", config.name, config.version);
95 let build = alloc::format!("/tmp/build/{}-{}/build", config.name, config.version);
96 Self {
97 config,
98 stage: BuildStage::Fetch,
99 log: Vec::new(),
100 exit_code: None,
101 staging_dir: staging,
102 build_dir: build,
103 }
104 }
105
106 pub fn log_message(&mut self, msg: &str) {
107 self.log.push(msg.to_string());
108 }
109
110 pub fn is_complete(&self) -> bool {
111 matches!(self.stage, BuildStage::Done | BuildStage::Failed)
112 }
113}
114
115#[cfg(feature = "alloc")]
117#[derive(Default)]
118pub struct DependencyGraph {
119 nodes: Vec<String>,
120 edges: BTreeMap<String, Vec<String>>,
121}
122
123#[cfg(feature = "alloc")]
124impl DependencyGraph {
125 pub fn new() -> Self {
126 Self {
127 nodes: Vec::new(),
128 edges: BTreeMap::new(),
129 }
130 }
131
132 pub fn add_package(&mut self, name: &str, deps: &[String]) {
133 if !self.nodes.contains(&name.to_string()) {
134 self.nodes.push(name.to_string());
135 }
136 self.edges.insert(name.to_string(), deps.to_vec());
137
138 for dep in deps {
139 if !self.nodes.contains(dep) {
140 self.nodes.push(dep.clone());
141 }
142 }
143 }
144
145 pub fn sort(&self) -> Result<Vec<String>, KernelError> {
147 let mut in_degree: BTreeMap<String, usize> = BTreeMap::new();
148 for node in &self.nodes {
149 in_degree.insert(node.clone(), 0);
150 }
151
152 for deps in self.edges.values() {
153 for dep in deps {
154 *in_degree.entry(dep.clone()).or_insert(0) += 1;
155 }
156 }
157
158 let mut reverse_in_degree: BTreeMap<String, usize> = BTreeMap::new();
164 for node in &self.nodes {
165 reverse_in_degree.insert(node.clone(), 0);
166 }
167
168 for (name, deps) in &self.edges {
170 reverse_in_degree.insert(name.clone(), deps.len());
171 }
172
173 let mut queue: Vec<String> = Vec::new();
174 for (node, °) in &reverse_in_degree {
175 if deg == 0 {
176 queue.push(node.clone());
177 }
178 }
179
180 let mut result = Vec::new();
181 while let Some(node) = queue.pop() {
182 result.push(node.clone());
183
184 for (name, deps) in &self.edges {
186 if deps.contains(&node) {
187 if let Some(deg) = reverse_in_degree.get_mut(name) {
188 *deg = deg.saturating_sub(1);
189 if *deg == 0 {
190 queue.push(name.clone());
191 }
192 }
193 }
194 }
195 }
196
197 if result.len() != self.nodes.len() {
198 return Err(KernelError::InvalidArgument {
199 name: "dependency",
200 value: "cycle detected",
201 });
202 }
203
204 Ok(result)
205 }
206}
207
208#[cfg(feature = "alloc")]
210#[derive(Debug, Clone)]
211pub struct BuildSandbox {
212 pub root_dir: String,
213 pub use_namespace: bool,
214 pub allowed_paths: Vec<String>,
215 pub env_vars: BTreeMap<String, String>,
216}
217
218#[cfg(feature = "alloc")]
219impl BuildSandbox {
220 pub fn new(root: &str) -> Self {
221 let mut env = BTreeMap::new();
222 env.insert("PATH".to_string(), "/usr/bin:/bin".to_string());
223 env.insert("HOME".to_string(), "/tmp/build".to_string());
224
225 Self {
226 root_dir: root.to_string(),
227 use_namespace: true,
228 allowed_paths: vec![
229 "/usr/include".to_string(),
230 "/usr/lib".to_string(),
231 "/usr/bin".to_string(),
232 ],
233 env_vars: env,
234 }
235 }
236}
237
238#[cfg(feature = "alloc")]
240#[derive(Default)]
241pub struct BuildOrchestrator {
242 jobs: Vec<BuildJob>,
243 completed: Vec<String>,
244}
245
246#[cfg(feature = "alloc")]
247impl BuildOrchestrator {
248 pub fn new() -> Self {
249 Self {
250 jobs: Vec::new(),
251 completed: Vec::new(),
252 }
253 }
254
255 pub fn add_job(&mut self, config: BuildConfig) {
256 self.jobs.push(BuildJob::new(config));
257 }
258
259 pub fn execute_job(&mut self, idx: usize) -> Result<(), KernelError> {
261 if idx >= self.jobs.len() {
262 return Err(KernelError::InvalidArgument {
263 name: "dependency",
264 value: "cycle detected",
265 });
266 }
267
268 let job = &mut self.jobs[idx];
269
270 for dep in &job.config.dependencies {
272 if !self.completed.contains(dep) {
273 job.log_message(&alloc::format!("Missing dependency: {}", dep));
274 job.stage = BuildStage::Failed;
275 return Err(KernelError::NotFound {
276 resource: "dependency",
277 id: 0,
278 });
279 }
280 }
281
282 job.stage = BuildStage::Fetch;
284 job.log_message(&alloc::format!(
285 "Fetching {} v{}",
286 job.config.name,
287 job.config.version
288 ));
289
290 job.stage = BuildStage::Verify;
291 job.log_message("Verifying source checksum");
292
293 job.stage = BuildStage::Extract;
294 job.log_message("Extracting source archive");
295
296 job.stage = BuildStage::Patch;
297 for patch in &job.config.patches.clone() {
298 job.log_message(&alloc::format!("Applying patch: {}", patch));
299 }
300
301 job.stage = BuildStage::Configure;
302 let configure_cmd = job.config.build_type.configure_command();
303 if !configure_cmd.is_empty() {
304 job.log_message(&alloc::format!("Running: {}", configure_cmd));
305 }
306
307 job.stage = BuildStage::Compile;
308 let build_cmd = job.config.build_type.build_command();
309 if !build_cmd.is_empty() {
310 job.log_message(&alloc::format!("Running: {}", build_cmd));
311 }
312
313 job.stage = BuildStage::Install;
314 job.log_message(&alloc::format!("Installing to {}", job.staging_dir));
315
316 job.stage = BuildStage::Done;
317 job.exit_code = Some(0);
318
319 let name = job.config.name.clone();
320 self.completed.push(name);
321
322 Ok(())
323 }
324
325 pub fn job_count(&self) -> usize {
326 self.jobs.len()
327 }
328
329 pub fn completed_count(&self) -> usize {
330 self.completed.len()
331 }
332
333 pub fn get_job(&self, idx: usize) -> Option<&BuildJob> {
334 self.jobs.get(idx)
335 }
336}
337
338#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_build_stage_eq() {
348 assert_eq!(BuildStage::Fetch, BuildStage::Fetch);
349 assert_ne!(BuildStage::Fetch, BuildStage::Compile);
350 }
351
352 #[test]
353 fn test_build_config_new() {
354 let config = BuildConfig::new("hello", "1.0");
355 assert_eq!(config.name, "hello");
356 assert_eq!(config.version, "1.0");
357 assert_eq!(config.install_prefix, "/usr");
358 assert!(config.dependencies.is_empty());
359 }
360
361 #[test]
362 fn test_build_job_new() {
363 let config = BuildConfig::new("test-pkg", "2.0");
364 let job = BuildJob::new(config);
365 assert_eq!(job.stage, BuildStage::Fetch);
366 assert!(!job.is_complete());
367 assert!(job.exit_code.is_none());
368 }
369
370 #[test]
371 fn test_build_job_logging() {
372 let config = BuildConfig::new("test", "1.0");
373 let mut job = BuildJob::new(config);
374 job.log_message("Starting build");
375 job.log_message("Build complete");
376 assert_eq!(job.log.len(), 2);
377 assert_eq!(job.log[0], "Starting build");
378 }
379
380 #[test]
381 fn test_build_job_complete() {
382 let config = BuildConfig::new("test", "1.0");
383 let mut job = BuildJob::new(config);
384 job.stage = BuildStage::Done;
385 assert!(job.is_complete());
386 job.stage = BuildStage::Failed;
387 assert!(job.is_complete());
388 job.stage = BuildStage::Compile;
389 assert!(!job.is_complete());
390 }
391
392 #[test]
393 fn test_dependency_graph_empty() {
394 let graph = DependencyGraph::new();
395 let result = graph.sort().unwrap();
396 assert!(result.is_empty());
397 }
398
399 #[test]
400 fn test_dependency_graph_single() {
401 let mut graph = DependencyGraph::new();
402 graph.add_package("hello", &[]);
403 let result = graph.sort().unwrap();
404 assert_eq!(result, vec!["hello"]);
405 }
406
407 #[test]
408 fn test_dependency_graph_chain() {
409 let mut graph = DependencyGraph::new();
410 graph.add_package("app", &["libfoo".to_string()]);
411 graph.add_package("libfoo", &["libc".to_string()]);
412 graph.add_package("libc", &[]);
413
414 let result = graph.sort().unwrap();
415 let libc_pos = result.iter().position(|n| n == "libc").unwrap();
417 let libfoo_pos = result.iter().position(|n| n == "libfoo").unwrap();
418 let app_pos = result.iter().position(|n| n == "app").unwrap();
419 assert!(libc_pos < libfoo_pos);
420 assert!(libfoo_pos < app_pos);
421 }
422
423 #[test]
424 fn test_dependency_graph_diamond() {
425 let mut graph = DependencyGraph::new();
426 graph.add_package("app", &["liba".to_string(), "libb".to_string()]);
427 graph.add_package("liba", &["libc".to_string()]);
428 graph.add_package("libb", &["libc".to_string()]);
429 graph.add_package("libc", &[]);
430
431 let result = graph.sort().unwrap();
432 let libc_pos = result.iter().position(|n| n == "libc").unwrap();
433 let app_pos = result.iter().position(|n| n == "app").unwrap();
434 assert!(libc_pos < app_pos);
435 }
436
437 #[test]
438 fn test_build_sandbox() {
439 let sandbox = BuildSandbox::new("/tmp/sandbox");
440 assert_eq!(sandbox.root_dir, "/tmp/sandbox");
441 assert!(sandbox.use_namespace);
442 assert!(sandbox.env_vars.contains_key("PATH"));
443 }
444
445 #[test]
446 fn test_orchestrator_basic() {
447 let mut orch = BuildOrchestrator::new();
448 assert_eq!(orch.job_count(), 0);
449
450 let config = BuildConfig::new("test", "1.0");
451 orch.add_job(config);
452 assert_eq!(orch.job_count(), 1);
453 assert_eq!(orch.completed_count(), 0);
454 }
455
456 #[test]
457 fn test_orchestrator_execute() {
458 let mut orch = BuildOrchestrator::new();
459 let config = BuildConfig::new("test", "1.0");
460 orch.add_job(config);
461
462 orch.execute_job(0).unwrap();
463 assert_eq!(orch.completed_count(), 1);
464 let job = orch.get_job(0).unwrap();
465 assert_eq!(job.stage, BuildStage::Done);
466 assert_eq!(job.exit_code, Some(0));
467 }
468
469 #[test]
470 fn test_orchestrator_missing_dep() {
471 let mut orch = BuildOrchestrator::new();
472 let mut config = BuildConfig::new("app", "1.0");
473 config.dependencies.push("missing-lib".to_string());
474 orch.add_job(config);
475
476 let result = orch.execute_job(0);
477 assert!(result.is_err());
478 }
479
480 #[test]
481 fn test_orchestrator_invalid_index() {
482 let mut orch = BuildOrchestrator::new();
483 let result = orch.execute_job(99);
484 assert!(result.is_err());
485 }
486
487 #[test]
488 fn test_orchestrator_with_deps() {
489 let mut orch = BuildOrchestrator::new();
490
491 let libc = BuildConfig::new("libc", "1.0");
492 orch.add_job(libc);
493
494 let mut app = BuildConfig::new("app", "1.0");
495 app.dependencies.push("libc".to_string());
496 orch.add_job(app);
497
498 orch.execute_job(0).unwrap();
500 orch.execute_job(1).unwrap();
502 assert_eq!(orch.completed_count(), 2);
503 }
504}