From d4446457424def421a5a1e96f382dd6d4c20f3f2 Mon Sep 17 00:00:00 2001 From: Ybeichen Date: Fri, 8 Mar 2024 21:58:46 +0800 Subject: [PATCH] Better get_srcs and arrange_targets --- doc/ruxgo_book/src/installation.md | 2 +- src/builder.rs | 115 ++++++-------- src/commands.rs | 12 +- src/parser.rs | 244 ++++++++++++++++------------- 4 files changed, 182 insertions(+), 191 deletions(-) diff --git a/doc/ruxgo_book/src/installation.md b/doc/ruxgo_book/src/installation.md index b116ef1..3d3f60e 100644 --- a/doc/ruxgo_book/src/installation.md +++ b/doc/ruxgo_book/src/installation.md @@ -1,6 +1,6 @@ # Ruxgo 安装 -要从源代码构建`ruxgo`可执行文件,你首先需要安装 Rust 和 Cargo。按照[Rust安装页面](https://www.rust-lang.org/tools/install)上的说明操作。Ruxgo 目前至少需要 Rust 1.70 版本。 +要从源代码构建`ruxgo`可执行文件,你首先需要安装 Rust 和 Cargo。按照[Rust安装页面](https://www.rust-lang.org/tools/install)上的说明操作。Ruxgo 目前至少需要 Rust 1.74 版本。 一旦你安装了 Rust,就可以使用以下命令来构建和安装 Ruxgo: diff --git a/src/builder.rs b/src/builder.rs index 26febb8..c87ea04 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -565,23 +565,27 @@ impl<'a> Target<'a> { cmd.push(' '); cmd.push_str(RUXLIBC_BIN); cmd.push(' '); + let mode = if !self.os_config.platform.mode.is_empty() { + &self.os_config.platform.mode + } else { + "debug" + }; cmd.push_str(&format!( "{}/target/{}/{}/{}", - BUILD_DIR, - &self.os_config.platform.target, - &self.os_config.platform.mode, - RUXLIBC_RUST_LIB + BUILD_DIR, &self.os_config.platform.target, mode, RUXLIBC_RUST_LIB )); } else if self.os_config.ulib == "ruxmusl" { cmd.push(' '); cmd.push_str(RUXMUSL_BIN); cmd.push(' '); + let mode = if !self.os_config.platform.mode.is_empty() { + &self.os_config.platform.mode + } else { + "debug" + }; cmd.push_str(&format!( "{}/target/{}/{}/{}", - BUILD_DIR, - &self.os_config.platform.target, - &self.os_config.platform.mode, - RUXMUSL_RUST_LIB + BUILD_DIR, &self.os_config.platform.target, mode, RUXMUSL_RUST_LIB )); } @@ -776,73 +780,42 @@ impl<'a> Target<'a> { /// Recursively gets all the source files in the given root path /// # Notes /// The source is first filtered through the `src_only` and `src_exclude` fields - fn get_srcs(&mut self, root_path: &str) -> Vec { - if root_path.is_empty() { - return Vec::new(); - } - let root_dir = PathBuf::from(root_path); - let mut srcs: Vec = Vec::new(); - let root_entries = std::fs::read_dir(root_dir).unwrap_or_else(|_| { - log( - LogLevel::Error, - &format!("Could not read directory: {}", root_path), - ); - std::process::exit(1); - }); - let src_only: Vec<&str> = self - .target_config - .src_only - .iter() - .map(AsRef::as_ref) - .collect(); - let src_exclude: Vec<&str> = self - .target_config - .src_exclude - .iter() - .map(AsRef::as_ref) - .collect(); - - // Iterate over all entrys - for entry in root_entries { - let entry = entry.unwrap(); - let path = entry - .path() - .to_str() - .unwrap() - .to_string() - .replace('\\', "/"); // if windows's path - // 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), - ); + fn get_srcs(&mut self, root_path: &str) { + for entry in WalkDir::new(root_path).into_iter().filter_map(|e| e.ok()) { + let path = entry.path(); + let path_str = path.to_str().unwrap_or_default(); + #[cfg(target_os = "windows")] + let path_str = path_str.replace('\\', "/"); + if self.should_exclude(path_str) { continue; } - if entry.path().is_dir() { - srcs.append(&mut self.get_srcs(&path)); - } else { - // Inclusion logic: Apply src_only logic only to files - 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; - } - if path.ends_with(".cpp") || path.ends_with(".c") { - self.add_src(path); + if path.is_file() { + if let Some(ext) = path.extension() { + if (ext == "cpp" || ext == "c") && self.should_include(path_str) { + self.add_src(path_str.to_owned()); + } } } } + } - srcs + /// Exclusion logic: Check if the path is in src_exclude + fn should_exclude(&self, path: &str) -> bool { + self.target_config + .src_exclude + .iter() + .any(|excluded| path.contains(excluded)) + } + + /// Inclusion logic: Apply src_only logic only to files + fn should_include(&self, path: &str) -> bool { + if self.target_config.src_only.is_empty() { + return true; + } + self.target_config + .src_only + .iter() + .any(|included| path.contains(included)) } /// Adds a source file to the target's srcs field @@ -867,7 +840,7 @@ impl<'a> Target<'a> { fn get_src_obj_name(&self, src_name: &str) -> String { let mut obj_name = String::new(); obj_name.push_str(OBJ_DIR); - obj_name.push('-'); + obj_name.push('/'); obj_name.push_str(&self.target_config.name); obj_name.push('-'); obj_name.push_str(src_name); @@ -878,7 +851,7 @@ impl<'a> Target<'a> { /// Returns a vector of .h or .hpp files the given C/C++ depends on fn get_dependant_includes(&mut self, path: &str) -> Vec { let mut result = HashSet::new(); - //Use the stack to handle recursive paths + // Use the stack to handle recursive paths let mut to_process = vec![path.to_string()]; let include_substrings: HashSet = self .get_include_substrings(path) diff --git a/src/commands.rs b/src/commands.rs index d585273..5545ffb 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -396,7 +396,10 @@ fn build_os(os_config: &OSConfig, ulib: &str, rux_feats: &[String], lib_feats: & } let target = format!("--target {}", os_config.platform.target); - let mode = format!("--{}", os_config.platform.mode); + let mut mode = String::new(); + if !os_config.platform.mode.is_empty() { + mode = format!("--{}", os_config.platform.mode); + } let os_ulib = format!("-p {}", ulib); let verbose = match os_config.platform.v.as_str() { "1" => "-v", @@ -1024,16 +1027,13 @@ 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) { #[cfg(target_os = "linux")] - let (build_config, os_config, targets) = parser::parse_config("./config_linux.toml", true); + let (build_config, os_config, targets) = parser::parse_config("./config_linux.toml", false); #[cfg(target_os = "windows")] let (build_config, os_config, targets) = utils::parse_config("./config_win32.toml", true); let mut num_exe = 0; let mut exe_target: Option<&TargetConfig> = None; - if targets.is_empty() { - log(LogLevel::Error, "No targets in config"); - std::process::exit(1); - } + for target in &targets { if target.typ == "exe" { num_exe += 1; diff --git a/src/parser.rs b/src/parser.rs index 013925d..41c8d14 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,12 +3,14 @@ use crate::builder::Target; use crate::utils::log::{log, LogLevel}; use serde::Serialize; +use std::collections::{HashMap, HashSet, VecDeque}; use std::default::Default; use std::fs::File; use std::process::Command; use std::sync::{Arc, RwLock}; use std::{io::Read, path::Path}; use toml::{Table, Value}; +use walkdir::WalkDir; /// Struct descibing the build config of the local project #[derive(Debug, Clone)] @@ -238,102 +240,137 @@ impl TargetConfig { /// # 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('\\', "/"); + let src_path = Path::new(tgt_path); - // 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; + let walker = WalkDir::new(src_path) + .into_iter() + .filter_entry(|e| self.should_include(e.path()) && !self.should_exclude(e.path())); + for entry in walker.filter_map(|e| e.ok()) { + let path = entry.path(); + if path.is_file() { + if let Some(ext) = path.extension() { + if ext == "cpp" || ext == "c" { + if let Some(file_path_str) = path.to_str() { + #[cfg(target_os = "windows")] + let formatted_path_str = file_path_str.replace('\\', "/"); + #[cfg(target_os = "linux")] + let formatted_path_str = file_path_str.to_string(); + src_names.push(formatted_path_str); + } + } + } } + } - // 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; - } + src_names + } + + /// Exclusion logic: Check if the path is in src_exclude + fn should_exclude(&self, path: &Path) -> bool { + self.src_exclude + .iter() + .any(|excluded| path.to_str().map_or(false, |p| p.contains(excluded))) + } + + /// Inclusion logic: Apply src_only logic only to files + fn should_include(&self, path: &Path) -> bool { + if self.src_only.is_empty() { + return true; + } + self.src_only + .iter() + .any(|included| path.to_str().map_or(false, |p| p.contains(included))) + } + + /// Checks for duplicate source files in the target + fn check_duplicate_srcs(&self) { + let mut src_file_names = self.get_src_names(&self.src); + src_file_names.sort_unstable(); + src_file_names.dedup(); + let mut last_name: Option = None; + let mut duplicates = Vec::new(); - 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; + for file_name in &src_file_names { + if let Some(ref last) = last_name { + if last == file_name { + duplicates.push(file_name.clone()); } - let file_path = entry.path(); - let file_path_str = file_path.to_str().unwrap(); - src_names.push(file_path_str.to_string()); } + last_name = Some(file_name.clone()); + } + if !duplicates.is_empty() { + log( + LogLevel::Error, + &format!("Duplicate source files found for target: {}", self.name), + ); + log(LogLevel::Error, "Source files must be unique"); + for duplicate in duplicates { + log(LogLevel::Error, &format!("Duplicate file: {}", duplicate)); + } + std::process::exit(1); } - - src_names } /// Rearrange the input targets + /// Using topological sorting to respect dependencies. 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); + // Create a mapping from the target name to the target configuration + let mut target_map: HashMap = targets + .into_iter() + .map(|target| (target.name.clone(), target)) + .collect(); + + // Build a graph and an in-degree table,and initialize + let mut graph: HashMap> = HashMap::new(); + let mut in_degree: HashMap = HashMap::new(); + for name in target_map.keys() { + graph.entry(name.clone()).or_default(); + in_degree.entry(name.clone()).or_insert(0); + } + + // Fill the graph and update the in-degree table + for target in target_map.values() { + for dep in &target.deps { + if let Some(deps) = graph.get_mut(dep) { + deps.push(target.name.clone()); + *in_degree.entry(target.name.clone()).or_insert(0) += 1; + } + } + } + + // Using topological sort + let mut queue: VecDeque = VecDeque::new(); + for (name, °ree) in &in_degree { + if degree == 0 { + queue.push_back(name.clone()); + } + } + let mut sorted_names = Vec::new(); + while let Some(name) = queue.pop_front() { + sorted_names.push(name.clone()); + if let Some(deps) = graph.get(&name) { + for dep in deps { + let degree = in_degree.entry(dep.clone()).or_default(); + *degree -= 1; + if *degree == 0 { + queue.push_back(dep.clone()); } - let temp = targets[i].clone(); - targets[i] = targets[j].clone(); - targets[j] = temp; - i = 0; - break; } - j += 1; } - i += 1; } - targets + + // Check for rings + if sorted_names.len() != target_map.len() { + log(LogLevel::Error, "Circular dependency detected"); + std::process::exit(1); + } + + // Rebuild the target list based on the sorted names + sorted_names + .into_iter() + .map(|name| target_map.remove(&name).unwrap()) + .collect() } } @@ -501,44 +538,25 @@ fn parse_targets(config: &Table, check_dup_src: bool) -> Vec { } // 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); - } + let mut names_set = HashSet::new(); + for target in &tgts { + if !names_set.insert(&target.name) { + log( + LogLevel::Error, + &format!("Duplicate target names found: {}", target.name), + ); + std::process::exit(1); } } // Checks for duplicate srcs in the target if check_dup_src { + log( + LogLevel::Info, + "Checking for duplicate srcs in all targets...", + ); 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), - ); - } + target.check_duplicate_srcs(); } } @@ -566,7 +584,7 @@ fn parse_platform(config: &Table) -> PlatformConfig { } }; let smp = parse_cfg_string(platform_table, "smp", "1"); - let mode = parse_cfg_string(platform_table, "mode", "release"); + let mode = parse_cfg_string(platform_table, "mode", ""); let log = parse_cfg_string(platform_table, "log", "warn"); let v = parse_cfg_string(platform_table, "v", ""); // determine whether enable qemu