From aad0727e8aad7ee903d6a68917109ba6447bd94a Mon Sep 17 00:00:00 2001 From: Cloyir Date: Fri, 12 May 2023 09:51:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E6=94=B9=E4=BA=86new=E5=92=8Cbuild?= =?UTF-8?q?=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++++++++--- src/cbuild/command.rs | 17 ++++++ src/cbuild/files.rs | 125 +++++++++++++++++++++++++++++++++++++++++ src/cbuild/mod.rs | 126 +++++++++++++++++++++++++++++------------- src/cnew/mod.rs | 2 - src/cnew/text.rs | 14 ----- src/ctest/mod.rs | 2 - src/main.rs | 6 +- src/rtools/config.rs | 29 ++++++++++ src/rtools/mod.rs | 2 + 10 files changed, 288 insertions(+), 68 deletions(-) create mode 100644 src/cbuild/command.rs create mode 100644 src/cbuild/files.rs delete mode 100644 src/cnew/text.rs create mode 100644 src/rtools/config.rs diff --git a/README.md b/README.md index b83ba4e..32b0079 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,42 @@ > 2023年了谁还在用传统cmake啊(大嘘) -用rust封装MinGW的命令,方便一键在vscode中构建c/c++简单项目,灵感来自cargo +用rust封装MinGW的`gcc/g++`等命令,方便一键在vscode中构建c/c++简单项目,灵感来自rust的包管理器cargo 现在是开发初期 #### 快速使用 -1. 将本程序加入到Path、塞到MinGW/bin目录下甚至是用路径直接使用, 这个随你 - * 没错你最好安装一个MinGW并将其的bin目录放到你的环境变量里 -2. 或许你会觉得它的名字过长了, 可以给它改一个你喜欢的简称, 比如 `rcm` , 之后我们就都用这个名字了 +1. 将本项目找个文件夹放好 + * 本项目需要一个`MinGW`并将其的`bin`目录放到你的环境变量里 + * 如果可能请直接将`MinGW`放到项目目录里,这样能省一点配置的时间 +2. 将本项目的bin目录加入到你的环境变量 + * 目前`bin`目录下应该只有一个名为`rcm`的可执行程序 + * 本项目构建出来的程序就是这个程序 3. 找到你放c/c++项目的根目录, 输入 `rcm new project` 创建一个名为`project`的新项目 -4. 进入生成的project文件夹, 用vscode打开它(`cd project`, `code .`) -5. 在src目录下编写你的程序, 默认已经生成好了一个main.cpp -6. 使用`rcm build -r`构建程序并运行, 或者你需要`rcm build -o 3`进行一个o3优化 -7. 如果你没有配置好MinGW的环境变量, 你需要显式指定MinGW文件夹的路径, 详情见`rcm build -h` +4. 进入生成的project文件夹, 用vscode打开它(`cd project`; `code .`) +5. 在src目录下编写你的程序 +6. 使用`rcm run`构建程序并运行, 或者你需要`rcm build --release`进行一个o3优化 +7. 你可以显式指定`MinGW`文件夹的路径, 详情见`rcm build -h`,你或许可以用这个实现交叉编译 8. 目前只能构建目录下`src/`文件夹里的 .c/.cpp 文件 +9. 默认统一使用`g++`指令来编译 #### 已知问题 1. 代码是赶出来的, 写的依托 2. 没有链接库的功能 -3. 不能判断哪些文件是修改过的, 所以只能全部编译一遍, 很耗时, 而且不知道怎么修 +3. 不能判断哪些文件是修改过的, 所以只能全部编译一遍, 很耗时, 而且大概率以后也不会改 4. 只能指定目录下`src/`文件夹里的内容来编译以及只能输出到`target/bin/`目录下, 之后或许会增加新的编译选项 + * 可能会影响到写学校的作业,但管他呢 5. 需要手动配置环境变量 6. 统一的用g++进行编译, 应该判断如果全是.c的文件就用gcc编译 + +#### rcm项目构建说明 + +1. 默认情况下, rcm只会构建`root/src`目录下的文件, 你应当将项目中的代码都放在此文件夹下, 这样你可以在根目录下放点别的, 比如一份`README.md`文档 +2. 如果`root/src`中仅有一份`.c`或`.cpp`文件, 则会将它视为入口直接编译为一份二进制程序 +3. `root/src`中同级目录下不应存在任何文件名相同但扩展名不同的文件, 比如`main.cpp`和`main.c`不应同时出现在`root/src`目录下 +4. 默认情况下, 如果要构建二进制程序, src目录下应该放有一份`main.cpp`或`main.c`作为入口, 你应该在这里放置主函数 +5. 默认情况下, `root/src/bin`下的每一个`.c/.cpp`文件都会独立作为入口构建一份二进制程序, 每个入口程序间互不干扰绕 +6. 在构建时会忽略`*/test`文件夹下的所有文件, 这样你可以写一点测试 +7. 推荐在`root/src`的每个文件夹(包括`src`文件夹它自己)下放一份`mod.h`文件, 引用这个`mod.h`文件就可以声明该目录下所有应包括的头文件, 包括子目录。这样构建的库项目会很清晰 \ No newline at end of file diff --git a/src/cbuild/command.rs b/src/cbuild/command.rs new file mode 100644 index 0000000..6903d8f --- /dev/null +++ b/src/cbuild/command.rs @@ -0,0 +1,17 @@ +use std::process::Command; + +use crate::rtools::config::RConfig; + +use super::CommandsBuild; + +/// 得到可能带有路径名的gcc/g++指令 +pub fn get_gcc_command(config: &CommandsBuild, r_config: &RConfig) -> Command { + let gcc = if config.c { "gcc" } else { "g++" }; + + if let Some(bin_path) = r_config.get_bin_path() { + let command: String = bin_path.join(gcc).into_os_string().into_string().unwrap(); + Command::new(&command) + } else { + Command::new(gcc) + } +} diff --git a/src/cbuild/files.rs b/src/cbuild/files.rs new file mode 100644 index 0000000..a626bae --- /dev/null +++ b/src/cbuild/files.rs @@ -0,0 +1,125 @@ +use std::path::PathBuf; + +#[derive(Debug)] +pub struct File { + pub path: PathBuf, + pub name: String, + pub extension: String, +} + +impl File { + pub fn new(path: PathBuf, name: String, extension: String) -> Self { + Self { + path, + name, + extension, + } + } +} + +#[derive(Debug)] +pub struct FilesSet { + /// 构建的入口文件 + pub entry_files: Vec, + /// 入口文件以外的.c/.cpp文件 + pub c_files: Vec, + /// .h/.hpp文件 + pub h_files: Vec, +} + +impl FilesSet { + /// 输入root/src的路径, 整理该路径下的所有文件 + pub fn read(path: PathBuf) -> Self { + let mut entry_files = Vec::new(); + let mut c_files = Vec::new(); + let mut h_files = Vec::new(); + + /// 将路径文件夹内所有文件信息整理到集合中 + fn read_dir( + path: &PathBuf, + c_files: &mut Vec, + h_files: &mut Vec, + ) -> std::io::Result<()> { + for file in std::fs::read_dir(path)? { + let file = file?; + if file.file_type()?.is_dir() { + let name = file.file_name().to_string_lossy().into_owned(); + // 忽略test文件夹 + if name == "test" { + continue; + } + let new_path = path.join(name); + read_dir(&new_path, c_files, h_files)?; + } else { + if let Some(name) = file.path().file_name() { + let file_name: String = name.to_string_lossy().into_owned(); + let extension = std::path::Path::new(&file_name).extension().unwrap(); + let file_name: String = file + .path() + .file_stem() + .unwrap() + .to_string_lossy() + .into_owned(); + let extension: String = extension.to_string_lossy().into_owned(); + if extension == "c" || extension == "cpp" { + c_files.push(File::new(file.path(), file_name, extension)); + } else if extension == "h" || extension == "hpp" { + h_files.push(File::new(file.path(), file_name, extension)); + } + } + } + } + Ok(()) + } + + for file in std::fs::read_dir(&path).unwrap() { + let file = file.unwrap(); + if file.file_type().unwrap().is_dir() { + let name = file.file_name().to_string_lossy().into_owned(); + // 忽略test文件夹 + if name == "test" { + continue; + } + // 将src/bin文件夹的所有.c/.cpp文件视作入口文件 + // 将src下其它文件夹视作正常.c/.cpp文件 + if name == "bin" { + let new_path = path.join("bin"); + read_dir(&new_path, &mut entry_files, &mut h_files).unwrap(); + } else { + let new_path = path.join(name); + read_dir(&new_path, &mut c_files, &mut h_files).unwrap(); + } + } else { + // 如果是文件 + if let Some(name) = file.path().file_name() { + let file_name = name.to_string_lossy().into_owned(); + let file_path = file.path(); + let extension = std::path::Path::new(&file_name).extension().unwrap(); + let file_name: String = file + .path() + .file_stem() + .unwrap() + .to_string_lossy() + .into_owned(); + let extension: String = extension.to_string_lossy().into_owned(); + let file = File::new(file_path, file_name, extension); + if file.extension == "c" || file.extension == "cpp" { + if file.name == "main" { + entry_files.push(file); + } else { + c_files.push(file); + } + } else if file.extension == "h" || file.extension == "hpp" { + h_files.push(file); + } + } + } + } + + Self { + entry_files, + c_files, + h_files, + } + } +} diff --git a/src/cbuild/mod.rs b/src/cbuild/mod.rs index b45271e..a7b2619 100644 --- a/src/cbuild/mod.rs +++ b/src/cbuild/mod.rs @@ -1,11 +1,13 @@ mod cmake_o; +mod command; mod config; +mod files; mod gcc; mod utils; -use std::path::PathBuf; +use crate::const_value::console_log; -use config::{BuildConfig, BuildConfigTrait}; +use self::files::FilesSet; #[derive(clap::Parser, Debug)] pub struct CommandsBuild { @@ -21,68 +23,116 @@ pub struct CommandsBuild { #[clap(short, long, default_value = "false")] run: bool, - /// MinGW编译器地址, 不填默认已配置为环境变量 + /// MinGW编译器地址, 不填则会从rcm的config文件中查找 #[clap(short, long, default_value = None)] mingw: Option, + + /// 采用gcc编译指令而不是g++ + #[clap(short, default_value = "false")] + c: bool, } impl CommandsBuild { + /// rcm run指令 pub fn new_run() -> CommandsBuild { CommandsBuild { quiet: true, release: false, run: true, mingw: None, + c: false, } } } pub fn run(config: CommandsBuild) -> Result<(), std::io::Error> { + // 获取项目路径 let project_path = std::env::current_dir().unwrap(); - println!("{:?}", project_path); - // 获取配置 - let config: Box = Box::new(BuildConfig::new( - config.mingw, - String::from("main"), - config.release, - config.run, - )); + // 获取指定的路径 + // let bin_path = project_path.join("target").join("bin"); + let bin_path = project_path.join("target"); + std::fs::create_dir_all(&bin_path).unwrap(); // 确保bin目录存在 - println!( - "{}: start to compile 开始编译", - crate::const_value::THIS_PROJECT_NAME - ); - - // 递归所有src目录下的.c/.cpp文件并编译为.o文件输出到@/target/o目录下 - let mut file_set: Vec = Vec::new(); // 所有应临时存储的文件路径集合 - std::fs::create_dir_all(config.getpath_target_o()).unwrap(); // 先创建这层目录 - cmake_o::cmake_o(&config, PathBuf::from(config.getpath_src()), &mut file_set)?; - - // 遍历@/target/o联合编译到@/target/bin中 - std::fs::create_dir_all(config.getpath_target_bin())?; // 先创建这层目录 - if PathBuf::from(config.getpath_target_file()).exists() { - std::fs::remove_file(config.getpath_target_file())?; // 先删除目标文件 + if !config.quiet { + console_log(&format!("项目路径{:?}", project_path)); + console_log(&format!("start to compile 开始编译")); } - gcc::make_o_files_to_bin( - config.getpath_gpp(), - &mut file_set, - config.getpath_target_file(), - config.get_extra_final_args(), - )?; - // 删除@/target/o文件夹 - std::fs::remove_dir_all(config.getpath_target_o())?; + // 将src目录下的文件整理归纳 + let files: FilesSet = { + let mut files = FilesSet::read(project_path.join("src")); + // 如果src目录下仅有一个.c/.cpp文件, 则视为唯一项目入口 + if files.entry_files.len() == 0 && files.c_files.len() == 1 { + let t = files.entry_files; + files.entry_files = files.c_files; + files.c_files = t; + } + files + }; - // 构建完之后判断是否要立即运行 - if config.need_run() { - println!("{}: 开始执行...", crate::const_value::THIS_PROJECT_NAME); - let mut command = std::process::Command::new(config.getpath_target_file()); + // build指令需要读取rust-cmaker下的config文件 + let r_config = crate::rtools::config::RConfig::read(); + let mut need_run = None; + + // 将每一个entry_file构建到指定目录下 + for entry_file in &files.entry_files { + let mut gcc_cmd = command::get_gcc_command(&config, &r_config); + + gcc_cmd.arg("-o").arg({ + let mut target_name = entry_file.name.clone(); + if cfg!(target_os = "windows") { + target_name += ".exe"; + } + let target_path = bin_path.join(target_name); + target_path + }); + + gcc_cmd.arg(&entry_file.path); + + for c_file in &files.c_files { + gcc_cmd.arg(&c_file.path); + } + + if !config.run { + console_log(&format!("{:?}", gcc_cmd)); + } + + let output = gcc_cmd.output(); + + match output { + Ok(output) => { + if !config.run { + console_log(&format!("编译成功: {:?}", output)); + } + } + Err(err) => { + if !config.run { + console_log(&format!("执行出错: {}", err)); + } + continue; + } + } + + // 构建完之后判断是否要立即运行 + if config.run && (files.entry_files.len() == 1 || entry_file.name == "main") { + need_run = Some(entry_file); + } + } + + if let Some(run_file) = need_run { + if !config.quiet { + console_log(&format!("{}开始执行: ", run_file.name)); + } + let mut command: std::process::Command = + std::process::Command::new(bin_path.join(&run_file.name)); command.stdin(std::process::Stdio::inherit()); command.stdout(std::process::Stdio::inherit()); command.stderr(std::process::Stdio::inherit()); command.output()?; - println!("\n{}: 执行完毕...", crate::const_value::THIS_PROJECT_NAME); + if !config.quiet { + console_log(&format!("{}执行完毕: ", run_file.name)); + } } Ok(()) diff --git a/src/cnew/mod.rs b/src/cnew/mod.rs index b313010..5e41b80 100644 --- a/src/cnew/mod.rs +++ b/src/cnew/mod.rs @@ -2,8 +2,6 @@ use clap::Parser; use crate::{const_value::console_log, rtools}; -mod text; - #[derive(Parser, Debug)] pub struct CommandsNew { /// 项目名称 diff --git a/src/cnew/text.rs b/src/cnew/text.rs deleted file mode 100644 index dcf894e..0000000 --- a/src/cnew/text.rs +++ /dev/null @@ -1,14 +0,0 @@ -// 返回生成文件的文本 - -pub fn get_readme_text() -> &'static str { - "" -} - -pub fn get_maincpp_text() -> &'static str { - r##"#include - -int main() { - std::cout << "Hello World!" << std::endl; -} -"## -} diff --git a/src/ctest/mod.rs b/src/ctest/mod.rs index 12e408e..5ecd984 100644 --- a/src/ctest/mod.rs +++ b/src/ctest/mod.rs @@ -1,7 +1,5 @@ use clap::Parser; -use crate::rtools; - #[derive(Parser, Debug)] pub struct CommandsTest { /// 每完成一个测试暂停一次 diff --git a/src/main.rs b/src/main.rs index 9bc8061..878111f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,13 +22,13 @@ struct Args { enum Commands { /// 在当前目录新建项目 New(cnew::CommandsNew), - /// 构建当前打开的项目 + /// 构建当前目录下的项目 Build(cbuild::CommandsBuild), /// `rcm build -q -r` 的简写 Run, - /// 生成静态库并存储到静态仓库 + /// 保存项目为库项目并存储到仓库中 Store(cstore::CommandsStore), - /// 在当前项目下添加静态库依赖 + /// 在当前项目下添加库依赖 Add(cadd::CommandsAdd), /// 运行单元测试 Test(ctest::CommandsTest), diff --git a/src/rtools/config.rs b/src/rtools/config.rs new file mode 100644 index 0000000..936748c --- /dev/null +++ b/src/rtools/config.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct RConfig { + mingw_path: Option, +} + +impl RConfig { + /// 从config.json中读取配置文件 + pub fn read() -> Self { + let path = super::get_home_path().join("config.json"); + let json = std::fs::read(path).unwrap(); + let json = String::from_utf8(json).unwrap(); + let ans = serde_json::from_str(&json).unwrap(); + return ans; + } + + /// 从config.json中获取mingw文件夹名称并返回其bin目录 + pub fn get_bin_path(&self) -> Option { + if let Some(dir_name) = &self.mingw_path { + // 路径为: rust-cmaker/mingw_path/bin + let path: PathBuf = super::get_home_path().join(dir_name).join("bin"); + return Some(path); + } + return None; + } +} diff --git a/src/rtools/mod.rs b/src/rtools/mod.rs index b1557e0..981f69c 100644 --- a/src/rtools/mod.rs +++ b/src/rtools/mod.rs @@ -1,3 +1,5 @@ +pub mod config; + use std::path::{Path, PathBuf}; /// 获取rust-cmaker文件夹目录