From b03e2f3e9a47a29cfb1247bda609d37ed118b7fb Mon Sep 17 00:00:00 2001 From: Ybeichen Date: Sat, 2 Mar 2024 15:35:14 +0800 Subject: [PATCH] Refactor utils and remove packages from build module --- Cargo.lock | 2 +- Cargo.toml | 2 +- doc/ruxgo_book/src/guide/builder_module.md | 6 +- src/builder.rs | 58 +- src/commands.rs | 86 +-- src/global_cfg.rs | 18 +- src/hasher.rs | 2 +- src/lib.rs | 4 +- src/main.rs | 43 +- src/packages.rs | 2 +- src/parser.rs | 582 +++++++++++++++ src/utils.rs | 810 +-------------------- src/utils/env.rs | 30 + src/{ => utils}/features.rs | 5 +- src/utils/log.rs | 70 ++ 15 files changed, 755 insertions(+), 965 deletions(-) create mode 100644 src/parser.rs create mode 100644 src/utils/env.rs rename src/{ => utils}/features.rs (96%) create mode 100644 src/utils/log.rs diff --git a/Cargo.lock b/Cargo.lock index c77e07a..f31594f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1007,7 +1007,7 @@ dependencies = [ [[package]] name = "ruxgo" -version = "0.8.9" +version = "0.9.0" dependencies = [ "bytes", "clap", diff --git a/Cargo.toml b/Cargo.toml index 65830c8..34c666a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruxgo" -version = "0.8.9" +version = "0.9.0" edition = "2021" authors = ["Zewei Yang "] description = "A Cargo-like build tool for building C and C++ applications" diff --git a/doc/ruxgo_book/src/guide/builder_module.md b/doc/ruxgo_book/src/guide/builder_module.md index 6d014ef..b97aadb 100644 --- a/doc/ruxgo_book/src/guide/builder_module.md +++ b/doc/ruxgo_book/src/guide/builder_module.md @@ -1,7 +1,5 @@ # builder 模块 -**[build]** 模块描述了编译器的类型和所需远程库包。它包含两个部分: `compiler` 和 `packages`。 +**[build]** 模块描述了编译器的类型。它包含 `compiler`。 -- `compiler`: 指定编译器类型,例如: "gcc"。 - -- `packages`: 可选。主要用于从 Github 中获取应用的源代码,然后通过解析其中的 `config_linux.toml`文件来获取所需的库。当使用包时,你需要指定远程仓库和分支。 \ No newline at end of file +- `compiler`: 指定编译器类型,例如: "gcc"。 \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index bd27e9a..028e18a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,7 +1,8 @@ //! This module contains the build related functions -use crate::features::cfg_feat; -use crate::utils::{BuildConfig, TargetConfig, Package, log, LogLevel, OSConfig}; +use crate::utils::features::cfg_feat; +use crate::parser::{BuildConfig, TargetConfig, OSConfig}; +use crate::utils::log::{log, LogLevel}; use std::path::{Path, PathBuf}; use std::io::{Read, Write}; use std::fs; @@ -62,7 +63,6 @@ pub struct Target<'a> { hash_file_path: String, path_hash: HashMap, dependant_libs: Vec>, - packages: &'a Vec, } /// Represents a source file (A single C or Cpp file) @@ -81,13 +81,11 @@ impl<'a> Target<'a> { /// * `build_config` - Build config /// * `target_config` - Target config /// * `targets` - All targets - /// * `packages` - All packages pub fn new ( build_config: &'a BuildConfig, os_config: &'a OSConfig, target_config: &'a TargetConfig, - targets: &'a Vec, - packages: &'a Vec + targets: &'a Vec ) -> Self { let srcs = Vec::new(); let dependant_includes: HashMap> = HashMap::new(); @@ -122,14 +120,7 @@ impl<'a> Target<'a> { for dependant_lib in &target_config.deps { for target in targets { if target.name == *dependant_lib { - dependant_libs.push(Target::new(build_config, os_config, target, targets, packages)); - } - } - for pkg in packages { - for target in &pkg.target_configs { - if target.name == *dependant_lib { - dependant_libs.push(Target::new(&pkg.build_config, os_config, target, &pkg.target_configs, &pkg.sub_packages)); - } + dependant_libs.push(Target::new(build_config, os_config, target, targets)); } } } @@ -137,14 +128,12 @@ impl<'a> Target<'a> { // check types of the dependant libs for dep_lib in &dependant_libs { if dep_lib.target_config.typ != "dll" && dep_lib.target_config.typ != "static" && dep_lib.target_config.typ != "object" { - log(LogLevel::Error, "Can add only dlls, static or object libraries as dependant libs"); + log(LogLevel::Error, "Can add only dll, static or object libs as dependant libs"); log(LogLevel::Error, &format!("Target: {} is not a dll, static or object library", dep_lib.target_config.name)); log(LogLevel::Error, &format!("Target: {} is a {}", dep_lib.target_config.name, dep_lib.target_config.typ)); std::process::exit(1); } - else { - log(LogLevel::Info, &format!("Adding dependant lib: {}", dep_lib.target_config.name)); - } + log(LogLevel::Info, &format!("Adding dependant lib: {}", dep_lib.target_config.name)); if !dep_lib.target_config.name.starts_with("lib") { log(LogLevel::Error, "Dependant lib name must start with lib"); log(LogLevel::Error, &format!("Target: {} does not start with lib", dep_lib.target_config.name)); @@ -154,12 +143,7 @@ impl<'a> Target<'a> { if target_config.deps.len() > dependant_libs.len() { log(LogLevel::Error, "Dependant libs not found!"); log(LogLevel::Error, &format!("Dependant libs: {:?}", target_config.deps)); - let mut targets_pkg = Vec::new(); - for pkg in packages { - targets_pkg.extend(pkg.target_configs.clone()); - } - let targets_all = targets.iter().chain(targets_pkg.iter()); - log(LogLevel::Error, &format!("Found libs: {:?}", targets_all.map(|x| { + log(LogLevel::Error, &format!("Found libs: {:?}", targets.iter().map(|x| { if x.typ == "dll" || x.typ == "static" || x.typ == "object" { x.name.clone() } else { @@ -179,7 +163,6 @@ impl<'a> Target<'a> { path_hash, hash_file_path, dependant_libs, - packages, }; target.get_srcs(&target_config.src); target @@ -188,21 +171,12 @@ impl<'a> Target<'a> { /// Builds the target /// # Arguments /// * `gen_cc` - Generate compile_commands.json + /// * `relink` - Determine whether to re-link pub fn build(&mut self, gen_cc: bool, relink: bool) { - // build other lib targets of packages firstly - for pkg in self.packages { - for target in &pkg.target_configs { - if target.typ == "dll" || target.typ == "static" || target.typ == "object" { - // If the root target(exe target) adds os_config, the pkg_tgt also adds os_config - let mut pkg_tgt = Target::new(&pkg.build_config, &self.os_config, target, &pkg.target_configs, &pkg.sub_packages); - pkg_tgt.build(gen_cc, relink); - } - } - } let mut to_link: bool = false; // if the source file needs to be build, then to link - let mut link_causer: Vec<&str> = Vec::new(); // trace the linked source files + let mut link_causer: Vec<&str> = Vec::new(); let mut srcs_needed = 0; let total_srcs = self.srcs.len(); let mut src_ccs = Vec::new(); @@ -266,7 +240,7 @@ impl<'a> Target<'a> { return; } - // Parallel built + // parallel built let progress_bar = Arc::new(Mutex::new(ProgressBar::new(srcs_needed as u64))); let num_complete = Arc::new(Mutex::new(0)); let src_hash_to_update = Arc::new(Mutex::new(Vec::new())); @@ -306,7 +280,7 @@ impl<'a> Target<'a> { Hasher::save_hash(&src.path, &mut self.path_hash); } - // Link target + // links the target if to_link { for src in link_causer { log(LogLevel::Info, &format!("\tLinking file: {}", &src)); @@ -592,14 +566,6 @@ impl<'a> Target<'a> { cc.push_str(include); }); } - for pack in self.packages { - for tgtg in &pack.target_configs { - tgtg.include_dir.iter().for_each(|include| { - cc.push_str(" -I"); - cc.push_str(include); - }); - } - } cc.push_str(" "); let cflags = &self.target_config.cflags; diff --git a/src/commands.rs b/src/commands.rs index d3d4796..ceec599 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,8 +2,10 @@ use crate::builder::Target; use crate::global_cfg::GlobalConfig; -use crate::utils::{self, BuildConfig, TargetConfig, OSConfig, QemuConfig, Package, log, LogLevel}; -use crate::features; +use crate::parser::{self, BuildConfig, TargetConfig, OSConfig, QemuConfig}; +use crate::utils::log::{log, LogLevel}; +use crate::utils::env; +use crate::utils::features; use std::path::Path; use std::io::Write; use std::fs; @@ -17,7 +19,6 @@ static OBJ_DIR: &str = "ruxgo_bld/obj_win32"; #[cfg(target_os = "linux")] static OBJ_DIR: &str = "ruxgo_bld/obj_linux"; static TARGET_DIR: &str = "ruxgo_bld/target"; -static PACKAGES_DIR: &str = "ruxgo_bld/packages"; // OSConfig hash file static OSCONFIG_HASH_FILE: &str = "ruxgo_bld/os_config.hash"; @@ -56,9 +57,8 @@ lazy_static! { /// # Arguments /// * `targets` - A vector of targets to clean /// * `os_config` - The local os configuration -/// * `packages` - A vector of packages to clean /// * `choices` - A vector of choices to select which components to delete -pub fn clean(targets: &Vec, os_config: &OSConfig, packages: &Vec, choices: Vec) { +pub fn clean(targets: &Vec, os_config: &OSConfig, choices: Vec) { // Helper function to remove a directory or a file and log the result let remove_dir = |dir_path: &str| { if Path::new(dir_path).exists() { @@ -133,32 +133,6 @@ pub fn clean(targets: &Vec, os_config: &OSConfig, packages: &Vec

bin_name.push_str(".dll"), - _ => (), - } - #[cfg(target_os = "linux")] - match target.typ.as_str() { - "dll" => bin_name.push_str(".so"), - "static" => bin_name.push_str(".a"), - "object" => bin_name.push_str(".o"), - _ => (), - } - remove_file(&bin_name); - } - } - } } // Removes obj if choices includes "Obj" or choices includes "All" @@ -166,11 +140,6 @@ pub fn clean(targets: &Vec, os_config: &OSConfig, packages: &Vec

, os_config: &OSConfig, packages: &Vec

, os_config: &OSConfig, gen_cc: bool, gen_vsc: bool, - packages: &Vec ) { if !Path::new(BUILD_DIR).exists() { fs::create_dir(BUILD_DIR).unwrap_or_else(|why| { @@ -224,12 +191,7 @@ pub fn build( std::process::exit(1); }); - let mut inc_dirs: Vec = targets.iter().flat_map(|t| t.include_dir.clone()).collect(); - for package in packages { - for target in &package.target_configs { - inc_dirs.extend(target.include_dir.clone()); - } - } + let inc_dirs: Vec = targets.iter().flat_map(|t| t.include_dir.clone()).collect(); let compiler_path: String = build_config.compiler.read().unwrap().clone(); let mut intellimode: String = String::new(); if compiler_path == "gcc" || compiler_path == "g++" { @@ -354,7 +316,7 @@ pub fn build( // Constructs each target separately based on the os_config changes. for target in targets { - let mut tgt = Target::new(build_config, os_config, target, targets, packages); + let mut tgt = Target::new(build_config, os_config, target, targets); let needs_relink = config_changed && target.typ == "exe"; tgt.build(gen_cc, needs_relink); @@ -454,8 +416,7 @@ fn build_ruxlibc(build_config: &BuildConfig, os_config: &OSConfig, gen_cc: bool) deps: Vec::new(), }; let ulib_targets = Vec::new(); - let ulib_packages = Vec::new(); - let mut tgt = Target::new(build_config, os_config, &ulib_tgt, &ulib_targets, &ulib_packages); + let mut tgt = Target::new(build_config, os_config, &ulib_tgt, &ulib_targets); tgt.build(gen_cc, false); } @@ -538,16 +499,14 @@ fn build_ruxmusl(build_config: &BuildConfig, os_config: &OSConfig) { /// * `build_config` - The local build configuration /// * `exe_target` - The exe target to run /// * `targets` - A vector of targets -/// * `packages` - A vector of packages pub fn run ( bin_args: Option>, build_config: &BuildConfig, os_config: &OSConfig, exe_target: &TargetConfig, - targets: &Vec, - packages: &Vec + targets: &Vec ) { - let trgt = Target::new(build_config, os_config, exe_target, targets, packages); + let trgt = Target::new(build_config, os_config, exe_target, targets); if !Path::new(&trgt.bin_path).exists() { log(LogLevel::Error, &format!("Could not find binary: {}", &trgt.bin_path)); std::process::exit(1); @@ -894,9 +853,9 @@ pub fn init_project(project_name: &str, is_c: Option, config: &GlobalConfi } /// Parses the config file of local project -pub fn parse_config() -> (BuildConfig, OSConfig, Vec, Vec) { +pub fn parse_config() -> (BuildConfig, OSConfig, Vec) { #[cfg(target_os = "linux")] - let (build_config, os_config, targets) = utils::parse_config("./config_linux.toml", true); + let (build_config, os_config, targets) = parser::parse_config("./config_linux.toml", true); #[cfg(target_os = "windows")] let (build_config, os_config, targets) = utils::parse_config("./config_win32.toml", true); @@ -905,13 +864,11 @@ pub fn parse_config() -> (BuildConfig, OSConfig, Vec, Vec if targets.is_empty() { log(LogLevel::Error, "No targets in config"); std::process::exit(1); - } else { - // Allow only one exe and set it as the exe_target - for target in &targets { - if target.typ == "exe" { - num_exe += 1; - exe_target = Some(target); - } + } + for target in &targets { + if target.typ == "exe" { + num_exe += 1; + exe_target = Some(target); } } if num_exe != 1 || exe_target.is_none() { @@ -919,15 +876,10 @@ pub fn parse_config() -> (BuildConfig, OSConfig, Vec, Vec std::process::exit(1); } - #[cfg(target_os = "linux")] - let packages = Package::parse_packages("./config_linux.toml"); - #[cfg(target_os = "windows")] - let packages = Package::parse_packages("./config_win32.toml"); - // Add environment config - utils::config_env(&os_config); + env::config_env(&os_config); - (build_config, os_config, targets, packages) + (build_config, os_config, targets) } pub fn pre_gen_cc() { diff --git a/src/global_cfg.rs b/src/global_cfg.rs index 6e721d7..4b74326 100644 --- a/src/global_cfg.rs +++ b/src/global_cfg.rs @@ -1,4 +1,4 @@ -use crate::utils; +use crate::utils::log::{log, LogLevel}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -39,8 +39,8 @@ impl GlobalConfig { if value == "gcc" || value == "clang" { set_config_param(ConfigParam::DefaultCompiler(value.to_string()), config); } else { - utils::log( - utils::LogLevel::Error, + log( + LogLevel::Error, "Invalid compiler. See `ruxgo config --help` for more info", ); std::process::exit(1); @@ -50,8 +50,8 @@ impl GlobalConfig { if value == "c" || value == "cpp" { set_config_param(ConfigParam::DefaultLanguage(value.to_string()), config); } else { - utils::log( - utils::LogLevel::Error, + log( + LogLevel::Error, "Invalid language. See `ruxgo config --help` for more info", ); std::process::exit(1); @@ -62,16 +62,16 @@ impl GlobalConfig { let value = std::fs::read_to_string(value).unwrap(); set_config_param(ConfigParam::License(value), config); } else { - utils::log( - utils::LogLevel::Error, + log( + LogLevel::Error, "Invalid license file. See `ruxgo config --help` for more info", ); std::process::exit(1); } } _ => { - utils::log( - utils::LogLevel::Error, + log( + LogLevel::Error, "Invalid parameter. See `ruxgo config --help` for more info", ); std::process::exit(1); diff --git a/src/hasher.rs b/src/hasher.rs index f836f0f..c12b143 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -6,7 +6,7 @@ use std::cmp::min; use std::path::Path; use std::collections::HashMap; use sha1::{Sha1, Digest}; -use crate::utils::{log, LogLevel}; +use crate::utils::log::{log, LogLevel}; const CHUNK_SIZE: usize = 1024 * 1024; // 1MB: read files in chunks for efficiency diff --git a/src/lib.rs b/src/lib.rs index bf313e1..340bcd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,12 +14,12 @@ pub mod commands; pub mod utils; /// Contains hashing related functions pub mod hasher; -/// Contains features related functions -pub mod features; /// Handles global config pub mod global_cfg; /// Contains packages management related functions pub mod packages; +/// Contains parse related functions +pub mod parser; #[macro_use] extern crate lazy_static; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2c86167..80e800d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ -use ruxgo::utils::OSConfig; -use ruxgo::{utils, commands}; +use ruxgo::parser::OSConfig; +use ruxgo::utils::log::{log, LogLevel}; +use ruxgo::commands; use clap::{Parser, Subcommand}; use directories::ProjectDirs; use ruxgo::global_cfg::GlobalConfig; @@ -23,7 +24,7 @@ struct CLIArgs { /// Run the executable #[arg(short, long)] run: bool, - /// Initialize a new project. See `init --help` for more info + /// Define subcommand #[command(subcommand)] commands: Option, /// Path argument to pass to switch to the specified directory @@ -128,10 +129,7 @@ license = "NONE" match args.commands { Some(Commands::Init { name, c, cpp }) => { if c && cpp { - utils::log( - utils::LogLevel::Error, - "Only one of --c or --cpp can be specified", - ); + log(LogLevel::Error, "Only one of --c or --cpp can be specified"); std::process::exit(1); } if !c && !cpp { @@ -172,7 +170,7 @@ license = "NONE" .iter() .map(|&index| String::from(items[index])) .collect(); - utils::log(utils::LogLevel::Log, "Cleaning packages..."); + log(LogLevel::Log, "Cleaning packages..."); packages::clean_all_packages(choices).await.expect("Failed to clean choice packages"); } } @@ -180,14 +178,11 @@ license = "NONE" let parameter = parameter.as_str(); let value = value.as_str(); GlobalConfig::set_defaults(&config, parameter, value); - utils::log( - utils::LogLevel::Log, - format!("Setting {} to {}", parameter, value).as_str(), - ); + log(LogLevel::Log, format!("Setting {} to {}", parameter, value).as_str()); std::process::exit(0); } None => { - utils::log(utils::LogLevel::Error, "Rust is broken"); + log(LogLevel::Error, "Rust is broken"); std::process::exit(1); } } @@ -207,7 +202,7 @@ license = "NONE" // If clean flag is provided, prompt user for choices if args.clean { - let (_, os_config, targets, packages) = commands::parse_config(); + let (_, os_config, targets) = commands::parse_config(); let mut items = vec!["All", "App_bins", "Obj"]; if os_config != OSConfig::default() { items.push("OS"); @@ -215,9 +210,6 @@ license = "NONE" items.push("Ulib"); } } - if !packages.is_empty() { - items.push("Packages"); - } let defaults = vec![false; items.len()]; let choices = MultiSelect::new() .with_prompt("What parts do you want to clean?") @@ -230,25 +222,24 @@ license = "NONE" .map(|&index| String::from(items[index])) .collect(); - utils::log(utils::LogLevel::Log, "Cleaning..."); - commands::clean(&targets, &os_config, &packages, choices); + log(LogLevel::Log, "Cleaning..."); + commands::clean(&targets, &os_config, choices); } if args.build { - let (build_config, os_config, targets, packages) = commands::parse_config(); - utils::log(utils::LogLevel::Log, "Building..."); - commands::build(&build_config, &targets, &os_config, gen_cc, gen_vsc, &packages); + let (build_config, os_config, targets) = commands::parse_config(); + log(LogLevel::Log, "Building..."); + commands::build(&build_config, &targets, &os_config, gen_cc, gen_vsc); } if args.run { - let (build_config, os_config, targets, packages) = commands::parse_config(); + let (build_config, os_config, targets) = commands::parse_config(); let bin_args: Option> = args.bin_args .as_ref() .map(|x| x.iter().map(|x| x.as_str()).collect()); - utils::log(utils::LogLevel::Log, "Running..."); + log(LogLevel::Log, "Running..."); let exe_target = targets.iter().find(|x| x.typ == "exe").unwrap(); - commands::run(bin_args, &build_config, &os_config, exe_target, &targets, &packages); + commands::run(bin_args, &build_config, &os_config, exe_target, &targets); } } - diff --git a/src/packages.rs b/src/packages.rs index f67a314..28700cc 100644 --- a/src/packages.rs +++ b/src/packages.rs @@ -3,7 +3,7 @@ use reqwest; use toml; use bytes::Bytes; -use crate::utils::{log, LogLevel}; +use crate::utils::log::{log, LogLevel}; use colored::Colorize; use std::{fs, fmt}; use std::path::{Path, PathBuf}; diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..e7ef9b5 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,582 @@ +//! Parsing Module + +use std::sync::{Arc, RwLock}; +use std::{io::Read, path::Path}; +use std::fs::File; +use toml::{Table, Value}; +use std::default::Default; +use std::process::Command; +use serde::Serialize; +use crate::builder::Target; +use crate::utils::log::{log, LogLevel}; + +/// Struct descibing the build config of the local project +#[derive(Debug, Clone)] +pub struct BuildConfig { + pub compiler: Arc>, +} + +/// Struct descibing the OS config of the local project +#[derive(Debug, Default, PartialEq, Clone, Serialize)] +pub struct OSConfig { + pub name: String, + pub features: Vec, + pub ulib: String, + pub platform: PlatformConfig, +} + +/// Struct descibing the platform config of the local project +#[derive(Debug, Default, PartialEq, Clone, Serialize)] +pub struct PlatformConfig { + pub name: String, + pub arch: String, + pub cross_compile: String, + pub target: String, + pub smp: String, + pub mode: String, + pub log: String, + pub v: String, + pub qemu: QemuConfig, +} + +/// Struct descibing the qemu config of the local project +#[derive(Debug, Default, PartialEq, Clone, Serialize)] +pub struct QemuConfig { + pub debug: String, + pub blk: String, + pub net: String, + pub graphic: String, + pub bus: String, + pub disk_img: String, + pub v9p: String, + pub v9p_path: String, + pub accel: String, + pub qemu_log: String, + pub net_dump: String, + pub net_dev: String, + pub ip: String, + pub gw: String, + pub args: String, + pub envs: String, +} + +impl QemuConfig { + /// This function is used to config qemu parameters when running on qemu + pub fn config_qemu(&self, platform_config: &PlatformConfig, trgt: &Target) -> (Vec, Vec) { + // vdev_suffix + let vdev_suffix = match self.bus.as_str() { + "mmio" => "device", + "pci" => "pci", + _ => { + log(LogLevel::Error, "BUS must be one of 'mmio' or 'pci'"); + std::process::exit(1); + } + }; + // config qemu + let mut qemu_args = Vec::new(); + qemu_args.push(format!("qemu-system-{}", platform_config.arch)); + // init + qemu_args.push("-m".to_string()); + qemu_args.push("128M".to_string()); + qemu_args.push("-smp".to_string()); + qemu_args.push(platform_config.smp.clone()); + // arch + match platform_config.arch.as_str() { + "x86_64" => { + qemu_args.extend( + vec!["-machine", "q35", "-kernel", &trgt.elf_path] + .iter().map(|&arg| arg.to_string()) + ); + } + "risc64" => { + qemu_args.extend( + vec!["-machine", "virt", "-bios", "default", "-kernel", &trgt.bin_path] + .iter().map(|&arg| arg.to_string()) + ); + } + "aarch64" => { + qemu_args.extend( + vec!["-cpu", "cortex-a72", "-machine", "virt", "-kernel", &trgt.bin_path] + .iter().map(|&arg| arg.to_string()) + ); + } + _ => { + log(LogLevel::Error, "Unsupported architecture"); + std::process::exit(1); + } + }; + // args and envs + qemu_args.push("-append".to_string()); + qemu_args.push(format!("\";{};{}\"", self.args, self.envs)); + // blk + if self.blk == "y" { + qemu_args.push("-device".to_string()); + qemu_args.push(format!("virtio-blk-{},drive=disk0", vdev_suffix)); + qemu_args.push("-drive".to_string()); + qemu_args.push(format!("id=disk0,if=none,format=raw,file={}", self.disk_img)); + } + // v9p + if self.v9p == "y" { + qemu_args.push("-fsdev".to_string()); + qemu_args.push(format!("local,id=myid,path={},security_model=none", self.v9p_path)); + qemu_args.push("-device".to_string()); + qemu_args.push(format!("virtio-9p-{},fsdev=myid,mount_tag=rootfs", vdev_suffix)); + } + // net + if self.net == "y" { + qemu_args.push("-device".to_string()); + qemu_args.push(format!("virtio-net-{},netdev=net0", vdev_suffix)); + // net_dev + if self.net_dev == "user" { + qemu_args.push("-netdev".to_string()); + qemu_args.push("user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555".to_string()); + } else if self.net_dev == "tap" { + qemu_args.push("-netdev".to_string()); + qemu_args.push("tap,id=net0,ifname=tap0,script=no,downscript=no".to_string()); + } else { + log(LogLevel::Error, "NET_DEV must be one of 'user' or 'tap'"); + std::process::exit(1); + } + // net_dump + if self.net_dump == "y" { + qemu_args.push("-object".to_string()); + qemu_args.push("filter-dump,id=dump0,netdev=net0,file=netdump.pcap".to_string()); + } + } + // graphic + if self.graphic == "y" { + qemu_args.push("-device".to_string()); + qemu_args.push(format!("virtio-gpu-{}", vdev_suffix)); + qemu_args.push("-vga".to_string()); + qemu_args.push("none".to_string()); + qemu_args.push("-serial".to_string()); + qemu_args.push("mon:stdio".to_string()); + } else if self.graphic == "n" { + qemu_args.push("-nographic".to_string()); + } + // qemu_log + if self.qemu_log == "y" { + qemu_args.push("-D".to_string()); + qemu_args.push("qemu.log".to_string()); + qemu_args.push("-d".to_string()); + qemu_args.push("in_asm,int,mmu,pcall,cpu_reset,guest_errors".to_string()); + } + // debug + let mut qemu_args_debug = Vec::new(); + qemu_args_debug.extend(qemu_args.clone()); + qemu_args_debug.push("-s".to_string()); + qemu_args_debug.push("-S".to_string()); + // acceel + if self.accel == "y" { + if cfg!(target_os = "darwin") { + qemu_args.push("-cpu".to_string()); + qemu_args.push("host".to_string()); + qemu_args.push("-accel".to_string()); + qemu_args.push("hvf".to_string()); + } else { + qemu_args.push("-cpu".to_string()); + qemu_args.push("host".to_string()); + qemu_args.push("-accel".to_string()); + qemu_args.push("kvm".to_string()); + } + } + + (qemu_args, qemu_args_debug) + } +} + +/// Struct describing the target config of the local project +#[derive(Debug, Clone)] +pub struct TargetConfig { + pub name: String, + pub src: String, + pub src_only: Vec, + pub src_exclude: Vec, + pub include_dir: Vec, + pub typ: String, + pub cflags: String, + pub archive: String, + pub linker: String, + pub ldflags: String, + pub deps: Vec, +} + +impl TargetConfig { + /// Returns a vec of all filenames ending in .cpp or .c in the src directory + /// # Arguments + /// * `path` - The path to the src directory + fn get_src_names(&self, tgt_path: &str) -> Vec { + if tgt_path.is_empty() { + return Vec::new(); + } + let mut src_names = Vec::new(); + let src_path = Path::new(&tgt_path); + let src_entries = std::fs::read_dir(src_path).unwrap_or_else(|_| { + log(LogLevel::Error, &format!("Could not read src dir: {}", tgt_path)); + std::process::exit(1); + }); + + // Convert src_only and src_exclude to Vec<&str> for easier comparison + let src_only: Vec<&str> = self.src_only.iter().map(AsRef::as_ref).collect(); + let src_exclude: Vec<&str> = self.src_exclude.iter().map(AsRef::as_ref).collect(); + + // Iterate over all entrys + for entry in src_entries { + let entry = entry.unwrap(); + let path = entry.path().to_str().unwrap().to_string().replace("\\", "/"); + + // Inclusion logic: Check if the path is in src_only + let include = if !src_only.is_empty() { + src_only.iter().any(|&included| path.contains(included)) + } else { + true // If src_only is empty, include all + }; + if !include { + log(LogLevel::Debug, &format!("Excluding (not in src_only): {}", path)); + continue; + } + + // Exclusion logic: Check if the path is in src_exclude + let exclude = src_exclude.iter().any(|&excluded| path.contains(excluded)); + if exclude { + log(LogLevel::Debug, &format!("Excluding (in src_exclude): {}", path)); + continue; + } + + if entry.path().is_dir() { + src_names.append(&mut self.get_src_names(&path)); + } else if entry.path().is_file() { + if !path.ends_with(".cpp") && !path.ends_with(".c") { + continue; + } + let file_path = entry.path(); + let file_path_str = file_path.to_str().unwrap(); + src_names.push(file_path_str.to_string()); + } + } + + src_names + } + + /// Rearrange the input targets + fn arrange_targets(targets: Vec) -> Vec { + let mut targets = targets.clone(); + let mut i = 0; + while i < targets.len() { + let mut j = i + 1; + while j < targets.len() { + if targets[i].deps.contains(&targets[j].name) { + // Check for circular dependencies + if targets[j].deps.contains(&targets[i].name) { + log( + LogLevel::Error, + &format!( + "Circular dependency found between {} and {}", + targets[i].name, targets[j].name + ), + ); + std::process::exit(1); + } + let temp = targets[i].clone(); + targets[i] = targets[j].clone(); + targets[j] = temp; + i = 0; + break; + } + j += 1; + } + i += 1; + } + targets + } +} + +/// This function is used to parse the config file of local project +/// # Arguments +/// * `path` - The path to the config file +/// * `check_dup_src` - If true, the function will check for duplicately named source files +pub fn parse_config(path: &str, check_dup_src: bool) -> (BuildConfig, OSConfig, Vec) { + // Open toml file and parse it into a string + let mut file = File::open(path).unwrap_or_else(|_| { + log(LogLevel::Error, &format!("Could not open config file: {}", path)); + std::process::exit(1); + }); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap_or_else(|_| { + log(LogLevel::Error, &format!("Could not read config file: {}", path)); + std::process::exit(1); + }); + let config = contents.parse::().unwrap_or_else(|e| { + log(LogLevel::Error, &format!("Could not parse config file: {}", path)); + log(LogLevel::Error, &format!("Error: {}", e)); + std::process::exit(1); + }); + + let build_config = parse_build_config(&config); + let os_config = parse_os_config(&config, &build_config); + let targets = parse_targets(&config, check_dup_src); + + (build_config, os_config, targets) +} + +/// Parses the build configuration +fn parse_build_config(config: &Table) -> BuildConfig { + let build = config["build"].as_table().unwrap_or_else(|| { + log(LogLevel::Error, "Could not find build in config file"); + std::process::exit(1); + }); + let compiler= Arc::new( + RwLock::new( + build.get("compiler").unwrap_or_else(|| { + log(LogLevel::Error, "Could not find compiler in config file"); + std::process::exit(1); + }).as_str().unwrap_or_else(|| { + log(LogLevel::Error, "Compiler is not a string"); + std::process::exit(1); + }).to_string() + ) + ); + + BuildConfig {compiler} +} + +/// Parses the OS configuration +fn parse_os_config(config: &Table, build_config: &BuildConfig) -> OSConfig { + let empty_os = Value::Table(toml::map::Map::default()); + let os = config.get("os").unwrap_or(&empty_os); + let os_config: OSConfig; + if os != &empty_os { + if let Some(os_table) = os.as_table() { + let name = parse_cfg_string(&os_table, "name", ""); + let ulib = parse_cfg_string(&os_table, "ulib", ""); + let mut features = parse_cfg_vector(&os_table, "services"); + if features.iter().any(|feat| { + feat == "fs" || feat == "net" || feat == "pipe" || feat == "select" || feat == "poll" || feat == "epoll" + }) { + features.push("fd".to_string()); + } + if ulib == "ruxmusl" { + features.push("musl".to_string()); + features.push("fp_simd".to_string()); + features.push("fd".to_string()); + features.push("tls".to_string()); + } + // Parse platform (if empty, it is the default value) + let platform = parse_platform(&os_table); + let current_compiler = build_config.compiler.read().unwrap(); + let new_compiler = format!("{}{}", platform.cross_compile, *current_compiler); + drop(current_compiler); + *build_config.compiler.write().unwrap() = new_compiler; + os_config = OSConfig {name, features, ulib, platform}; + } else { + log(LogLevel::Error, "OS is not a table"); + std::process::exit(1); + } + } else { + os_config = OSConfig::default(); + } + + os_config +} + +/// Parses the targets configuration +fn parse_targets(config: &Table, check_dup_src: bool) -> Vec { + let mut tgts = Vec::new(); + let targets = config["targets"].as_array().unwrap_or_else(|| { + log(LogLevel::Error, "Could not find targets in config file"); + std::process::exit(1); + }); + for target in targets { + let target_tb = target.as_table().unwrap_or_else(|| { + log(LogLevel::Error, "Target is not a table"); + std::process::exit(1); + }); + // include_dir is compatible with both string and vector types + let include_dir = if let Some(value) = target_tb.get("include_dir") { + match value { + Value::String(_s) => vec![parse_cfg_string(target_tb, "include_dir", "./")], + Value::Array(_arr) => parse_cfg_vector(target_tb, "include_dir"), + _ => { + log(LogLevel::Error, "Invalid include_dir field"); + std::process::exit(1); + } + } + } else { + vec!["./".to_owned()] + }; + let target_config = TargetConfig { + name: parse_cfg_string(target_tb, "name", ""), + src: parse_cfg_string(target_tb, "src", ""), + src_only: parse_cfg_vector(target_tb, "src_only"), + src_exclude: parse_cfg_vector(target_tb, "src_exclude"), + include_dir, + typ: parse_cfg_string(target_tb, "type", ""), + cflags: parse_cfg_string(target_tb, "cflags", ""), + archive: parse_cfg_string(target_tb, "archive", ""), + linker: parse_cfg_string(target_tb, "linker", ""), + ldflags: parse_cfg_string(target_tb, "ldflags", ""), + deps: parse_cfg_vector(target_tb, "deps"), + }; + if target_config.typ != "exe" && target_config.typ != "dll" + && target_config.typ != "static" && target_config.typ != "object" { + log(LogLevel::Error, "Type must be exe, dll, object or static"); + std::process::exit(1); + } + tgts.push(target_config); + } + if tgts.is_empty() { + log(LogLevel::Error, "No targets found"); + std::process::exit(1); + } + + // Checks for duplicate target names + for i in 0..tgts.len() - 1 { + for j in i + 1..tgts.len() { + if tgts[i].name == tgts[j].name { + log(LogLevel::Error, &format!("Duplicate target names found: {}", tgts[i].name)); + std::process::exit(1); + } + } + } + + // Checks for duplicate srcs in the target + if check_dup_src { + for target in &tgts { + let mut src_file_names = target.get_src_names(&target.src); + src_file_names.sort(); + if !src_file_names.is_empty() { + for i in 0..src_file_names.len() - 1 { + if src_file_names[i] == src_file_names[i + 1] { + log(LogLevel::Error, &format!("Duplicate source files found for target: {}", target.name)); + log(LogLevel::Error, "Source files must be unique"); + log(LogLevel::Error, &format!("Duplicate file: {}", src_file_names[i])); + std::process::exit(1); + } + } + } else { + log(LogLevel::Warn, &format!("No source files found for target: {}", target.name)); + } + } + } + let tgts_arranged = TargetConfig::arrange_targets(tgts); + + tgts_arranged +} + +/// Parses the platform configuration +fn parse_platform(config: &Table) -> PlatformConfig { + let empty_platform = Value::Table(toml::map::Map::default()); + let platform = config.get("platform").unwrap_or(&empty_platform); + if let Some(platform_table) = platform.as_table() { + let name = parse_cfg_string(platform_table, "name", "x86_64-qemu-q35"); + let arch = name.split("-").next().unwrap_or("x86_64").to_string(); + let cross_compile = format!("{}-linux-musl-", arch); + let target = match &arch[..] { + "x86_64" => "x86_64-unknown-none".to_string(), + "riscv64" => "riscv64gc-unknown-none-elf".to_string(), + "aarch64" => "aarch64-unknown-none-softfloat".to_string(), + _ => { + log(LogLevel::Error, "\"ARCH\" must be one of \"x86_64\", \"riscv64\", or \"aarch64\""); + std::process::exit(1); + } + }; + let smp = parse_cfg_string(platform_table, "smp", "1"); + let mode = parse_cfg_string(platform_table, "mode", "release"); + let log = parse_cfg_string(platform_table, "log", "warn"); + let v = parse_cfg_string(platform_table, "v", ""); + // determine whether enable qemu + let qemu: QemuConfig; + if name.split("-").any(|s| s == "qemu") { + // parse qemu (if empty, it is the default value) + qemu = parse_qemu(&arch, platform_table); + } else { + qemu = QemuConfig::default(); + } + PlatformConfig {name, arch, cross_compile, target, smp, mode, log, v, qemu} + } else { + log(LogLevel::Error, "Platform is not a table"); + std::process::exit(1); + } +} + +/// Parses the qemu configuration +fn parse_qemu(arch: &str, config: &Table) -> QemuConfig { + let empty_qemu = Value::Table(toml::map::Map::default()); + let qemu = config.get("qemu").unwrap_or(&empty_qemu); + if let Some(qemu_table) = qemu.as_table() { + let debug = parse_cfg_string(qemu_table, "debug", "n"); + let blk = parse_cfg_string(qemu_table, "blk", "n"); + let net = parse_cfg_string(qemu_table, "net", "n"); + let graphic = parse_cfg_string(qemu_table, "graphic", "n"); + let bus = match &arch[..] { + "x86_64" => "pci".to_string(), + _ => "mmio".to_string() + }; + let disk_img = parse_cfg_string(qemu_table, "disk_img", "disk.img"); + let v9p = parse_cfg_string(qemu_table, "v9p", "n"); + let v9p_path = parse_cfg_string(qemu_table, "v9p_path", "./"); + let accel_pre = match Command::new("uname").arg("-r").output() { + Ok(output) => { + let kernel_version = String::from_utf8_lossy(&output.stdout).to_lowercase(); + if kernel_version.contains("-microsoft") { "n" } else { "y" } + }, + Err(_) => { + log(LogLevel::Error, "Failed to execute command"); + std::process::exit(1); + } + }; + let accel = match &arch[..] { + "x86_64" => accel_pre.to_string(), + _ => "n".to_string() + }; + let qemu_log = parse_cfg_string(qemu_table, "qemu_log", "n"); + let net_dump = parse_cfg_string(qemu_table, "net_dump", "n"); + let net_dev = parse_cfg_string(qemu_table, "net_dev", "user"); + let ip = parse_cfg_string(qemu_table, "ip", "10.0.2.15"); + let gw = parse_cfg_string(qemu_table, "gw", "10.0.2.2"); + let args = parse_cfg_string(qemu_table, "args", ""); + let envs = parse_cfg_string(qemu_table, "envs", ""); + QemuConfig { + debug, blk, net, graphic, bus, disk_img, v9p, v9p_path, accel, qemu_log, net_dump, net_dev, ip, gw, args, envs + } + } else { + log(LogLevel::Error, "Qemu is not a table"); + std::process::exit(1); + } +} + +/// Parses the configuration field of the string type +fn parse_cfg_string(config: &Table, field: &str, default: &str) -> String { + let default_string = Value::String(default.to_string()); + config.get(field) + .unwrap_or_else(|| &default_string) + .as_str() + .unwrap_or_else(|| { + log(LogLevel::Error, &format!("{} is not a string", field)); + std::process::exit(1); + }) + .to_string() +} + +/// Parses the configuration field of the vector type +fn parse_cfg_vector(config: &Table, field: &str) -> Vec { + let empty_vector = Value::Array(Vec::new()); + config.get(field) + .unwrap_or_else(|| &empty_vector) + .as_array() + .unwrap_or_else(|| { + log(LogLevel::Error, &format!("{} is not an array", field)); + std::process::exit(1); + }) + .iter() + .map(|value| { + value + .as_str() + .unwrap_or_else(|| { + log(LogLevel::Error, &format!("{} elements are strings", field)); + std::process::exit(1); + }) + .to_string() + }) + .collect() +} diff --git a/src/utils.rs b/src/utils.rs index 9c038c1..4918872 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,808 +1,6 @@ -//! This file contains various logging, toml parsing functions and environment configuration +//! This module contains various logging, environment config and features config. //! used by the ruxgo library -use std::sync::{Arc, RwLock, Once}; -use std::{io::Read, path::Path}; -use std::fs::{self, File}; -use toml::{Table, Value}; -use colored::Colorize; -use std::default::Default; -use std::process::{Command, Stdio}; -use serde::Serialize; -use crate::builder::Target; - -static INIT: Once = Once::new(); -static LOG_LEVEL: RwLock = RwLock::new(LogLevel::Info); - -/// This enum is used to represent the different log levels -#[derive(PartialEq, PartialOrd, Debug)] -pub enum LogLevel { - Debug, - Info, - Log, - Warn, - Error, -} - -/// Initializes the log level, which is called only once when the program starts -fn init_log_level() { - let level = std::env::var("RUXGO_LOG_LEVEL").unwrap_or_else(|_| "Info".to_string()); - let log_level = match level.as_str() { - "Debug" => LogLevel::Debug, - "Info" => LogLevel::Info, - "Log" => LogLevel::Log, - "Warn" => LogLevel::Warn, - "Error" => LogLevel::Error, - _ => LogLevel::Log, - }; - - // Use write lock to update the log level - let mut write_lock = LOG_LEVEL.write().unwrap(); - *write_lock = log_level; -} - -/// This function is used to log messages to the console -/// # Arguments -/// * `level` - The log level of the message -/// * `message` - The message to log -/// # Example -/// ``` -/// log(LogLevel::Info, "Hello World!"); -/// log(LogLevel::Error, &format!("Something went wrong! {}", error)); -/// ``` -/// -/// # Level setting -/// The log level can be set by setting the environment variable `RUXGO_LOG_LEVEL` -/// to one of the following values: -/// * `Debug` -/// * `Info` -/// * `Log` -/// * `Warn` -/// * `Error` -/// If the environment variable is not set, the default log level is `Log` -pub fn log(level: LogLevel, message: &str) { - INIT.call_once(|| { - init_log_level(); - }); - let level_str = match level { - LogLevel::Debug => "[DEBUG]".purple(), - LogLevel::Info => "[INFO]".blue(), - LogLevel::Log => "[LOG]".green(), - LogLevel::Warn => "[WARN]".yellow(), - LogLevel::Error => "[ERROR]".red(), - }; - // Use read lock to check log level - if level >= *LOG_LEVEL.read().unwrap() { - println!("{} {}", level_str, message); - } -} - -/// Struct descibing the build config of the local project -#[derive(Debug, Clone)] -pub struct BuildConfig { - pub compiler: Arc>, - pub packages: Vec, -} - -/// Struct descibing the OS config of the local project -#[derive(Debug, Default, PartialEq, Clone, Serialize)] -pub struct OSConfig { - pub name: String, - pub features: Vec, - pub ulib: String, - pub platform: PlatformConfig, -} - -/// Struct descibing the platform config of the local project -#[derive(Debug, Default, PartialEq, Clone, Serialize)] -pub struct PlatformConfig { - pub name: String, - pub arch: String, - pub cross_compile: String, - pub target: String, - pub smp: String, - pub mode: String, - pub log: String, - pub v: String, - pub qemu: QemuConfig, -} - -/// Struct descibing the qemu config of the local project -#[derive(Debug, Default, PartialEq, Clone, Serialize)] -pub struct QemuConfig { - pub debug: String, - pub blk: String, - pub net: String, - pub graphic: String, - pub bus: String, - pub disk_img: String, - pub v9p: String, - pub v9p_path: String, - pub accel: String, - pub qemu_log: String, - pub net_dump: String, - pub net_dev: String, - pub ip: String, - pub gw: String, - pub args: String, - pub envs: String, -} - -impl QemuConfig { - /// This function is used to config qemu parameters when running on qemu - pub fn config_qemu(&self, platform_config: &PlatformConfig, trgt: &Target) -> (Vec, Vec) { - // vdev_suffix - let vdev_suffix = match self.bus.as_str() { - "mmio" => "device", - "pci" => "pci", - _ => { - log(LogLevel::Error, "BUS must be one of 'mmio' or 'pci'"); - std::process::exit(1); - } - }; - // config qemu - let mut qemu_args = Vec::new(); - qemu_args.push(format!("qemu-system-{}", platform_config.arch)); - // init - qemu_args.push("-m".to_string()); - qemu_args.push("128M".to_string()); - qemu_args.push("-smp".to_string()); - qemu_args.push(platform_config.smp.clone()); - // arch - match platform_config.arch.as_str() { - "x86_64" => { - qemu_args.extend( - vec!["-machine", "q35", "-kernel", &trgt.elf_path] - .iter().map(|&arg| arg.to_string()) - ); - } - "risc64" => { - qemu_args.extend( - vec!["-machine", "virt", "-bios", "default", "-kernel", &trgt.bin_path] - .iter().map(|&arg| arg.to_string()) - ); - } - "aarch64" => { - qemu_args.extend( - vec!["-cpu", "cortex-a72", "-machine", "virt", "-kernel", &trgt.bin_path] - .iter().map(|&arg| arg.to_string()) - ); - } - _ => { - log(LogLevel::Error, "Unsupported architecture"); - std::process::exit(1); - } - }; - // args and envs - qemu_args.push("-append".to_string()); - qemu_args.push(format!("\";{};{}\"", self.args, self.envs)); - // blk - if self.blk == "y" { - qemu_args.push("-device".to_string()); - qemu_args.push(format!("virtio-blk-{},drive=disk0", vdev_suffix)); - qemu_args.push("-drive".to_string()); - qemu_args.push(format!("id=disk0,if=none,format=raw,file={}", self.disk_img)); - } - // v9p - if self.v9p == "y" { - qemu_args.push("-fsdev".to_string()); - qemu_args.push(format!("local,id=myid,path={},security_model=none", self.v9p_path)); - qemu_args.push("-device".to_string()); - qemu_args.push(format!("virtio-9p-{},fsdev=myid,mount_tag=rootfs", vdev_suffix)); - } - // net - if self.net == "y" { - qemu_args.push("-device".to_string()); - qemu_args.push(format!("virtio-net-{},netdev=net0", vdev_suffix)); - // net_dev - if self.net_dev == "user" { - qemu_args.push("-netdev".to_string()); - qemu_args.push("user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555".to_string()); - } else if self.net_dev == "tap" { - qemu_args.push("-netdev".to_string()); - qemu_args.push("tap,id=net0,ifname=tap0,script=no,downscript=no".to_string()); - } else { - log(LogLevel::Error, "NET_DEV must be one of 'user' or 'tap'"); - std::process::exit(1); - } - // net_dump - if self.net_dump == "y" { - qemu_args.push("-object".to_string()); - qemu_args.push("filter-dump,id=dump0,netdev=net0,file=netdump.pcap".to_string()); - } - } - // graphic - if self.graphic == "y" { - qemu_args.push("-device".to_string()); - qemu_args.push(format!("virtio-gpu-{}", vdev_suffix)); - qemu_args.push("-vga".to_string()); - qemu_args.push("none".to_string()); - qemu_args.push("-serial".to_string()); - qemu_args.push("mon:stdio".to_string()); - } else if self.graphic == "n" { - qemu_args.push("-nographic".to_string()); - } - // qemu_log - if self.qemu_log == "y" { - qemu_args.push("-D".to_string()); - qemu_args.push("qemu.log".to_string()); - qemu_args.push("-d".to_string()); - qemu_args.push("in_asm,int,mmu,pcall,cpu_reset,guest_errors".to_string()); - } - // debug - let mut qemu_args_debug = Vec::new(); - qemu_args_debug.extend(qemu_args.clone()); - qemu_args_debug.push("-s".to_string()); - qemu_args_debug.push("-S".to_string()); - // acceel - if self.accel == "y" { - if cfg!(target_os = "darwin") { - qemu_args.push("-cpu".to_string()); - qemu_args.push("host".to_string()); - qemu_args.push("-accel".to_string()); - qemu_args.push("hvf".to_string()); - } else { - qemu_args.push("-cpu".to_string()); - qemu_args.push("host".to_string()); - qemu_args.push("-accel".to_string()); - qemu_args.push("kvm".to_string()); - } - } - - (qemu_args, qemu_args_debug) - } -} - -/// Struct describing the target config of the local project -#[derive(Debug, Clone)] -pub struct TargetConfig { - pub name: String, - pub src: String, - pub src_only: Vec, - pub src_exclude: Vec, - pub include_dir: Vec, - pub typ: String, - pub cflags: String, - pub archive: String, - pub linker: String, - pub ldflags: String, - pub deps: Vec, -} - -impl TargetConfig { - /// Returns a vec of all filenames ending in .cpp or .c in the src directory - /// # Arguments - /// * `path` - The path to the src directory - fn get_src_names(&self, tgt_path: &str) -> Vec { - if tgt_path.is_empty() { - return Vec::new(); - } - let mut src_names = Vec::new(); - let src_path = Path::new(&tgt_path); - let src_entries = std::fs::read_dir(src_path).unwrap_or_else(|_| { - log(LogLevel::Error, &format!("Could not read src dir: {}", tgt_path)); - std::process::exit(1); - }); - - // Convert src_only and src_exclude to Vec<&str> for easier comparison - let src_only: Vec<&str> = self.src_only.iter().map(AsRef::as_ref).collect(); - let src_exclude: Vec<&str> = self.src_exclude.iter().map(AsRef::as_ref).collect(); - - // Iterate over all entrys - for entry in src_entries { - let entry = entry.unwrap(); - let path = entry.path().to_str().unwrap().to_string().replace("\\", "/"); - - // Inclusion logic: Check if the path is in src_only - let include = if !src_only.is_empty() { - src_only.iter().any(|&included| path.contains(included)) - } else { - true // If src_only is empty, include all - }; - if !include { - log(LogLevel::Debug, &format!("Excluding (not in src_only): {}", path)); - continue; - } - - // Exclusion logic: Check if the path is in src_exclude - let exclude = src_exclude.iter().any(|&excluded| path.contains(excluded)); - if exclude { - log(LogLevel::Debug, &format!("Excluding (in src_exclude): {}", path)); - continue; - } - - if entry.path().is_dir() { - src_names.append(&mut self.get_src_names(&path)); - } else if entry.path().is_file() { - if !path.ends_with(".cpp") && !path.ends_with(".c") { - continue; - } - let file_path = entry.path(); - let file_path_str = file_path.to_str().unwrap(); - src_names.push(file_path_str.to_string()); - } - } - - src_names - } - - /// Rearrange the input targets - fn arrange_targets(targets: Vec) -> Vec { - let mut targets = targets.clone(); - let mut i = 0; - while i < targets.len() { - let mut j = i + 1; - while j < targets.len() { - if targets[i].deps.contains(&targets[j].name) { - // Check for circular dependencies - if targets[j].deps.contains(&targets[i].name) { - log( - LogLevel::Error, - &format!( - "Circular dependency found between {} and {}", - targets[i].name, targets[j].name - ), - ); - std::process::exit(1); - } - let temp = targets[i].clone(); - targets[i] = targets[j].clone(); - targets[j] = temp; - i = 0; - break; - } - j += 1; - } - i += 1; - } - targets - } -} - -/// This function is used to parse the config file of local project -/// # Arguments -/// * `path` - The path to the config file -/// * `check_dup_src` - If true, the function will check for duplicately named source files -pub fn parse_config(path: &str, check_dup_src: bool) -> (BuildConfig, OSConfig, Vec) { - // Open toml file and parse it into a string - let mut file = File::open(path).unwrap_or_else(|_| { - log(LogLevel::Error, &format!("Could not open config file: {}", path)); - std::process::exit(1); - }); - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap_or_else(|_| { - log(LogLevel::Error, &format!("Could not read config file: {}", path)); - std::process::exit(1); - }); - let config = contents.parse::
().unwrap_or_else(|e| { - log(LogLevel::Error, &format!("Could not parse config file: {}", path)); - log(LogLevel::Error, &format!("Error: {}", e)); - std::process::exit(1); - }); - - let build_config = parse_build_config(&config); - let os_config = parse_os_config(&config, &build_config); - let targets = parse_targets(&config, check_dup_src); - - (build_config, os_config, targets) -} - -/// Parses the build configuration -fn parse_build_config(config: &Table) -> BuildConfig { - let build = config["build"].as_table().unwrap_or_else(|| { - log(LogLevel::Error, "Could not find build in config file"); - std::process::exit(1); - }); - let compiler= Arc::new( - RwLock::new( - build.get("compiler").unwrap_or_else(|| { - log(LogLevel::Error, "Could not find compiler in config file"); - std::process::exit(1); - }).as_str().unwrap_or_else(|| { - log(LogLevel::Error, "Compiler is not a string"); - std::process::exit(1); - }).to_string() - ) - ); - let packages = parse_cfg_vector(build, "packages"); - - BuildConfig {compiler, packages} -} - -/// Parses the OS configuration -fn parse_os_config(config: &Table, build_config: &BuildConfig) -> OSConfig { - let empty_os = Value::Table(toml::map::Map::default()); - let os = config.get("os").unwrap_or(&empty_os); - let os_config: OSConfig; - if os != &empty_os { - if let Some(os_table) = os.as_table() { - let name = parse_cfg_string(&os_table, "name", ""); - let ulib = parse_cfg_string(&os_table, "ulib", ""); - let mut features = parse_cfg_vector(&os_table, "services"); - if features.iter().any(|feat| { - feat == "fs" || feat == "net" || feat == "pipe" || feat == "select" || feat == "poll" || feat == "epoll" - }) { - features.push("fd".to_string()); - } - if ulib == "ruxmusl" { - features.push("musl".to_string()); - features.push("fp_simd".to_string()); - features.push("fd".to_string()); - features.push("tls".to_string()); - } - // Parse platform (if empty, it is the default value) - let platform = parse_platform(&os_table); - let current_compiler = build_config.compiler.read().unwrap(); - let new_compiler = format!("{}{}", platform.cross_compile, *current_compiler); - drop(current_compiler); - *build_config.compiler.write().unwrap() = new_compiler; - os_config = OSConfig {name, features, ulib, platform}; - } else { - log(LogLevel::Error, "OS is not a table"); - std::process::exit(1); - } - } else { - os_config = OSConfig::default(); - } - - os_config -} - -/// Parses the targets configuration -fn parse_targets(config: &Table, check_dup_src: bool) -> Vec { - let mut tgts = Vec::new(); - let targets = config["targets"].as_array().unwrap_or_else(|| { - log(LogLevel::Error, "Could not find targets in config file"); - std::process::exit(1); - }); - for target in targets { - let target_tb = target.as_table().unwrap_or_else(|| { - log(LogLevel::Error, "Target is not a table"); - std::process::exit(1); - }); - // include_dir is compatible with both string and vector types - let include_dir = if let Some(value) = target_tb.get("include_dir") { - match value { - Value::String(_s) => vec![parse_cfg_string(target_tb, "include_dir", "./")], - Value::Array(_arr) => parse_cfg_vector(target_tb, "include_dir"), - _ => { - log(LogLevel::Error, "Invalid include_dir field"); - std::process::exit(1); - } - } - } else { - vec!["./".to_owned()] - }; - let target_config = TargetConfig { - name: parse_cfg_string(target_tb, "name", ""), - src: parse_cfg_string(target_tb, "src", ""), - src_only: parse_cfg_vector(target_tb, "src_only"), - src_exclude: parse_cfg_vector(target_tb, "src_exclude"), - include_dir, - typ: parse_cfg_string(target_tb, "type", ""), - cflags: parse_cfg_string(target_tb, "cflags", ""), - archive: parse_cfg_string(target_tb, "archive", ""), - linker: parse_cfg_string(target_tb, "linker", ""), - ldflags: parse_cfg_string(target_tb, "ldflags", ""), - deps: parse_cfg_vector(target_tb, "deps"), - }; - if target_config.typ != "exe" && target_config.typ != "dll" - && target_config.typ != "static" && target_config.typ != "object" { - log(LogLevel::Error, "Type must be exe, dll, object or static"); - std::process::exit(1); - } - tgts.push(target_config); - } - if tgts.is_empty() { - log(LogLevel::Error, "No targets found"); - std::process::exit(1); - } - - // Checks for duplicate target names - for i in 0..tgts.len() - 1 { - for j in i + 1..tgts.len() { - if tgts[i].name == tgts[j].name { - log(LogLevel::Error, &format!("Duplicate target names found: {}", tgts[i].name)); - std::process::exit(1); - } - } - } - - // Checks for duplicate srcs in the target - if check_dup_src { - for target in &tgts { - let mut src_file_names = target.get_src_names(&target.src); - src_file_names.sort(); - if !src_file_names.is_empty() { - for i in 0..src_file_names.len() - 1 { - if src_file_names[i] == src_file_names[i + 1] { - log(LogLevel::Error, &format!("Duplicate source files found for target: {}", target.name)); - log(LogLevel::Error, "Source files must be unique"); - log(LogLevel::Error, &format!("Duplicate file: {}", src_file_names[i])); - std::process::exit(1); - } - } - } else { - log(LogLevel::Warn, &format!("No source files found for target: {}", target.name)); - } - } - } - let tgts_arranged = TargetConfig::arrange_targets(tgts); - - tgts_arranged -} - -/// Parses the platform configuration -fn parse_platform(config: &Table) -> PlatformConfig { - let empty_platform = Value::Table(toml::map::Map::default()); - let platform = config.get("platform").unwrap_or(&empty_platform); - if let Some(platform_table) = platform.as_table() { - let name = parse_cfg_string(platform_table, "name", "x86_64-qemu-q35"); - let arch = name.split("-").next().unwrap_or("x86_64").to_string(); - let cross_compile = format!("{}-linux-musl-", arch); - let target = match &arch[..] { - "x86_64" => "x86_64-unknown-none".to_string(), - "riscv64" => "riscv64gc-unknown-none-elf".to_string(), - "aarch64" => "aarch64-unknown-none-softfloat".to_string(), - _ => { - log(LogLevel::Error, "\"ARCH\" must be one of \"x86_64\", \"riscv64\", or \"aarch64\""); - std::process::exit(1); - } - }; - let smp = parse_cfg_string(platform_table, "smp", "1"); - let mode = parse_cfg_string(platform_table, "mode", "release"); - let log = parse_cfg_string(platform_table, "log", "warn"); - let v = parse_cfg_string(platform_table, "v", ""); - // determine whether enable qemu - let qemu: QemuConfig; - if name.split("-").any(|s| s == "qemu") { - // parse qemu (if empty, it is the default value) - qemu = parse_qemu(&arch, platform_table); - } else { - qemu = QemuConfig::default(); - } - PlatformConfig {name, arch, cross_compile, target, smp, mode, log, v, qemu} - } else { - log(LogLevel::Error, "Platform is not a table"); - std::process::exit(1); - } -} - -/// Parses the qemu configuration -fn parse_qemu(arch: &str, config: &Table) -> QemuConfig { - let empty_qemu = Value::Table(toml::map::Map::default()); - let qemu = config.get("qemu").unwrap_or(&empty_qemu); - if let Some(qemu_table) = qemu.as_table() { - let debug = parse_cfg_string(qemu_table, "debug", "n"); - let blk = parse_cfg_string(qemu_table, "blk", "n"); - let net = parse_cfg_string(qemu_table, "net", "n"); - let graphic = parse_cfg_string(qemu_table, "graphic", "n"); - let bus = match &arch[..] { - "x86_64" => "pci".to_string(), - _ => "mmio".to_string() - }; - let disk_img = parse_cfg_string(qemu_table, "disk_img", "disk.img"); - let v9p = parse_cfg_string(qemu_table, "v9p", "n"); - let v9p_path = parse_cfg_string(qemu_table, "v9p_path", "./"); - let accel_pre = match Command::new("uname").arg("-r").output() { - Ok(output) => { - let kernel_version = String::from_utf8_lossy(&output.stdout).to_lowercase(); - if kernel_version.contains("-microsoft") { "n" } else { "y" } - }, - Err(_) => { - log(LogLevel::Error, "Failed to execute command"); - std::process::exit(1); - } - }; - let accel = match &arch[..] { - "x86_64" => accel_pre.to_string(), - _ => "n".to_string() - }; - let qemu_log = parse_cfg_string(qemu_table, "qemu_log", "n"); - let net_dump = parse_cfg_string(qemu_table, "net_dump", "n"); - let net_dev = parse_cfg_string(qemu_table, "net_dev", "user"); - let ip = parse_cfg_string(qemu_table, "ip", "10.0.2.15"); - let gw = parse_cfg_string(qemu_table, "gw", "10.0.2.2"); - let args = parse_cfg_string(qemu_table, "args", ""); - let envs = parse_cfg_string(qemu_table, "envs", ""); - QemuConfig { - debug, blk, net, graphic, bus, disk_img, v9p, v9p_path, accel, qemu_log, net_dump, net_dev, ip, gw, args, envs - } - } else { - log(LogLevel::Error, "Qemu is not a table"); - std::process::exit(1); - } -} - -/// Parses the configuration field of the string type -fn parse_cfg_string(config: &Table, field: &str, default: &str) -> String { - let default_string = Value::String(default.to_string()); - config.get(field) - .unwrap_or_else(|| &default_string) - .as_str() - .unwrap_or_else(|| { - log(LogLevel::Error, &format!("{} is not a string", field)); - std::process::exit(1); - }) - .to_string() -} - -/// Parses the configuration field of the vector type -fn parse_cfg_vector(config: &Table, field: &str) -> Vec { - let empty_vector = Value::Array(Vec::new()); - config.get(field) - .unwrap_or_else(|| &empty_vector) - .as_array() - .unwrap_or_else(|| { - log(LogLevel::Error, &format!("{} is not an array", field)); - std::process::exit(1); - }) - .iter() - .map(|value| { - value - .as_str() - .unwrap_or_else(|| { - log(LogLevel::Error, &format!("{} elements are strings", field)); - std::process::exit(1); - }) - .to_string() - }) - .collect() -} - -// This function is used to configure environment variables -pub fn config_env(os_config: &OSConfig,) { - if os_config != &OSConfig::default() && os_config.platform != PlatformConfig::default() { - std::env::set_var("RUX_ARCH", &os_config.platform.arch); - std::env::set_var("RUX_PLATFORM", &os_config.platform.name); - std::env::set_var("RUX_SMP", &os_config.platform.smp); - std::env::set_var("RUX_MODE", &os_config.platform.mode); - std::env::set_var("RUX_LOG", &os_config.platform.log); - std::env::set_var("RUX_TARGET", &os_config.platform.target); - if os_config.platform.qemu != QemuConfig::default() { - // ip and gw is for QEMU user netdev - std::env::set_var("RUX_IP", &os_config.platform.qemu.ip); - std::env::set_var("RUX_GW", &os_config.platform.qemu.gw); - // v9p option - if os_config.platform.qemu.v9p == "y" { - std::env::set_var("RUX_9P_ADDR", "127.0.0.1:564"); - std::env::set_var("RUX_ANAME_9P", "./"); - std::env::set_var("RUX_PROTOCOL_9P", "9P2000.L"); - } - } - // musl - if os_config.ulib == "ruxmusl" { - std::env::set_var("RUX_MUSL", "y"); - } - } -} - -/// Represents a package -#[derive(Debug, Clone)] -pub struct Package { - pub name: String, - pub repo: String, - pub branch: String, - pub build_config: BuildConfig, - pub target_configs: Vec, - pub sub_packages: Vec, -} - -impl Package { - /// Creates a new package - pub fn new ( - name: String, - repo: String, - branch: String, - build_config: BuildConfig, - target_configs: Vec, - sub_packages: Vec - ) -> Package { - Package { - name, - repo, - branch, - build_config, - target_configs, - sub_packages, - } - } - - /// Parses a package contained in a folder - /// The folder must contain a config toml file - /// # Arguments - /// * `path` - The path to the folder containing the package - pub fn parse_packages(path: &str) -> Vec { - let mut packages: Vec = Vec::new(); - // parse the root toml file, eg: packages = ["Ybeichen/redis, redis-7.0.12"] - let (build_config_toml, _ , _) = parse_config(path, false); - for package in build_config_toml.packages { - let deets = package.split_whitespace().collect::>(); - if deets.len() != 2 { - log(LogLevel::Error, "Packages must be in the form of \" \""); - std::process::exit(1); - } - let repo = deets[0].to_string().replace(",", ""); - let branch = deets[1].to_string(); - let name = repo.split("/").collect::>()[1].to_string(); - let source_dir = format!("./ruxgo_bld/packages/{}/", name); - let mut sub_packages: Vec = Vec::new(); - // git clone packages - if !Path::new(&source_dir).exists() { - fs::create_dir_all(&source_dir) - .unwrap_or_else(|err| { - log(LogLevel::Error, &format!("Failed to create {}: {}", source_dir, err)); - std::process::exit(1); - }); - log(LogLevel::Info, &format!("Created {}", source_dir)); - log(LogLevel::Log, &format!("Cloning {} into {}...", repo, source_dir)); - let repo_https = format!("https://mirror.ghproxy.com/https://github.com/{}", repo); - let mut cmd = Command::new("git"); - cmd.arg("clone") - .arg("--branch") - .arg(&branch) - .arg(&repo_https) - .arg(&source_dir) - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); - let output = cmd.output().expect("Failed to execute git clone"); - if !output.status.success() { - log(LogLevel::Error, &format!("Failed to clone {} branch {} into {}", repo, branch, source_dir)); - std::process::exit(1); - } - } else { - log(LogLevel::Log, &format!("{} already exists!", source_dir)); - } - #[cfg(target_os = "linux")] - let pkg_toml = format!("{}/config_linux.toml", source_dir).replace("//", "/"); - #[cfg(target_os = "windows")] - let pkg_toml = format!("{}/config_win32.toml", source_dir).replace("//", "/"); - let (pkg_bld_config_toml, _, pkg_targets_toml) = parse_config(&pkg_toml, false); - log(LogLevel::Info, &format!("Parsed {}", pkg_toml)); - - // recursive parse all of the packages - if !pkg_bld_config_toml.packages.is_empty() { - sub_packages = Package::parse_packages(&pkg_toml); - for foreign_package in sub_packages.clone() { - packages.push(foreign_package); - } - } - - // get build_config - let mut build_config = pkg_bld_config_toml; - build_config.compiler = build_config_toml.compiler.clone(); // use current compiler - - // get tgt_config - let mut target_configs = Vec::new(); - let tgt_configs = pkg_targets_toml; - for mut tgt in tgt_configs { - if tgt.typ != "dll" && tgt.typ != "static" && tgt.typ != "object" { - continue; - } - // concatenate to generate a new src path and include path - tgt.src = format!("{}/{}", source_dir, tgt.src) - .replace("\\", "/") - .replace("/./", "/") - .replace("//", "/"); - let tgt_include_dir = tgt.include_dir - .iter() - .map(|include| { - format!("{}/{}", source_dir, include) - .replace("\\", "/") - .replace("/./", "/") - .replace("//", "/") - }) - .collect(); - tgt.include_dir = tgt_include_dir; - target_configs.push(tgt); - } - packages.push(Package::new(name, repo, branch, build_config, target_configs, sub_packages)); - } - - // sort and remove duplicate packages - packages.sort_by_key(|a| a.name.clone()); - packages.dedup_by_key(|a| a.name.clone()); - packages - } -} \ No newline at end of file +pub mod log; +pub mod env; +pub mod features; \ No newline at end of file diff --git a/src/utils/env.rs b/src/utils/env.rs new file mode 100644 index 0000000..02b0760 --- /dev/null +++ b/src/utils/env.rs @@ -0,0 +1,30 @@ +//! Environment Configuration + +use crate::parser::{OSConfig, PlatformConfig, QemuConfig}; + +// This function is used to configure environment variables +pub fn config_env(os_config: &OSConfig,) { + if os_config != &OSConfig::default() && os_config.platform != PlatformConfig::default() { + std::env::set_var("RUX_ARCH", &os_config.platform.arch); + std::env::set_var("RUX_PLATFORM", &os_config.platform.name); + std::env::set_var("RUX_SMP", &os_config.platform.smp); + std::env::set_var("RUX_MODE", &os_config.platform.mode); + std::env::set_var("RUX_LOG", &os_config.platform.log); + std::env::set_var("RUX_TARGET", &os_config.platform.target); + if os_config.platform.qemu != QemuConfig::default() { + // ip and gw is for QEMU user netdev + std::env::set_var("RUX_IP", &os_config.platform.qemu.ip); + std::env::set_var("RUX_GW", &os_config.platform.qemu.gw); + // v9p option + if os_config.platform.qemu.v9p == "y" { + std::env::set_var("RUX_9P_ADDR", "127.0.0.1:564"); + std::env::set_var("RUX_ANAME_9P", "./"); + std::env::set_var("RUX_PROTOCOL_9P", "9P2000.L"); + } + } + // musl + if os_config.ulib == "ruxmusl" { + std::env::set_var("RUX_MUSL", "y"); + } + } +} diff --git a/src/features.rs b/src/utils/features.rs similarity index 96% rename from src/features.rs rename to src/utils/features.rs index c302284..28b28de 100644 --- a/src/features.rs +++ b/src/utils/features.rs @@ -1,4 +1,7 @@ -use crate::utils::{OSConfig, log, LogLevel}; +//! Features Module + +use crate::parser::OSConfig; +use crate::utils::log::{log, LogLevel}; pub fn cfg_feat(os_config: &OSConfig) -> (Vec, Vec) { let mut lib_features = vec![ diff --git a/src/utils/log.rs b/src/utils/log.rs new file mode 100644 index 0000000..98f18c7 --- /dev/null +++ b/src/utils/log.rs @@ -0,0 +1,70 @@ +//! Log Module + +use std::sync::{RwLock, Once}; +use colored::Colorize; + +static INIT: Once = Once::new(); +static LOG_LEVEL: RwLock = RwLock::new(LogLevel::Info); + +/// This enum is used to represent the different log levels +#[derive(PartialEq, PartialOrd, Debug)] +pub enum LogLevel { + Debug, + Info, + Log, + Warn, + Error, +} + +/// Initializes the log level, which is called only once when the program starts +fn init_log_level() { + let level = std::env::var("RUXGO_LOG_LEVEL").unwrap_or_else(|_| "Info".to_string()); + let log_level = match level.as_str() { + "Debug" => LogLevel::Debug, + "Info" => LogLevel::Info, + "Log" => LogLevel::Log, + "Warn" => LogLevel::Warn, + "Error" => LogLevel::Error, + _ => LogLevel::Log, + }; + + // Use write lock to update the log level + let mut write_lock = LOG_LEVEL.write().unwrap(); + *write_lock = log_level; +} + +/// This function is used to log messages to the console +/// # Arguments +/// * `level` - The log level of the message +/// * `message` - The message to log +/// # Example +/// ``` +/// log(LogLevel::Info, "Hello World!"); +/// log(LogLevel::Error, &format!("Something went wrong! {}", error)); +/// ``` +/// +/// # Level setting +/// The log level can be set by setting the environment variable `RUXGO_LOG_LEVEL` +/// to one of the following values: +/// * `Debug` +/// * `Info` +/// * `Log` +/// * `Warn` +/// * `Error` +/// If the environment variable is not set, the default log level is `Log` +pub fn log(level: LogLevel, message: &str) { + INIT.call_once(|| { + init_log_level(); + }); + let level_str = match level { + LogLevel::Debug => "[DEBUG]".purple(), + LogLevel::Info => "[INFO]".blue(), + LogLevel::Log => "[LOG]".green(), + LogLevel::Warn => "[WARN]".yellow(), + LogLevel::Error => "[ERROR]".red(), + }; + // Use read lock to check log level + if level >= *LOG_LEVEL.read().unwrap() { + println!("{} {}", level_str, message); + } +}