1pub mod collection;
12#[cfg(feature = "alloc")]
13pub mod llvm;
14#[cfg(feature = "alloc")]
15pub mod rustc_bootstrap;
16#[cfg(feature = "alloc")]
17pub mod rustdoc;
18
19#[cfg(feature = "alloc")]
20use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
21
22#[cfg(feature = "alloc")]
23use super::toml_parser;
24#[cfg(feature = "alloc")]
25use crate::error::KernelError;
26
27#[cfg(feature = "alloc")]
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum BuildType {
35 Autotools,
37 CMake,
39 Meson,
41 Cargo,
43 Make,
45 Custom,
47}
48
49#[cfg(feature = "alloc")]
50impl BuildType {
51 pub fn parse(s: &str) -> Option<Self> {
53 match s {
54 "autotools" | "Autotools" => Some(Self::Autotools),
55 "cmake" | "CMake" | "CMAKE" => Some(Self::CMake),
56 "meson" | "Meson" => Some(Self::Meson),
57 "cargo" | "Cargo" => Some(Self::Cargo),
58 "make" | "Make" => Some(Self::Make),
59 "custom" | "Custom" => Some(Self::Custom),
60 _ => None,
61 }
62 }
63
64 pub fn configure_command(&self) -> &'static str {
66 match self {
67 Self::Autotools => "./configure --prefix=/usr",
68 Self::CMake => "cmake -B build -DCMAKE_INSTALL_PREFIX=/usr",
69 Self::Meson => "meson setup build --prefix=/usr",
70 Self::Cargo => "cargo build --release",
71 Self::Make => "",
72 Self::Custom => "",
73 }
74 }
75
76 pub fn build_command(&self) -> &'static str {
78 match self {
79 Self::Autotools | Self::Make => "make -j$(nproc)",
80 Self::CMake => "cmake --build build",
81 Self::Meson => "ninja -C build",
82 Self::Cargo => "", Self::Custom => "",
84 }
85 }
86
87 pub fn install_command(&self) -> &'static str {
89 match self {
90 Self::Autotools | Self::Make => "make install DESTDIR=$PKG_DIR",
91 Self::CMake => "cmake --install build --prefix $PKG_DIR/usr",
92 Self::Meson => "DESTDIR=$PKG_DIR ninja -C build install",
93 Self::Cargo => "cargo install --root $PKG_DIR/usr --path .",
94 Self::Custom => "",
95 }
96 }
97}
98
99#[cfg(feature = "alloc")]
101#[derive(Debug, Clone)]
102pub struct Port {
103 pub name: String,
105 pub version: String,
107 pub description: String,
109 pub homepage: String,
111 pub sources: Vec<String>,
113 pub checksums: Vec<[u8; 32]>,
115 pub build_type: BuildType,
117 pub build_steps: Vec<String>,
119 pub dependencies: Vec<String>,
121 pub category: String,
123 pub license: String,
125}
126
127#[cfg(feature = "alloc")]
128impl Port {
129 pub fn new(name: String, version: String) -> Self {
131 Self {
132 name,
133 version,
134 description: String::new(),
135 homepage: String::new(),
136 sources: Vec::new(),
137 checksums: Vec::new(),
138 build_type: BuildType::Make,
139 build_steps: Vec::new(),
140 dependencies: Vec::new(),
141 category: String::from("misc"),
142 license: String::new(),
143 }
144 }
145}
146
147#[cfg(feature = "alloc")]
153#[derive(Debug, Clone)]
154pub struct BuildEnvironment {
155 pub build_root: String,
157 pub source_dir: String,
159 pub build_dir: String,
161 pub pkg_dir: String,
163 pub env_vars: BTreeMap<String, String>,
165 pub build_timeout_ms: u64,
167}
168
169#[cfg(feature = "alloc")]
170impl BuildEnvironment {
171 pub fn new(port: &Port) -> Self {
173 let build_root = alloc::format!("/tmp/ports-build/{}-{}", port.name, port.version);
174 let source_dir = alloc::format!("{}/src", build_root);
175 let build_dir = alloc::format!("{}/build", build_root);
176 let pkg_dir = alloc::format!("{}/pkg", build_root);
177
178 let mut env_vars = BTreeMap::new();
179 env_vars.insert(String::from("PKG_DIR"), pkg_dir.clone());
180 env_vars.insert(String::from("SRC_DIR"), source_dir.clone());
181 env_vars.insert(String::from("BUILD_DIR"), build_dir.clone());
182 env_vars.insert(String::from("PORT_NAME"), port.name.clone());
183 env_vars.insert(String::from("PORT_VERSION"), port.version.clone());
184
185 Self {
186 build_root,
187 source_dir,
188 build_dir,
189 pkg_dir,
190 env_vars,
191 build_timeout_ms: 300_000, }
193 }
194
195 pub fn setup(&self) -> Result<(), KernelError> {
199 if self.build_root.is_empty() {
201 return Err(KernelError::InvalidArgument {
202 name: "build_root",
203 value: "empty_path",
204 });
205 }
206
207 crate::println!("[PORTS] Build environment ready: {}", self.build_root);
215
216 Ok(())
217 }
218
219 pub fn get_env(&self, key: &str) -> Option<&str> {
221 self.env_vars.get(key).map(|v| v.as_str())
222 }
223
224 pub fn set_env(&mut self, key: String, value: String) {
226 self.env_vars.insert(key, value);
227 }
228}
229
230#[cfg(feature = "alloc")]
236pub struct PortManager {
237 ports: BTreeMap<String, Port>,
239}
240
241#[cfg(feature = "alloc")]
242impl PortManager {
243 pub fn new() -> Self {
245 Self {
246 ports: BTreeMap::new(),
247 }
248 }
249
250 #[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
254 pub fn load_port(&mut self, path: &str, content: &str) -> Result<(), KernelError> {
255 let port = parse_portfile(content).inspect_err(|e| {
256 crate::println!("[PORTS] Failed to parse {}: {:?}", path, e);
257 })?;
258
259 crate::println!(
260 "[PORTS] Loaded port {} {} from {}",
261 port.name,
262 port.version,
263 path
264 );
265 self.ports.insert(port.name.clone(), port);
266 Ok(())
267 }
268
269 pub fn register_port(&mut self, port: Port) {
271 self.ports.insert(port.name.clone(), port);
272 }
273
274 pub fn get_port(&self, name: &str) -> Option<&Port> {
276 self.ports.get(name)
277 }
278
279 pub fn list_ports(&self) -> Vec<&Port> {
281 self.ports.values().collect()
282 }
283
284 pub fn search(&self, query: &str) -> Vec<&Port> {
287 let query_lower = query.to_lowercase();
288 self.ports
289 .values()
290 .filter(|p| {
291 p.name.to_lowercase().contains(&query_lower)
292 || p.description.to_lowercase().contains(&query_lower)
293 })
294 .collect()
295 }
296
297 pub fn resolve_build_deps(&self, port: &Port) -> Result<Vec<String>, KernelError> {
302 let mut resolved: Vec<String> = Vec::new();
303 let mut visited = BTreeMap::<String, bool>::new();
304 self.resolve_deps_inner(&port.name, &mut resolved, &mut visited)?;
305 if let Some(pos) = resolved.iter().position(|n| n == &port.name) {
308 resolved.remove(pos);
309 }
310 Ok(resolved)
311 }
312
313 fn resolve_deps_inner(
315 &self,
316 name: &str,
317 resolved: &mut Vec<String>,
318 visited: &mut BTreeMap<String, bool>,
319 ) -> Result<(), KernelError> {
320 if let Some(&in_progress) = visited.get(name) {
321 if in_progress {
322 return Err(KernelError::InvalidState {
324 expected: "acyclic dependency graph",
325 actual: "dependency cycle detected",
326 });
327 }
328 return Ok(());
330 }
331
332 visited.insert(String::from(name), true);
334
335 let port = self.ports.get(name).ok_or(KernelError::NotFound {
336 resource: "port",
337 id: 0,
338 })?;
339
340 for dep_name in &port.dependencies {
341 self.resolve_deps_inner(dep_name, resolved, visited)?;
342 }
343
344 visited.insert(String::from(name), false);
346 resolved.push(String::from(name));
347 Ok(())
348 }
349
350 pub fn port_count(&self) -> usize {
352 self.ports.len()
353 }
354}
355
356#[cfg(feature = "alloc")]
357impl Default for PortManager {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363#[cfg(feature = "alloc")]
391fn parse_portfile(content: &str) -> Result<Port, KernelError> {
392 let toml = toml_parser::parse_toml(content)?;
393
394 let port_table =
396 toml.get("port")
397 .and_then(|v| v.as_table())
398 .ok_or(KernelError::InvalidArgument {
399 name: "portfile",
400 value: "missing_port_section",
401 })?;
402
403 let name =
404 port_table
405 .get("name")
406 .and_then(|v| v.as_str())
407 .ok_or(KernelError::InvalidArgument {
408 name: "portfile",
409 value: "missing_port_name",
410 })?;
411
412 let version =
413 port_table
414 .get("version")
415 .and_then(|v| v.as_str())
416 .ok_or(KernelError::InvalidArgument {
417 name: "portfile",
418 value: "missing_port_version",
419 })?;
420
421 let description = port_table
422 .get("description")
423 .and_then(|v| v.as_str())
424 .unwrap_or("");
425
426 let homepage = port_table
427 .get("homepage")
428 .and_then(|v| v.as_str())
429 .unwrap_or("");
430
431 let license = port_table
432 .get("license")
433 .and_then(|v| v.as_str())
434 .unwrap_or("");
435
436 let category = port_table
437 .get("category")
438 .and_then(|v| v.as_str())
439 .unwrap_or("misc");
440
441 let build_type_str = port_table
442 .get("build_type")
443 .and_then(|v| v.as_str())
444 .unwrap_or("make");
445
446 let build_type = BuildType::parse(build_type_str).unwrap_or(BuildType::Make);
447
448 let mut sources = Vec::new();
450 let mut checksums: Vec<[u8; 32]> = Vec::new();
451
452 if let Some(src_table) = toml.get("sources").and_then(|v| v.as_table()) {
453 if let Some(urls) = src_table.get("urls").and_then(|v| v.as_array()) {
454 for url_val in urls {
455 if let Some(url) = url_val.as_str() {
456 sources.push(String::from(url));
457 }
458 }
459 }
460 if let Some(chk_arr) = src_table.get("checksums").and_then(|v| v.as_array()) {
461 for chk_val in chk_arr {
462 if let Some(hex) = chk_val.as_str() {
463 checksums.push(parse_hex_checksum(hex));
464 }
465 }
466 }
467 }
468
469 let mut dependencies = Vec::new();
471 if let Some(dep_table) = toml.get("dependencies").and_then(|v| v.as_table()) {
472 if let Some(build_deps) = dep_table.get("build").and_then(|v| v.as_array()) {
473 for dep_val in build_deps {
474 if let Some(dep) = dep_val.as_str() {
475 dependencies.push(String::from(dep));
476 }
477 }
478 }
479 if let Some(runtime_deps) = dep_table.get("runtime").and_then(|v| v.as_array()) {
481 for dep_val in runtime_deps {
482 if let Some(dep) = dep_val.as_str() {
483 dependencies.push(String::from(dep));
484 }
485 }
486 }
487 }
488
489 let mut build_steps = Vec::new();
491 if let Some(build_table) = toml.get("build").and_then(|v| v.as_table()) {
492 if let Some(steps) = build_table.get("steps").and_then(|v| v.as_array()) {
493 for step_val in steps {
494 if let Some(step) = step_val.as_str() {
495 build_steps.push(String::from(step));
496 }
497 }
498 }
499 }
500
501 Ok(Port {
502 name: String::from(name),
503 version: String::from(version),
504 description: String::from(description),
505 homepage: String::from(homepage),
506 sources,
507 checksums,
508 build_type,
509 build_steps,
510 dependencies,
511 category: String::from(category),
512 license: String::from(license),
513 })
514}
515
516#[cfg(feature = "alloc")]
519fn parse_hex_checksum(hex: &str) -> [u8; 32] {
520 let mut result = [0u8; 32];
521 let hex = hex.trim();
522 let bytes = hex.as_bytes();
523
524 let mut i = 0;
525 let mut out = 0;
526 while i + 1 < bytes.len() && out < 32 {
527 let high = hex_nibble(bytes[i]);
528 let low = hex_nibble(bytes[i + 1]);
529 result[out] = (high << 4) | low;
530 i += 2;
531 out += 1;
532 }
533
534 result
535}
536
537#[cfg(feature = "alloc")]
539fn hex_nibble(b: u8) -> u8 {
540 match b {
541 b'0'..=b'9' => b - b'0',
542 b'a'..=b'f' => b - b'a' + 10,
543 b'A'..=b'F' => b - b'A' + 10,
544 _ => 0,
545 }
546}
547
548#[cfg(feature = "alloc")]
558pub fn build_port(port: &Port, env: &mut BuildEnvironment) -> Result<(), KernelError> {
559 let _label = build_type_label(port.build_type);
560 crate::println!(
561 "[PORTS] Building {} {} ({})",
562 port.name,
563 port.version,
564 _label
565 );
566
567 crate::pkg::reproducible::normalize_environment(env);
569
570 verify_checksums(port)?;
572
573 configure_port(port, env)?;
575
576 execute_build(port, env)?;
578
579 package_result(port, env)?;
581
582 let pkg_dir = env.pkg_dir.clone();
584 match crate::pkg::reproducible::create_build_manifest(port, env, &pkg_dir) {
585 Ok(_manifest) => {
586 crate::println!(
587 "[PORTS] Build manifest recorded ({} inputs, {} outputs)",
588 _manifest.inputs.source_hashes.len(),
589 _manifest.outputs.file_count
590 );
591 }
592 Err(_e) => {
593 crate::println!("[PORTS] Warning: could not create build manifest: {:?}", _e);
594 }
595 }
596
597 crate::println!("[PORTS] Successfully built {} {}", port.name, port.version);
598 Ok(())
599}
600
601#[cfg(feature = "alloc")]
608fn verify_checksums(port: &Port) -> Result<(), KernelError> {
609 if port.sources.is_empty() {
610 return Err(KernelError::InvalidArgument {
611 name: "port_sources",
612 value: "no_sources_defined",
613 });
614 }
615
616 if !port.checksums.is_empty() && port.checksums.len() != port.sources.len() {
618 return Err(KernelError::InvalidArgument {
619 name: "port_checksums",
620 value: "checksum_count_mismatch",
621 });
622 }
623
624 for (i, source) in port.sources.iter().enumerate() {
625 if source.is_empty() {
626 return Err(KernelError::InvalidArgument {
627 name: "port_source_url",
628 value: "empty_url",
629 });
630 }
631
632 if i >= port.checksums.len() {
633 continue;
635 }
636
637 let zero_checksum = [0u8; 32];
638 if port.checksums[i] == zero_checksum {
639 crate::println!(
640 "[PORTS] WARNING: zero checksum for source {}, skipping verify",
641 i
642 );
643 continue;
644 }
645
646 let _filename = source.rsplit('/').next().unwrap_or("source.tar.gz");
648 let _archive_path = alloc::format!(
649 "/tmp/ports-build/{}-{}/src/{}",
650 port.name,
651 port.version,
652 _filename
653 );
654
655 let _verified = verify_source_from_vfs(&_archive_path, &port.checksums[i], i)?;
657 }
658
659 Ok(())
660}
661
662#[cfg(feature = "alloc")]
668#[cfg_attr(
669 not(target_arch = "x86_64"),
670 allow(unused_variables, clippy::unnecessary_wraps)
671)]
672fn verify_source_from_vfs(
673 archive_path: &str,
674 expected: &[u8; 32],
675 source_index: usize,
676) -> Result<bool, KernelError> {
677 let vfs_lock = match crate::fs::try_get_vfs() {
678 Some(lock) => lock,
679 None => {
680 crate::println!(
681 "[PORTS] WARNING: VFS not available, skipping checksum verify for source {}",
682 source_index
683 );
684 return Ok(false);
685 }
686 };
687
688 let vfs = vfs_lock.read();
689 let node = match vfs.resolve_path(archive_path) {
690 Ok(n) => n,
691 Err(_) => {
692 crate::println!(
693 "[PORTS] WARNING: source file not found at {}, skipping verify",
694 archive_path
695 );
696 return Ok(false);
697 }
698 };
699
700 let metadata = node.metadata().map_err(|_| KernelError::InvalidState {
702 expected: "readable source file",
703 actual: "metadata unavailable",
704 })?;
705
706 let file_size = metadata.size;
707 if file_size == 0 {
708 crate::println!(
709 "[PORTS] WARNING: empty source file at {}, skipping verify",
710 archive_path
711 );
712 return Ok(false);
713 }
714
715 let mut buf = vec![0u8; file_size];
717 let bytes_read = node
718 .read(0, &mut buf)
719 .map_err(|_| KernelError::InvalidState {
720 expected: "readable source file",
721 actual: "read failed",
722 })?;
723
724 let hash = crate::crypto::hash::sha256(&buf[..bytes_read]);
726 if hash.as_bytes() != expected {
727 crate::println!(
728 "[PORTS] ERROR: checksum mismatch for source {} at {}",
729 source_index,
730 archive_path
731 );
732 return Err(KernelError::PermissionDenied {
733 operation: "verify source checksum",
734 });
735 }
736
737 crate::println!(
738 "[PORTS] Checksum verified for source {} (SHA-256 match)",
739 source_index
740 );
741 Ok(true)
742}
743
744#[cfg(feature = "alloc")]
750#[cfg_attr(
751 not(target_arch = "x86_64"),
752 allow(unused_variables, clippy::for_kv_map)
753)]
754fn execute_command(
755 cmd: &str,
756 env: &BuildEnvironment,
757 working_dir: &str,
758) -> Result<i32, KernelError> {
759 crate::println!("[PORTS] exec: {} (in {})", cmd, working_dir);
768
769 for (_key, _value) in &env.env_vars {
771 crate::println!("[PORTS] {}={}", _key, _value);
772 }
773
774 Ok(0)
776}
777
778#[cfg(feature = "alloc")]
780fn configure_port(port: &Port, env: &BuildEnvironment) -> Result<(), KernelError> {
781 let configure_cmd = port.build_type.configure_command();
782 if configure_cmd.is_empty() {
783 crate::println!("[PORTS] No configure step for build type");
784 return Ok(());
785 }
786
787 crate::println!(
788 "[PORTS] Configure: {} (in {})",
789 configure_cmd,
790 env.source_dir
791 );
792
793 let exit_code = execute_command(configure_cmd, env, &env.source_dir)?;
794 if exit_code != 0 {
795 return Err(KernelError::InvalidState {
796 expected: "configure exit code 0",
797 actual: "configure command failed",
798 });
799 }
800
801 Ok(())
802}
803
804#[cfg(feature = "alloc")]
810#[cfg_attr(
811 not(target_arch = "x86_64"),
812 allow(unused_variables, clippy::for_kv_map)
813)]
814fn execute_build(port: &Port, env: &BuildEnvironment) -> Result<(), KernelError> {
815 let steps: Vec<&str> = if port.build_steps.is_empty() {
816 let cmd = port.build_type.build_command();
818 if cmd.is_empty() {
819 vec![]
820 } else {
821 vec![cmd]
822 }
823 } else {
824 port.build_steps.iter().map(|s| s.as_str()).collect()
825 };
826
827 if steps.is_empty() {
828 crate::println!("[PORTS] No build steps to execute");
829 return Ok(());
830 }
831
832 let _log_path = alloc::format!("/var/log/ports/{}-{}-build.log", port.name, port.version);
833 crate::println!("[PORTS] Build output will be logged to {}", _log_path);
834 crate::println!("[PORTS] Build timeout: {} ms", env.build_timeout_ms);
835
836 for (i, step) in steps.iter().enumerate() {
837 crate::println!(
838 "[PORTS] Step {}/{}: {} (in {})",
839 i + 1,
840 steps.len(),
841 step,
842 env.build_dir
843 );
844
845 let exit_code = execute_command(step, env, &env.build_dir)?;
846 if exit_code != 0 {
847 crate::println!(
848 "[PORTS] ERROR: build step {}/{} failed with exit code {}",
849 i + 1,
850 steps.len(),
851 exit_code
852 );
853 return Err(KernelError::InvalidState {
854 expected: "build step exit code 0",
855 actual: "build step failed",
856 });
857 }
858 }
859
860 Ok(())
861}
862
863#[cfg(feature = "alloc")]
869#[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
870fn package_result(port: &Port, env: &BuildEnvironment) -> Result<(), KernelError> {
871 crate::println!("[PORTS] Packaging {} from {}", port.name, env.pkg_dir);
872
873 let install_cmd = port.build_type.install_command();
875 if !install_cmd.is_empty() {
876 crate::println!("[PORTS] Install command: {}", install_cmd);
877 let exit_code = execute_command(install_cmd, env, &env.build_dir)?;
878 if exit_code != 0 {
879 return Err(KernelError::InvalidState {
880 expected: "install exit code 0",
881 actual: "install command failed",
882 });
883 }
884 }
885
886 let _file_records = collect_installed_files(&env.pkg_dir);
888
889 let _metadata = super::PackageMetadata {
891 name: port.name.clone(),
892 version: parse_port_version(&port.version),
893 author: String::new(),
894 description: port.description.clone(),
895 license: port.license.clone(),
896 dependencies: port
897 .dependencies
898 .iter()
899 .map(|dep| super::Dependency {
900 name: dep.clone(),
901 version_req: String::from(">=0.0.0"),
902 })
903 .collect(),
904 conflicts: Vec::new(),
905 };
906
907 let _vpkg_path = alloc::format!("/var/cache/packages/{}-{}.vpkg", port.name, port.version);
908 crate::println!(
909 "[PORTS] Package metadata: {} v{} ({} files tracked)",
910 port.name,
911 port.version,
912 _file_records.len()
913 );
914 crate::println!("[PORTS] vpkg destination: {}", _vpkg_path);
915
916 Ok(())
921}
922
923#[cfg(feature = "alloc")]
929#[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
930fn collect_installed_files(pkg_dir: &str) -> Vec<super::manifest::FileRecord> {
931 use super::manifest::{FileRecord, FileType};
932
933 let mut records = Vec::new();
934
935 let vfs_lock = match crate::fs::try_get_vfs() {
936 Some(lock) => lock,
937 None => {
938 crate::println!(
939 "[PORTS] WARNING: VFS not available, cannot scan {} for installed files",
940 pkg_dir
941 );
942 return records;
943 }
944 };
945
946 let vfs = vfs_lock.read();
947 let node = match vfs.resolve_path(pkg_dir) {
948 Ok(n) => n,
949 Err(_) => {
950 crate::println!(
951 "[PORTS] WARNING: pkg_dir {} not found, no files to package",
952 pkg_dir
953 );
954 return records;
955 }
956 };
957
958 match node.readdir() {
960 Ok(entries) => {
961 for entry in &entries {
962 let file_path = alloc::format!("{}/{}", pkg_dir, entry.name);
963 let file_type = FileType::from_path(&file_path);
964
965 let size = if let Ok(child) = vfs.resolve_path(&file_path) {
967 child.metadata().map(|m| m.size as u64).unwrap_or(0)
968 } else {
969 0
970 };
971
972 let checksum = if let Ok(child) = vfs.resolve_path(&file_path) {
974 let mut buf = vec![0u8; size as usize];
975 if let Ok(n) = child.read(0, &mut buf) {
976 super::manifest::fnv1a_hash(&buf[..n])
977 } else {
978 0
979 }
980 } else {
981 0
982 };
983
984 records.push(FileRecord {
985 path: file_path,
986 size,
987 checksum,
988 file_type,
989 });
990 }
991 crate::println!(
992 "[PORTS] Collected {} installed files from {}",
993 records.len(),
994 pkg_dir
995 );
996 }
997 Err(_) => {
998 crate::println!(
999 "[PORTS] WARNING: cannot list directory {}, skipping file collection",
1000 pkg_dir
1001 );
1002 }
1003 }
1004
1005 records
1006}
1007
1008#[cfg(feature = "alloc")]
1011fn parse_port_version(version_str: &str) -> super::Version {
1012 let parts: Vec<&str> = version_str.split('.').collect();
1013
1014 let major = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
1015 let minor = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
1016 let patch = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
1017
1018 super::Version {
1019 major,
1020 minor,
1021 patch,
1022 }
1023}
1024
1025#[cfg(feature = "alloc")]
1030#[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
1031pub fn fetch_source(port: &Port, env: &BuildEnvironment) -> Result<(), KernelError> {
1032 if port.sources.is_empty() {
1033 return Ok(());
1034 }
1035
1036 for (i, url) in port.sources.iter().enumerate() {
1037 let _filename = url.rsplit('/').next().unwrap_or("source.tar.gz");
1039 let _dest_path = alloc::format!("{}/{}", env.source_dir, _filename);
1040
1041 crate::println!(
1042 "[PORTS] Fetching source {}/{}: {} -> {}",
1043 i + 1,
1044 port.sources.len(),
1045 url,
1046 _dest_path
1047 );
1048
1049 }
1056
1057 Ok(())
1058}
1059
1060#[cfg(feature = "alloc")]
1062fn build_type_label(bt: BuildType) -> &'static str {
1063 match bt {
1064 BuildType::Autotools => "autotools",
1065 BuildType::CMake => "cmake",
1066 BuildType::Meson => "meson",
1067 BuildType::Cargo => "cargo",
1068 BuildType::Make => "make",
1069 BuildType::Custom => "custom",
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 #[allow(unused_imports)]
1076 use alloc::vec;
1077
1078 use super::*;
1079
1080 #[test]
1083 fn test_build_type_parse() {
1084 assert_eq!(BuildType::parse("autotools"), Some(BuildType::Autotools));
1085 assert_eq!(BuildType::parse("Autotools"), Some(BuildType::Autotools));
1086 assert_eq!(BuildType::parse("cmake"), Some(BuildType::CMake));
1087 assert_eq!(BuildType::parse("CMake"), Some(BuildType::CMake));
1088 assert_eq!(BuildType::parse("CMAKE"), Some(BuildType::CMake));
1089 assert_eq!(BuildType::parse("meson"), Some(BuildType::Meson));
1090 assert_eq!(BuildType::parse("Meson"), Some(BuildType::Meson));
1091 assert_eq!(BuildType::parse("cargo"), Some(BuildType::Cargo));
1092 assert_eq!(BuildType::parse("Cargo"), Some(BuildType::Cargo));
1093 assert_eq!(BuildType::parse("make"), Some(BuildType::Make));
1094 assert_eq!(BuildType::parse("custom"), Some(BuildType::Custom));
1095 assert_eq!(BuildType::parse("unknown"), None);
1096 }
1097
1098 #[test]
1099 fn test_build_type_configure_command() {
1100 assert!(BuildType::Autotools
1101 .configure_command()
1102 .contains("./configure"));
1103 assert!(BuildType::CMake.configure_command().contains("cmake"));
1104 assert!(BuildType::Meson.configure_command().contains("meson"));
1105 assert!(BuildType::Cargo.configure_command().contains("cargo build"));
1106 assert!(BuildType::Make.configure_command().is_empty());
1107 assert!(BuildType::Custom.configure_command().is_empty());
1108 }
1109
1110 #[test]
1111 fn test_build_type_build_command() {
1112 assert!(BuildType::Autotools.build_command().contains("make"));
1113 assert!(BuildType::CMake.build_command().contains("cmake --build"));
1114 assert!(BuildType::Meson.build_command().contains("ninja"));
1115 assert!(BuildType::Cargo.build_command().is_empty());
1116 assert!(BuildType::Make.build_command().contains("make"));
1117 assert!(BuildType::Custom.build_command().is_empty());
1118 }
1119
1120 #[test]
1121 fn test_build_type_install_command() {
1122 assert!(BuildType::Autotools
1123 .install_command()
1124 .contains("make install"));
1125 assert!(BuildType::CMake
1126 .install_command()
1127 .contains("cmake --install"));
1128 assert!(BuildType::Meson.install_command().contains("ninja"));
1129 assert!(BuildType::Cargo.install_command().contains("cargo install"));
1130 assert!(BuildType::Custom.install_command().is_empty());
1131 }
1132
1133 #[test]
1136 fn test_port_new() {
1137 let port = Port::new(String::from("curl"), String::from("8.5.0"));
1138 assert_eq!(port.name, "curl");
1139 assert_eq!(port.version, "8.5.0");
1140 assert!(port.description.is_empty());
1141 assert!(port.homepage.is_empty());
1142 assert!(port.sources.is_empty());
1143 assert!(port.checksums.is_empty());
1144 assert_eq!(port.build_type, BuildType::Make);
1145 assert!(port.build_steps.is_empty());
1146 assert!(port.dependencies.is_empty());
1147 assert_eq!(port.category, "misc");
1148 assert!(port.license.is_empty());
1149 }
1150
1151 #[test]
1154 fn test_build_environment_new() {
1155 let port = Port::new(String::from("curl"), String::from("8.5.0"));
1156 let env = BuildEnvironment::new(&port);
1157 assert!(env.build_root.contains("curl-8.5.0"));
1158 assert!(env.source_dir.contains("/src"));
1159 assert!(env.build_dir.contains("/build"));
1160 assert!(env.pkg_dir.contains("/pkg"));
1161 assert_eq!(env.build_timeout_ms, 300_000);
1162 assert_eq!(env.get_env("PORT_NAME"), Some("curl"));
1163 assert_eq!(env.get_env("PORT_VERSION"), Some("8.5.0"));
1164 }
1165
1166 #[test]
1167 fn test_build_environment_get_set_env() {
1168 let port = Port::new(String::from("test"), String::from("1.0"));
1169 let mut env = BuildEnvironment::new(&port);
1170 assert!(env.get_env("MY_VAR").is_none());
1171 env.set_env(String::from("MY_VAR"), String::from("my_val"));
1172 assert_eq!(env.get_env("MY_VAR"), Some("my_val"));
1173 }
1174
1175 #[test]
1178 fn test_port_manager_new() {
1179 let pm = PortManager::new();
1180 assert_eq!(pm.port_count(), 0);
1181 assert!(pm.list_ports().is_empty());
1182 }
1183
1184 #[test]
1185 fn test_port_manager_register_and_get() {
1186 let mut pm = PortManager::new();
1187 let port = Port::new(String::from("curl"), String::from("8.5.0"));
1188 pm.register_port(port);
1189 assert_eq!(pm.port_count(), 1);
1190 let p = pm.get_port("curl").unwrap();
1191 assert_eq!(p.version, "8.5.0");
1192 }
1193
1194 #[test]
1195 fn test_port_manager_get_nonexistent() {
1196 let pm = PortManager::new();
1197 assert!(pm.get_port("nonexistent").is_none());
1198 }
1199
1200 #[test]
1201 fn test_port_manager_search() {
1202 let mut pm = PortManager::new();
1203 let mut p1 = Port::new(String::from("curl"), String::from("8.5.0"));
1204 p1.description = String::from("URL transfer tool");
1205 pm.register_port(p1);
1206
1207 let mut p2 = Port::new(String::from("wget"), String::from("1.0.0"));
1208 p2.description = String::from("Network downloader");
1209 pm.register_port(p2);
1210
1211 let results = pm.search("curl");
1212 assert_eq!(results.len(), 1);
1213 assert_eq!(results[0].name, "curl");
1214
1215 let results = pm.search("downloader");
1217 assert_eq!(results.len(), 1);
1218 assert_eq!(results[0].name, "wget");
1219
1220 let results = pm.search("CURL");
1222 assert_eq!(results.len(), 1);
1223 }
1224
1225 #[test]
1226 fn test_port_manager_resolve_build_deps_simple() {
1227 let mut pm = PortManager::new();
1228 let mut app = Port::new(String::from("app"), String::from("1.0.0"));
1229 app.dependencies = vec![String::from("lib")];
1230 pm.register_port(app.clone());
1231 pm.register_port(Port::new(String::from("lib"), String::from("1.0.0")));
1232
1233 let deps = pm.resolve_build_deps(&app).unwrap();
1234 assert_eq!(deps.len(), 1);
1235 assert_eq!(deps[0], "lib");
1236 }
1237
1238 #[test]
1239 fn test_port_manager_resolve_build_deps_cycle() {
1240 let mut pm = PortManager::new();
1241 let mut a = Port::new(String::from("a"), String::from("1.0.0"));
1242 a.dependencies = vec![String::from("b")];
1243 let mut b = Port::new(String::from("b"), String::from("1.0.0"));
1244 b.dependencies = vec![String::from("a")];
1245 pm.register_port(a.clone());
1246 pm.register_port(b);
1247
1248 let result = pm.resolve_build_deps(&a);
1249 assert!(result.is_err());
1250 }
1251
1252 #[test]
1253 fn test_port_manager_resolve_build_deps_missing() {
1254 let mut pm = PortManager::new();
1255 let mut app = Port::new(String::from("app"), String::from("1.0.0"));
1256 app.dependencies = vec![String::from("missing-dep")];
1257 pm.register_port(app.clone());
1258
1259 let result = pm.resolve_build_deps(&app);
1260 assert!(result.is_err());
1261 }
1262
1263 #[test]
1266 fn test_parse_portfile_minimal() {
1267 let content = r#"
1268[port]
1269name = "curl"
1270version = "8.5.0"
1271"#;
1272 let port = parse_portfile(content).unwrap();
1273 assert_eq!(port.name, "curl");
1274 assert_eq!(port.version, "8.5.0");
1275 assert_eq!(port.build_type, BuildType::Make); assert_eq!(port.category, "misc"); }
1278
1279 #[test]
1280 fn test_parse_portfile_full() {
1281 let content = r#"
1282[port]
1283name = "curl"
1284version = "8.5.0"
1285description = "URL transfer tool"
1286homepage = "https://curl.se"
1287license = "MIT"
1288category = "net"
1289build_type = "autotools"
1290
1291[dependencies]
1292build = ["openssl", "zlib"]
1293"#;
1294 let port = parse_portfile(content).unwrap();
1295 assert_eq!(port.name, "curl");
1296 assert_eq!(port.version, "8.5.0");
1297 assert_eq!(port.description, "URL transfer tool");
1298 assert_eq!(port.homepage, "https://curl.se");
1299 assert_eq!(port.license, "MIT");
1300 assert_eq!(port.category, "net");
1301 assert_eq!(port.build_type, BuildType::Autotools);
1302 assert_eq!(port.dependencies.len(), 2);
1303 assert_eq!(port.dependencies[0], "openssl");
1304 assert_eq!(port.dependencies[1], "zlib");
1305 }
1306
1307 #[test]
1308 fn test_parse_portfile_missing_port_section() {
1309 let content = r#"
1310[other]
1311name = "test"
1312"#;
1313 assert!(parse_portfile(content).is_err());
1314 }
1315
1316 #[test]
1317 fn test_parse_portfile_missing_name() {
1318 let content = r#"
1319[port]
1320version = "1.0.0"
1321"#;
1322 assert!(parse_portfile(content).is_err());
1323 }
1324
1325 #[test]
1326 fn test_parse_portfile_missing_version() {
1327 let content = r#"
1328[port]
1329name = "test"
1330"#;
1331 assert!(parse_portfile(content).is_err());
1332 }
1333
1334 #[test]
1337 fn test_hex_nibble() {
1338 assert_eq!(hex_nibble(b'0'), 0);
1339 assert_eq!(hex_nibble(b'9'), 9);
1340 assert_eq!(hex_nibble(b'a'), 10);
1341 assert_eq!(hex_nibble(b'f'), 15);
1342 assert_eq!(hex_nibble(b'A'), 10);
1343 assert_eq!(hex_nibble(b'F'), 15);
1344 assert_eq!(hex_nibble(b'z'), 0); }
1346
1347 #[test]
1350 fn test_parse_hex_checksum() {
1351 let hex = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
1352 let result = parse_hex_checksum(hex);
1353 assert_eq!(result[0], 0x01);
1354 assert_eq!(result[15], 0x10);
1355 assert_eq!(result[31], 0x20);
1356 }
1357
1358 #[test]
1359 fn test_parse_hex_checksum_short() {
1360 let hex = "0102";
1361 let result = parse_hex_checksum(hex);
1362 assert_eq!(result[0], 0x01);
1363 assert_eq!(result[1], 0x02);
1364 assert_eq!(result[2], 0x00); }
1366
1367 #[test]
1370 fn test_build_type_label() {
1371 assert_eq!(build_type_label(BuildType::Autotools), "autotools");
1372 assert_eq!(build_type_label(BuildType::CMake), "cmake");
1373 assert_eq!(build_type_label(BuildType::Meson), "meson");
1374 assert_eq!(build_type_label(BuildType::Cargo), "cargo");
1375 assert_eq!(build_type_label(BuildType::Make), "make");
1376 assert_eq!(build_type_label(BuildType::Custom), "custom");
1377 }
1378
1379 #[test]
1382 fn test_parse_port_version() {
1383 let v = parse_port_version("8.5.0");
1384 assert_eq!(v.major, 8);
1385 assert_eq!(v.minor, 5);
1386 assert_eq!(v.patch, 0);
1387 }
1388
1389 #[test]
1390 fn test_parse_port_version_partial() {
1391 let v = parse_port_version("3.1");
1392 assert_eq!(v.major, 3);
1393 assert_eq!(v.minor, 1);
1394 assert_eq!(v.patch, 0);
1395 }
1396}