大改了new和build指令

This commit is contained in:
Cloyir 2023-05-12 09:51:42 +08:00
parent cf186539d9
commit aad0727e8a
10 changed files with 288 additions and 68 deletions

View File

@ -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`文件就可以声明该目录下所有应包括的头文件, 包括子目录。这样构建的库项目会很清晰

17
src/cbuild/command.rs Normal file
View File

@ -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)
}
}

125
src/cbuild/files.rs Normal file
View File

@ -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<File>,
/// 入口文件以外的.c/.cpp文件
pub c_files: Vec<File>,
/// .h/.hpp文件
pub h_files: Vec<File>,
}
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<File>,
h_files: &mut Vec<File>,
) -> 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,
}
}
}

View File

@ -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<String>,
/// 采用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<dyn BuildConfigTrait> = 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<PathBuf> = 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(())

View File

@ -2,8 +2,6 @@ use clap::Parser;
use crate::{const_value::console_log, rtools};
mod text;
#[derive(Parser, Debug)]
pub struct CommandsNew {
/// 项目名称

View File

@ -1,14 +0,0 @@
// 返回生成文件的文本
pub fn get_readme_text() -> &'static str {
""
}
pub fn get_maincpp_text() -> &'static str {
r##"#include <iostream>
int main() {
std::cout << "Hello World!" << std::endl;
}
"##
}

View File

@ -1,7 +1,5 @@
use clap::Parser;
use crate::rtools;
#[derive(Parser, Debug)]
pub struct CommandsTest {
/// 每完成一个测试暂停一次

View File

@ -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),

29
src/rtools/config.rs Normal file
View File

@ -0,0 +1,29 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct RConfig {
mingw_path: Option<String>,
}
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<PathBuf> {
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;
}
}

View File

@ -1,3 +1,5 @@
pub mod config;
use std::path::{Path, PathBuf};
/// 获取rust-cmaker文件夹目录