1use crate::builder::Target;
4use crate::utils::log::{log, LogLevel};
5use serde::Serialize;
6use std::collections::{HashMap, HashSet, VecDeque};
7use std::default::Default;
8use std::fs::File;
9use std::process::Command;
10use std::sync::{Arc, RwLock};
11use std::{io::Read, path::Path};
12use toml::{Table, Value};
13use walkdir::WalkDir;
14
15#[derive(Debug, Clone)]
17pub struct BuildConfig {
18 pub compiler: Arc<RwLock<String>>,
19 pub app: String,
20}
21
22#[derive(Debug, Default, PartialEq, Clone, Serialize)]
24pub struct OSConfig {
25 pub name: String,
26 pub features: Vec<String>,
27 pub ulib: String,
28 pub develop: String,
29 pub platform: PlatformConfig,
30}
31
32#[derive(Debug, Default, PartialEq, Clone, Serialize)]
34pub struct PlatformConfig {
35 pub name: String,
36 pub arch: String,
37 pub cross_compile: String,
38 pub target: String,
39 pub smp: String,
40 pub mode: String,
41 pub log: String,
42 pub v: String,
43 pub qemu: QemuConfig,
44}
45
46#[derive(Debug, Default, PartialEq, Clone, Serialize)]
48pub struct QemuConfig {
49 pub debug: String,
50 pub blk: String,
51 pub net: String,
52 pub mem: String,
53 pub graphic: String,
54 pub bus: String,
55 pub disk_img: String,
56 pub v9p: String,
57 pub v9p_path: String,
58 pub accel: String,
59 pub qemu_log: String,
60 pub net_dump: String,
61 pub net_dev: String,
62 pub ip: String,
63 pub gw: String,
64 pub args: String,
65 pub envs: String,
66}
67
68impl QemuConfig {
69 pub fn config_qemu(
71 &self,
72 platform_config: &PlatformConfig,
73 trgt: &Target,
74 ) -> (Vec<String>, Vec<String>) {
75 let vdev_suffix = match self.bus.as_str() {
77 "mmio" => "device",
78 "pci" => "pci",
79 _ => {
80 log(LogLevel::Error, "BUS must be one of 'mmio' or 'pci'");
81 std::process::exit(1);
82 }
83 };
84 let mut qemu_args = Vec::new();
86 qemu_args.push(format!("qemu-system-{}", platform_config.arch));
87 qemu_args.push("-m".to_string());
89 qemu_args.push(self.mem.to_string());
90 qemu_args.push("-smp".to_string());
91 qemu_args.push(platform_config.smp.clone());
92 match platform_config.arch.as_str() {
94 "x86_64" => {
95 qemu_args.extend(
96 ["-machine", "q35", "-kernel", &trgt.elf_path]
97 .iter()
98 .map(|&arg| arg.to_string()),
99 );
100 }
101 "risc64" => {
102 qemu_args.extend(
103 [
104 "-machine",
105 "virt",
106 "-bios",
107 "default",
108 "-kernel",
109 &trgt.bin_path,
110 ]
111 .iter()
112 .map(|&arg| arg.to_string()),
113 );
114 }
115 "aarch64" => {
116 qemu_args.extend(
117 [
118 "-cpu",
119 "cortex-a72",
120 "-machine",
121 "virt",
122 "-kernel",
123 &trgt.bin_path,
124 ]
125 .iter()
126 .map(|&arg| arg.to_string()),
127 );
128 }
129 _ => {
130 log(LogLevel::Error, "Unsupported architecture");
131 std::process::exit(1);
132 }
133 };
134 qemu_args.push("-append".to_string());
136 qemu_args.push(format!("\";{};{}\"", self.args, self.envs));
137 if self.blk == "y" {
139 qemu_args.push("-device".to_string());
140 qemu_args.push(format!("virtio-blk-{},drive=disk0", vdev_suffix));
141 qemu_args.push("-drive".to_string());
142 qemu_args.push(format!(
143 "id=disk0,if=none,format=raw,file={}",
144 self.disk_img
145 ));
146 }
147 if self.v9p == "y" {
149 qemu_args.push("-fsdev".to_string());
150 qemu_args.push(format!(
151 "local,id=myid,path={},security_model=none",
152 self.v9p_path
153 ));
154 qemu_args.push("-device".to_string());
155 qemu_args.push(format!(
156 "virtio-9p-{},fsdev=myid,mount_tag=rootfs",
157 vdev_suffix
158 ));
159 }
160 if self.net == "y" {
162 qemu_args.push("-device".to_string());
163 qemu_args.push(format!("virtio-net-{},netdev=net0", vdev_suffix));
164 if self.net_dev == "user" {
166 qemu_args.push("-netdev".to_string());
167 qemu_args.push(
168 "user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555".to_string(),
169 );
170 } else if self.net_dev == "tap" {
171 qemu_args.push("-netdev".to_string());
172 qemu_args.push("tap,id=net0,ifname=tap0,script=no,downscript=no".to_string());
173 } else {
174 log(LogLevel::Error, "NET_DEV must be one of 'user' or 'tap'");
175 std::process::exit(1);
176 }
177 if self.net_dump == "y" {
179 qemu_args.push("-object".to_string());
180 qemu_args.push("filter-dump,id=dump0,netdev=net0,file=netdump.pcap".to_string());
181 }
182 }
183 if self.graphic == "y" {
185 qemu_args.push("-device".to_string());
186 qemu_args.push(format!("virtio-gpu-{}", vdev_suffix));
187 qemu_args.push("-vga".to_string());
188 qemu_args.push("none".to_string());
189 qemu_args.push("-serial".to_string());
190 qemu_args.push("mon:stdio".to_string());
191 } else if self.graphic == "n" {
192 qemu_args.push("-nographic".to_string());
193 }
194 if self.qemu_log == "y" {
196 qemu_args.push("-D".to_string());
197 qemu_args.push("qemu.log".to_string());
198 qemu_args.push("-d".to_string());
199 qemu_args.push("in_asm,int,mmu,pcall,cpu_reset,guest_errors".to_string());
200 }
201 let mut qemu_args_debug = Vec::new();
203 qemu_args_debug.extend(qemu_args.clone());
204 qemu_args_debug.push("-s".to_string());
205 qemu_args_debug.push("-S".to_string());
206 if self.accel == "y" {
208 if cfg!(target_os = "darwin") {
209 qemu_args.push("-cpu".to_string());
210 qemu_args.push("host".to_string());
211 qemu_args.push("-accel".to_string());
212 qemu_args.push("hvf".to_string());
213 } else {
214 qemu_args.push("-cpu".to_string());
215 qemu_args.push("host".to_string());
216 qemu_args.push("-accel".to_string());
217 qemu_args.push("kvm".to_string());
218 }
219 }
220
221 (qemu_args, qemu_args_debug)
222 }
223}
224
225#[derive(Debug, Clone)]
227pub struct TargetConfig {
228 pub name: String,
229 pub src: String,
230 pub src_only: Vec<String>,
231 pub src_exclude: Vec<String>,
232 pub include_dir: Vec<String>,
233 pub typ: String,
234 pub cflags: String,
235 pub archive: String,
236 pub linker: String,
237 pub ldflags: String,
238 pub deps: Vec<String>,
239}
240
241impl TargetConfig {
242 fn get_src_names(&self, tgt_path: &str) -> Vec<String> {
246 let mut src_names = Vec::new();
247 let src_path = Path::new(tgt_path);
248
249 let walker = WalkDir::new(src_path)
250 .into_iter()
251 .filter_entry(|e| self.should_include(e.path()) && !self.should_exclude(e.path()));
252 for entry in walker.filter_map(|e| e.ok()) {
253 let path = entry.path();
254 if path.is_file() {
255 if let Some(ext) = path.extension() {
256 if ext == "cpp" || ext == "c" {
257 if let Some(file_path_str) = path.to_str() {
258 #[cfg(target_os = "windows")]
259 let formatted_path_str = file_path_str.replace('\\', "/");
260 #[cfg(target_os = "linux")]
261 let formatted_path_str = file_path_str.to_string();
262 src_names.push(formatted_path_str);
263 }
264 }
265 }
266 }
267 }
268
269 src_names
270 }
271
272 fn should_exclude(&self, path: &Path) -> bool {
274 self.src_exclude
275 .iter()
276 .any(|excluded| path.to_str().map_or(false, |p| p.contains(excluded)))
277 }
278
279 fn should_include(&self, path: &Path) -> bool {
281 if self.src_only.is_empty() {
282 return true;
283 }
284 self.src_only
285 .iter()
286 .any(|included| path.to_str().map_or(false, |p| p.contains(included)))
287 }
288
289 fn check_duplicate_srcs(&self) {
291 let mut src_file_names = self.get_src_names(&self.src);
292 src_file_names.sort_unstable();
293 src_file_names.dedup();
294 let mut last_name: Option<String> = None;
295 let mut duplicates = Vec::new();
296
297 for file_name in &src_file_names {
298 if let Some(ref last) = last_name {
299 if last == file_name {
300 duplicates.push(file_name.clone());
301 }
302 }
303 last_name = Some(file_name.clone());
304 }
305 if !duplicates.is_empty() {
306 log(
307 LogLevel::Error,
308 &format!("Duplicate source files found for target: {}", self.name),
309 );
310 log(LogLevel::Error, "Source files must be unique");
311 for duplicate in duplicates {
312 log(LogLevel::Error, &format!("Duplicate file: {}", duplicate));
313 }
314 std::process::exit(1);
315 }
316 }
317
318 fn arrange_targets(targets: Vec<TargetConfig>) -> Vec<TargetConfig> {
321 let mut target_map: HashMap<String, TargetConfig> = targets
323 .into_iter()
324 .map(|target| (target.name.clone(), target))
325 .collect();
326
327 let mut graph: HashMap<String, Vec<String>> = HashMap::new();
329 let mut in_degree: HashMap<String, usize> = HashMap::new();
330 for name in target_map.keys() {
331 graph.entry(name.clone()).or_default();
332 in_degree.entry(name.clone()).or_insert(0);
333 }
334
335 for target in target_map.values() {
337 for dep in &target.deps {
338 if let Some(deps) = graph.get_mut(dep) {
339 deps.push(target.name.clone());
340 *in_degree.entry(target.name.clone()).or_insert(0) += 1;
341 }
342 }
343 }
344
345 let mut queue: VecDeque<String> = VecDeque::new();
347 for (name, °ree) in &in_degree {
348 if degree == 0 {
349 queue.push_back(name.clone());
350 }
351 }
352 let mut sorted_names = Vec::new();
353 while let Some(name) = queue.pop_front() {
354 sorted_names.push(name.clone());
355 if let Some(deps) = graph.get(&name) {
356 for dep in deps {
357 let degree = in_degree.entry(dep.clone()).or_default();
358 *degree -= 1;
359 if *degree == 0 {
360 queue.push_back(dep.clone());
361 }
362 }
363 }
364 }
365
366 if sorted_names.len() != target_map.len() {
368 log(LogLevel::Error, "Circular dependency detected");
369 std::process::exit(1);
370 }
371
372 sorted_names
374 .into_iter()
375 .map(|name| target_map.remove(&name).unwrap())
376 .collect()
377 }
378}
379
380pub fn parse_config(path: &str, check_dup_src: bool) -> (BuildConfig, OSConfig, Vec<TargetConfig>) {
385 let mut file = File::open(path).unwrap_or_else(|_| {
387 log(
388 LogLevel::Error,
389 &format!("Could not open config file: {}", path),
390 );
391 std::process::exit(1);
392 });
393 let mut contents = String::new();
394 file.read_to_string(&mut contents).unwrap_or_else(|_| {
395 log(
396 LogLevel::Error,
397 &format!("Could not read config file: {}", path),
398 );
399 std::process::exit(1);
400 });
401 let config = contents.parse::<Table>().unwrap_or_else(|e| {
402 log(
403 LogLevel::Error,
404 &format!("Could not parse config file: {}", path),
405 );
406 log(LogLevel::Error, &format!("Error: {}", e));
407 std::process::exit(1);
408 });
409
410 let build_config = parse_build_config(&config);
411 let os_config = parse_os_config(&config, &build_config);
412 let targets = parse_targets(&config, &build_config, check_dup_src);
413
414 (build_config, os_config, targets)
415}
416
417fn parse_build_config(config: &Table) -> BuildConfig {
419 let build = config["build"].as_table().unwrap_or_else(|| {
420 log(LogLevel::Error, "Could not find build in config file");
421 std::process::exit(1);
422 });
423 let compiler = Arc::new(RwLock::new(parse_cfg_string(build, "compiler", "")));
424 let app = parse_cfg_string(build, "app", "");
425
426 BuildConfig { compiler, app }
427}
428
429fn parse_os_config(config: &Table, build_config: &BuildConfig) -> OSConfig {
431 let empty_os = Value::Table(toml::map::Map::default());
432 let os = config.get("os").unwrap_or(&empty_os);
433 let os_config: OSConfig;
434 if os != &empty_os {
435 if let Some(os_table) = os.as_table() {
436 let name = parse_cfg_string(os_table, "name", "");
437 let ulib = parse_cfg_string(os_table, "ulib", "");
438 let develop = parse_cfg_string(os_table, "develop", "n");
439 let mut features = parse_cfg_vector(os_table, "services");
440 if features.iter().any(|feat| {
441 feat == "fs"
442 || feat == "net"
443 || feat == "pipe"
444 || feat == "select"
445 || feat == "poll"
446 || feat == "epoll"
447 }) {
448 features.push("fd".to_string());
449 }
450 if ulib == "ruxmusl" {
451 features.push("musl".to_string());
452 features.push("fp_simd".to_string());
453 features.push("fd".to_string());
454 features.push("tls".to_string());
455 }
456 let platform = parse_platform(os_table);
458 let current_compiler = build_config.compiler.read().unwrap();
459 let new_compiler = format!("{}{}", platform.cross_compile, *current_compiler);
460 drop(current_compiler);
461 *build_config.compiler.write().unwrap() = new_compiler;
462 os_config = OSConfig {
463 name,
464 features,
465 ulib,
466 develop,
467 platform,
468 };
469 } else {
470 log(LogLevel::Error, "OS is not a table");
471 std::process::exit(1);
472 }
473 } else {
474 os_config = OSConfig::default();
475 }
476
477 os_config
478}
479
480fn parse_targets(
482 config: &Table,
483 build_config: &BuildConfig,
484 check_dup_src: bool,
485) -> Vec<TargetConfig> {
486 let mut tgts = Vec::new();
487 let targets = config.get("targets").and_then(|v| v.as_array());
488 if targets.is_none() && build_config.app.is_empty() {
489 log(
490 LogLevel::Error,
491 "Could not find targets or app in config file",
492 );
493 std::process::exit(1);
494 }
495 if let Some(targets) = targets {
496 for target in targets {
497 let target_tb = target.as_table().unwrap_or_else(|| {
498 log(LogLevel::Error, "Target is not a table");
499 std::process::exit(1);
500 });
501 let include_dir = if let Some(value) = target_tb.get("include_dir") {
503 match value {
504 Value::String(_s) => vec![parse_cfg_string(target_tb, "include_dir", "./")],
505 Value::Array(_arr) => parse_cfg_vector(target_tb, "include_dir"),
506 _ => {
507 log(LogLevel::Error, "Invalid include_dir field");
508 std::process::exit(1);
509 }
510 }
511 } else {
512 vec!["./".to_owned()]
513 };
514 let target_config = TargetConfig {
515 name: parse_cfg_string(target_tb, "name", ""),
516 src: parse_cfg_string(target_tb, "src", ""),
517 src_only: parse_cfg_vector(target_tb, "src_only"),
518 src_exclude: parse_cfg_vector(target_tb, "src_exclude"),
519 include_dir,
520 typ: parse_cfg_string(target_tb, "type", ""),
521 cflags: parse_cfg_string(target_tb, "cflags", ""),
522 archive: parse_cfg_string(target_tb, "archive", ""),
523 linker: parse_cfg_string(target_tb, "linker", ""),
524 ldflags: parse_cfg_string(target_tb, "ldflags", ""),
525 deps: parse_cfg_vector(target_tb, "deps"),
526 };
527 if target_config.typ != "exe"
528 && target_config.typ != "dll"
529 && target_config.typ != "static"
530 && target_config.typ != "object"
531 {
532 log(LogLevel::Error, "Type must be exe, dll, object or static");
533 std::process::exit(1);
534 }
535 tgts.push(target_config);
536 }
537 }
538
539 if tgts.is_empty() && build_config.app.is_empty() {
540 log(LogLevel::Error, "No targets found!");
541 std::process::exit(1);
542 }
543
544 let mut names_set = HashSet::new();
546 for target in &tgts {
547 if !names_set.insert(&target.name) {
548 log(
549 LogLevel::Error,
550 &format!("Duplicate target names found: {}", target.name),
551 );
552 std::process::exit(1);
553 }
554 }
555
556 if check_dup_src {
558 log(
559 LogLevel::Info,
560 "Checking for duplicate srcs in all targets...",
561 );
562 for target in &tgts {
563 target.check_duplicate_srcs();
564 }
565 }
566
567 TargetConfig::arrange_targets(tgts)
568}
569
570fn parse_platform(config: &Table) -> PlatformConfig {
572 let empty_platform = Value::Table(toml::map::Map::default());
573 let platform = config.get("platform").unwrap_or(&empty_platform);
574 if let Some(platform_table) = platform.as_table() {
575 let name = parse_cfg_string(platform_table, "name", "x86_64-qemu-q35");
576 let arch = name.split('-').next().unwrap_or("x86_64").to_string();
577 let cross_compile = format!("{}-linux-musl-", arch);
578 let target = match &arch[..] {
579 "x86_64" => "x86_64-unknown-none".to_string(),
580 "riscv64" => "riscv64gc-unknown-none-elf".to_string(),
581 "aarch64" => "aarch64-unknown-none-softfloat".to_string(),
582 _ => {
583 log(
584 LogLevel::Error,
585 "\"ARCH\" must be one of \"x86_64\", \"riscv64\", or \"aarch64\"",
586 );
587 std::process::exit(1);
588 }
589 };
590 let smp = parse_cfg_string(platform_table, "smp", "1");
591 let mode = parse_cfg_string(platform_table, "mode", "");
592 let log = parse_cfg_string(platform_table, "log", "warn");
593 let v = parse_cfg_string(platform_table, "v", "");
594 let qemu: QemuConfig = if name.split('-').any(|s| s == "qemu") {
596 parse_qemu(&arch, platform_table)
597 } else {
598 QemuConfig::default()
599 };
600 PlatformConfig {
601 name,
602 arch,
603 cross_compile,
604 target,
605 smp,
606 mode,
607 log,
608 v,
609 qemu,
610 }
611 } else {
612 log(LogLevel::Error, "Platform is not a table");
613 std::process::exit(1);
614 }
615}
616
617fn parse_qemu(arch: &str, config: &Table) -> QemuConfig {
619 let empty_qemu = Value::Table(toml::map::Map::default());
620 let qemu = config.get("qemu").unwrap_or(&empty_qemu);
621 if let Some(qemu_table) = qemu.as_table() {
622 let debug = parse_cfg_string(qemu_table, "debug", "n");
623 let blk = parse_cfg_string(qemu_table, "blk", "n");
624 let net = parse_cfg_string(qemu_table, "net", "n");
625 let mem = parse_cfg_string(qemu_table, "memory", "128M");
626 let graphic = parse_cfg_string(qemu_table, "graphic", "n");
627 let bus = match arch {
628 "x86_64" => "pci".to_string(),
629 _ => "mmio".to_string(),
630 };
631 let disk_img = parse_cfg_string(qemu_table, "disk_img", "disk.img");
632 let v9p = parse_cfg_string(qemu_table, "v9p", "n");
633 let v9p_path = parse_cfg_string(qemu_table, "v9p_path", "./");
634 let accel_pre = match Command::new("uname").arg("-r").output() {
635 Ok(output) => {
636 let kernel_version = String::from_utf8_lossy(&output.stdout).to_lowercase();
637 if kernel_version.contains("-microsoft") {
638 "n"
639 } else {
640 "y"
641 }
642 }
643 Err(_) => {
644 log(LogLevel::Error, "Failed to execute command");
645 std::process::exit(1);
646 }
647 };
648 let accel = match arch {
649 "x86_64" => accel_pre.to_string(),
650 _ => "n".to_string(),
651 };
652 let qemu_log = parse_cfg_string(qemu_table, "qemu_log", "n");
653 let net_dump = parse_cfg_string(qemu_table, "net_dump", "n");
654 let net_dev = parse_cfg_string(qemu_table, "net_dev", "user");
655 let ip = parse_cfg_string(qemu_table, "ip", "10.0.2.15");
656 let gw = parse_cfg_string(qemu_table, "gw", "10.0.2.2");
657 let args = parse_cfg_string(qemu_table, "args", "");
658 let envs = parse_cfg_string(qemu_table, "envs", "");
659 QemuConfig {
660 debug,
661 blk,
662 net,
663 mem,
664 graphic,
665 bus,
666 disk_img,
667 v9p,
668 v9p_path,
669 accel,
670 qemu_log,
671 net_dump,
672 net_dev,
673 ip,
674 gw,
675 args,
676 envs,
677 }
678 } else {
679 log(LogLevel::Error, "Qemu is not a table");
680 std::process::exit(1);
681 }
682}
683
684fn parse_cfg_string(config: &Table, field: &str, default: &str) -> String {
686 let default_string = Value::String(default.to_string());
687 config
688 .get(field)
689 .unwrap_or(&default_string)
690 .as_str()
691 .unwrap_or_else(|| {
692 log(LogLevel::Error, &format!("{} is not a string", field));
693 std::process::exit(1);
694 })
695 .to_string()
696}
697
698fn parse_cfg_vector(config: &Table, field: &str) -> Vec<String> {
700 let empty_vector = Value::Array(Vec::new());
701 config
702 .get(field)
703 .unwrap_or(&empty_vector)
704 .as_array()
705 .unwrap_or_else(|| {
706 log(LogLevel::Error, &format!("{} is not an array", field));
707 std::process::exit(1);
708 })
709 .iter()
710 .map(|value| {
711 value
712 .as_str()
713 .unwrap_or_else(|| {
714 log(LogLevel::Error, &format!("{} elements are strings", field));
715 std::process::exit(1);
716 })
717 .to_string()
718 })
719 .collect()
720}