Compare commits
8 Commits
v0.0.1-alp
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fb6ddb3e4 | |||
| 0f0fec5e64 | |||
| e21382ae29 | |||
| 2df87f19c1 | |||
| aad0727e8a | |||
| cf186539d9 | |||
| 2bee2f325f | |||
| c594cbf22a |
@ -7,3 +7,5 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.2.2", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
44
README.md
44
README.md
@ -4,27 +4,39 @@
|
||||
|
||||
> 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` , 之后我们就都用这个名字了
|
||||
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`
|
||||
8. 目前只能构建目录下`src/`文件夹里的 .c/.cpp 文件
|
||||
1. 将本项目找个文件夹放好
|
||||
* 然后将本项目的bin文件夹添加到你的环境变量中
|
||||
2. 将本项目的bin目录加入到你的环境变量
|
||||
* 目前`bin`目录下应该只有一个名为`rcm`的可执行程序
|
||||
* 本项目构建出来的程序就是这个程序
|
||||
3. 找到你放c/c++项目的根目录, 输入 `rcm new project` 以default为模板创建一个名为`project`的新项目
|
||||
4. 进入生成的project文件夹, 用vscode打开它(`cd project`; `code .`)
|
||||
5. 在src目录下编写你的程序
|
||||
6. 你可以显式指定`MinGW`文件夹的路径, 详情见`rcm build -h`,你或许可以用这个实现交叉编译
|
||||
7. 目前只能构建目录下`src/`文件夹里的 .c/.cpp 文件
|
||||
8. 默认统一使用`g++`指令来编译
|
||||
|
||||
#### 已知问题
|
||||
|
||||
1. 代码是赶出来的, 写的依托
|
||||
2. 没有链接库的功能
|
||||
3. 不能判断哪些文件是修改过的, 所以只能全部编译一遍, 很耗时, 而且不知道怎么修
|
||||
4. 只能指定目录下`src/`文件夹里的内容来编译以及只能输出到`target/bin/`目录下, 之后或许会增加新的编译选项
|
||||
5. 需要手动配置环境变量
|
||||
6. 统一的用g++进行编译, 应该判断如果全是.c的文件就用gcc编译
|
||||
1. 代码写的依托
|
||||
2. 不能判断哪些文件是修改过的, 所以只能全部编译一遍, 很耗时, 而且大概率以后也不会改
|
||||
3. 只能指定目录下`src/`文件夹里的内容来编译以及只能输出到`target/bin/`目录下, 之后或许会增加新的编译选项
|
||||
* 可能会影响到写学校的作业,但管他呢
|
||||
4. 需要手动配置环境变量
|
||||
|
||||
#### 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. rcm会将项目根目录下的lib文件夹内的所有以`lib`开头以`.a`结尾的文件作为静态链接库一起编译
|
||||
8. 推荐在`root/src`的每个文件夹(包括`src`文件夹它自己)下放一份`mod.h`文件, 引用这个`mod.h`文件就可以声明该目录下所有应包括的头文件, 包括子目录。这样构建的库项目会很清晰
|
||||
@ -1,46 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::config::BuildConfigTrait;
|
||||
|
||||
pub fn cmake_o(
|
||||
config: &Box<dyn BuildConfigTrait>,
|
||||
src_path: PathBuf,
|
||||
file_o_set: &mut Vec<PathBuf>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
// 遍历文件夹内每个子项
|
||||
let dir = std::fs::read_dir(src_path)?;
|
||||
for path in dir {
|
||||
let path = path?;
|
||||
// 如果子项为文件夹, 递归查找内部项
|
||||
if path.file_type()?.is_dir() {
|
||||
cmake_o(config, path.path(), file_o_set)?;
|
||||
continue;
|
||||
}
|
||||
// 判断是否为需要编译的文件, 即.c/.cpp文件
|
||||
let file_path = path;
|
||||
let file_name = String::from(file_path.file_name().to_str().unwrap());
|
||||
let file_type = super::utils::file_is_c_or_cpp(&file_name);
|
||||
if file_type.is_else() {
|
||||
continue;
|
||||
}
|
||||
// 找到需要编译的文件, 将其输出到target/o目录下
|
||||
let new_file_path =
|
||||
PathBuf::from(config.getpath_target_o()).join((*file_o_set).len().to_string() + ".o");
|
||||
|
||||
use super::gcc::make_file_to_target_o;
|
||||
match file_type {
|
||||
super::utils::FileType::C => {
|
||||
make_file_to_target_o(config.getpath_gcc(), &file_path.path(), &new_file_path)?;
|
||||
}
|
||||
super::utils::FileType::Cpp => {
|
||||
make_file_to_target_o(config.getpath_gpp(), &file_path.path(), &new_file_path)?;
|
||||
}
|
||||
super::utils::FileType::Else => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
file_o_set.push(new_file_path);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait BuildConfigTrait {
|
||||
fn getpath_gcc(&self) -> String;
|
||||
fn getpath_gpp(&self) -> String;
|
||||
fn getpath_src(&self) -> String;
|
||||
fn getpath_target_o(&self) -> String;
|
||||
fn get_project_name(&self) -> &str;
|
||||
fn getpath_target_bin(&self) -> String;
|
||||
fn getpath_target_file(&self) -> String;
|
||||
fn get_extra_final_args(&self) -> &Vec<String>;
|
||||
fn need_run(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuildConfig {
|
||||
compiler_path: Option<PathBuf>,
|
||||
project_path: PathBuf,
|
||||
project_name: String,
|
||||
extra_final_args: Vec<String>,
|
||||
need_run: bool,
|
||||
}
|
||||
|
||||
impl BuildConfig {
|
||||
pub fn new(
|
||||
compiler_path: Option<PathBuf>,
|
||||
project_name: String,
|
||||
build_options: i32,
|
||||
run: bool,
|
||||
) -> Self {
|
||||
let project_path = std::env::current_dir().unwrap();
|
||||
let mut extra = Vec::new();
|
||||
match build_options {
|
||||
1 => {
|
||||
extra.push(String::from("-O1"));
|
||||
}
|
||||
2 => {
|
||||
extra.push(String::from("-O2"));
|
||||
}
|
||||
3 => {
|
||||
extra.push(String::from("-O3"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Self {
|
||||
compiler_path,
|
||||
project_path,
|
||||
project_name,
|
||||
extra_final_args: extra,
|
||||
need_run: run,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildConfigTrait for BuildConfig {
|
||||
fn getpath_gcc(&self) -> String {
|
||||
return self.getpath_gpp(); // 此处需要修改
|
||||
}
|
||||
|
||||
fn getpath_gpp(&self) -> String {
|
||||
if self.compiler_path.is_none() {
|
||||
return String::from("g++");
|
||||
}
|
||||
String::from(
|
||||
self.compiler_path
|
||||
.clone()
|
||||
.unwrap()
|
||||
.join("bin")
|
||||
.join("g++")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn getpath_src(&self) -> String {
|
||||
String::from(self.project_path.clone().join("src").to_str().unwrap())
|
||||
}
|
||||
|
||||
fn getpath_target_o(&self) -> String {
|
||||
String::from(
|
||||
self.project_path
|
||||
.clone()
|
||||
.join("target")
|
||||
.join("o")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_project_name(&self) -> &str {
|
||||
&self.project_name
|
||||
}
|
||||
|
||||
fn getpath_target_bin(&self) -> String {
|
||||
String::from(
|
||||
self.project_path
|
||||
.clone()
|
||||
.join("target")
|
||||
.join("bin")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn getpath_target_file(&self) -> String {
|
||||
let file_name = super::utils::make_app_file_name(self.get_project_name());
|
||||
let ans = PathBuf::from(self.getpath_target_bin()).join(file_name);
|
||||
String::from(ans.to_str().unwrap())
|
||||
}
|
||||
|
||||
fn get_extra_final_args(&self) -> &Vec<String> {
|
||||
return &self.extra_final_args;
|
||||
}
|
||||
|
||||
fn need_run(&self) -> bool {
|
||||
return self.need_run;
|
||||
}
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// 根据单个.c/.cpp文件的路径创建.o文件并输出到new_file_path
|
||||
pub fn make_file_to_target_o(
|
||||
command: String,
|
||||
source_file_path: &PathBuf,
|
||||
new_file_path: &PathBuf,
|
||||
) -> Result<(), std::io::Error> {
|
||||
// 获取路径
|
||||
let source_file_path = source_file_path.to_str().unwrap();
|
||||
let new_file_path = new_file_path.to_str().unwrap();
|
||||
|
||||
// 运行指令
|
||||
let mut output = std::process::Command::new(command);
|
||||
output.args(["-o", new_file_path]);
|
||||
output.args(["-c", source_file_path]);
|
||||
let output = output.output()?;
|
||||
|
||||
// 输出调试信息
|
||||
let state = if output.status.success() {
|
||||
format!("{}: success to compile 编译成功", crate::PROJECT_NAME)
|
||||
} else {
|
||||
format!(
|
||||
"{}: failed to compile 编译失败 \n错误信息: {:?}",
|
||||
crate::PROJECT_NAME,
|
||||
String::from_utf8(output.stderr).unwrap()
|
||||
)
|
||||
};
|
||||
println!(
|
||||
"{}: file : {:?} ---> new file : {:?} ---> {}",
|
||||
crate::PROJECT_NAME,
|
||||
Path::new(source_file_path).file_name().unwrap(),
|
||||
Path::new(new_file_path).file_name().unwrap(),
|
||||
state
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 根据.o文件的集合构建可执行程序
|
||||
pub fn make_o_files_to_bin(
|
||||
command: String,
|
||||
source_paths: &mut Vec<PathBuf>,
|
||||
target_path: String,
|
||||
extra_args: &Vec<String>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
// 创建指令
|
||||
let mut output = std::process::Command::new(command);
|
||||
while !source_paths.is_empty() {
|
||||
let item = source_paths.pop().unwrap();
|
||||
output.arg(item.to_str().unwrap());
|
||||
}
|
||||
for item in extra_args {
|
||||
output.arg(item);
|
||||
}
|
||||
output.args(["-o", &target_path]);
|
||||
|
||||
// 运行指令
|
||||
let output = output.output()?;
|
||||
if output.status.success() {
|
||||
println!("{}: success to compile 编译成功", crate::PROJECT_NAME);
|
||||
} else {
|
||||
println!(
|
||||
"{}: failed to compile 编译失败\n{:?}",
|
||||
crate::PROJECT_NAME,
|
||||
output
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
mod cmake_o;
|
||||
mod config;
|
||||
mod gcc;
|
||||
mod utils;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use config::{BuildConfig, BuildConfigTrait};
|
||||
|
||||
pub fn build_project(
|
||||
build_options: i32,
|
||||
run: bool,
|
||||
mingw: Option<PathBuf>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let project_path = std::env::current_dir().unwrap();
|
||||
println!("{:?}", project_path);
|
||||
|
||||
// 获取配置
|
||||
let config: Box<dyn BuildConfigTrait> = Box::new(BuildConfig::new(
|
||||
mingw,
|
||||
String::from("main"),
|
||||
build_options,
|
||||
run,
|
||||
));
|
||||
|
||||
println!("{}: start to compile 开始编译", crate::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())?; // 先删除目标文件
|
||||
}
|
||||
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())?;
|
||||
|
||||
// 构建完之后是否要立即运行
|
||||
if config.need_run() {
|
||||
println!("{}: 开始执行...", crate::PROJECT_NAME);
|
||||
let mut command = std::process::Command::new(config.getpath_target_file());
|
||||
command.stdin(std::process::Stdio::inherit());
|
||||
command.stdout(std::process::Stdio::inherit());
|
||||
command.output()?;
|
||||
println!("{}: 执行完毕...", crate::PROJECT_NAME);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
pub enum FileType {
|
||||
C,
|
||||
Cpp,
|
||||
Else,
|
||||
}
|
||||
|
||||
impl FileType {
|
||||
pub fn is_else(&self) -> bool {
|
||||
match self {
|
||||
Self::Else => {
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断文件名是否为.c或.cpp结尾
|
||||
pub fn file_is_c_or_cpp(file_name: &String) -> FileType {
|
||||
let t: Vec<&u8> = file_name.as_bytes().iter().collect();
|
||||
if t.len() >= 2 && *t[t.len() - 2] == b'.' && *t[t.len() - 1] == b'c' {
|
||||
return FileType::C;
|
||||
}
|
||||
if t.len() >= 4
|
||||
&& *t[t.len() - 4] == b'.'
|
||||
&& *t[t.len() - 3] == b'c'
|
||||
&& *t[t.len() - 2] == b'p'
|
||||
&& *t[t.len() - 1] == b'p'
|
||||
{
|
||||
return FileType::Cpp;
|
||||
}
|
||||
return FileType::Else;
|
||||
}
|
||||
|
||||
// 生成可执行程序的文件名(仅windows上有.exe结尾)
|
||||
pub fn make_app_file_name(file_name: &str) -> String {
|
||||
if cfg!(target_os = "windows") {
|
||||
return String::from(file_name) + ".exe";
|
||||
}
|
||||
return String::from(file_name);
|
||||
}
|
||||
12
src/cadd/mod.rs
Normal file
12
src/cadd/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CommandsAdd {
|
||||
/// 项目名称
|
||||
#[clap(name = "lib_name")]
|
||||
lib_name: String,
|
||||
}
|
||||
|
||||
pub fn run(_config: CommandsAdd) -> Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
17
src/cbuild/command.rs
Normal file
17
src/cbuild/command.rs
Normal 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
125
src/cbuild/files.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_by_path(path: PathBuf) -> Self {
|
||||
let name = path.file_name().unwrap().to_string_lossy().into_owned(); // 带扩展名的文件
|
||||
let extension = std::path::Path::new(&path).extension().unwrap();
|
||||
let extension = extension.to_string_lossy().into_owned();
|
||||
let name = String::from(
|
||||
std::path::Path::new(&name)
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
Self::new(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_src(path: PathBuf) -> Self {
|
||||
let mut entry_files = Vec::new();
|
||||
let mut c_files = Vec::new();
|
||||
let mut h_files = Vec::new();
|
||||
|
||||
// 将src文件夹单独处理, 逻辑和fileset_read_dir差不多
|
||||
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");
|
||||
fileset_read_dir(&new_path, &mut entry_files, &mut h_files).unwrap();
|
||||
} else {
|
||||
let new_path = path.join(name);
|
||||
fileset_read_dir(&new_path, &mut c_files, &mut h_files).unwrap();
|
||||
}
|
||||
} else {
|
||||
// 如果是文件
|
||||
let file = File::new_by_path(file.path());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果src目录下仅有一个.c/.cpp文件, 则视为唯一项目入口
|
||||
if entry_files.len() == 0 && c_files.len() == 1 {
|
||||
let t = entry_files;
|
||||
entry_files = c_files;
|
||||
c_files = t;
|
||||
}
|
||||
|
||||
Self {
|
||||
entry_files,
|
||||
c_files,
|
||||
h_files,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 将路径文件夹内所有文件分组整理到集合中
|
||||
fn fileset_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);
|
||||
fileset_read_dir(&new_path, c_files, h_files)?;
|
||||
} else {
|
||||
let file = File::new_by_path(file.path());
|
||||
if file.extension == "c" || file.extension == "cpp" {
|
||||
c_files.push(file);
|
||||
} else if file.extension == "h" || file.extension == "hpp" {
|
||||
h_files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
174
src/cbuild/mod.rs
Normal file
174
src/cbuild/mod.rs
Normal file
@ -0,0 +1,174 @@
|
||||
mod command;
|
||||
mod files;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::const_value::console_log;
|
||||
|
||||
use self::files::FilesSet;
|
||||
|
||||
#[derive(clap::Parser, Debug)]
|
||||
pub struct CommandsBuild {
|
||||
/// 静默执行
|
||||
#[clap(short, long, default_value = "false")]
|
||||
quiet: bool,
|
||||
|
||||
/// 编译后是否立即运行
|
||||
#[clap(short, long, default_value = "false")]
|
||||
run: bool,
|
||||
|
||||
/// MinGW编译器地址, 不填则会从rcm的config文件中查找
|
||||
#[clap(short, long, default_value = None)]
|
||||
mingw: Option<String>,
|
||||
|
||||
/// 采用gcc编译指令而不是g++
|
||||
#[clap(short, default_value = "false")]
|
||||
c: bool,
|
||||
|
||||
/// 其它自定义选项, 需要在语句前加'.', 举例: --other ".-O3 .-mwindows"
|
||||
#[clap(long)]
|
||||
other: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(config: CommandsBuild) -> Result<(), std::io::Error> {
|
||||
// 获取项目路径
|
||||
let project_path = std::env::current_dir().unwrap();
|
||||
|
||||
// 获取生成文件的目标路径
|
||||
// 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目录存在
|
||||
|
||||
if !config.quiet {
|
||||
console_log(&format!("项目路径{:?}", project_path));
|
||||
console_log(&format!("start to compile 开始编译"));
|
||||
}
|
||||
|
||||
// 将src目录下的文件整理归纳
|
||||
let files: FilesSet = FilesSet::read_src(project_path.join("src"));
|
||||
|
||||
// build指令需要读取rust-cmaker下的config文件
|
||||
let r_config = crate::rtools::config::RConfig::read();
|
||||
let mut need_run = None;
|
||||
|
||||
// 命令通用部分
|
||||
let current_args: Vec<String> = make_current_args(&config, &files, project_path.join("lib"));
|
||||
|
||||
// 将每一个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 item in ¤t_args {
|
||||
gcc_cmd.arg(item);
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
let output = gcc_cmd.output();
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
if !output.status.success() {
|
||||
if !config.quiet {
|
||||
let msg = String::from_utf8_lossy(&output.stderr);
|
||||
console_log(&format!("编译失败: {}", msg));
|
||||
}
|
||||
continue;
|
||||
} else if !config.quiet {
|
||||
console_log(&format!(
|
||||
"编译成功: {}.{}",
|
||||
entry_file.name, entry_file.extension
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if !config.quiet {
|
||||
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()?;
|
||||
if !config.quiet {
|
||||
console_log(&format!("{}执行完毕: ", run_file.name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_current_args(config: &CommandsBuild, files: &FilesSet, lib_path: PathBuf) -> Vec<String> {
|
||||
let mut current_args: Vec<String> = Vec::new();
|
||||
|
||||
// 添加所有非入口文件
|
||||
for c_file in &files.c_files {
|
||||
let arg = String::from(&c_file.path.clone().into_os_string().into_string().unwrap());
|
||||
current_args.push(arg);
|
||||
}
|
||||
|
||||
// 从project/lib文件夹里获取所有的.a文件作为静态链接库
|
||||
if let Ok(entries) = std::fs::read_dir(&lib_path) {
|
||||
let mut a_files = Vec::new();
|
||||
|
||||
for entry in entries {
|
||||
if let Ok(entry) = &entry {
|
||||
let file_path = entry.path();
|
||||
if file_path.is_file() {
|
||||
let file = files::File::new_by_path(file_path);
|
||||
if file.extension == "a" && file.name.starts_with("lib") {
|
||||
a_files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a_files.len() != 0 {
|
||||
current_args.push(String::from("-L"));
|
||||
current_args.push(lib_path.into_os_string().into_string().to_owned().unwrap());
|
||||
for item in a_files {
|
||||
let ans = item.name.replacen("lib", "-l", 1);
|
||||
current_args.push(ans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组装config.other
|
||||
if let Some(other) = &config.other {
|
||||
for option in other.split_whitespace() {
|
||||
let op: String = option.chars().skip(1).collect(); // 去除前置'.'
|
||||
current_args.push(op);
|
||||
}
|
||||
}
|
||||
|
||||
current_args
|
||||
}
|
||||
45
src/cnew/mod.rs
Normal file
45
src/cnew/mod.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{const_value::console_log, rtools};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CommandsNew {
|
||||
/// 项目名称
|
||||
#[clap(name = "new_project_name")]
|
||||
project_name: String,
|
||||
|
||||
/// 静默执行
|
||||
#[clap(short, long, default_value = "false")]
|
||||
quiet: bool,
|
||||
|
||||
/// 从自定义模板构建
|
||||
#[clap(short, long, name = "template_name", default_value = "default")]
|
||||
template: String,
|
||||
}
|
||||
|
||||
/// 创建新项目
|
||||
pub fn run(config: CommandsNew) -> Result<(), std::io::Error> {
|
||||
let target_path = std::env::current_dir()?.join(&config.project_name);
|
||||
let mould_path = rtools::get_home_path().join("mould").join(&config.template);
|
||||
|
||||
if let Ok(_) = target_path.read_dir() {
|
||||
if !config.quiet {
|
||||
console_log(&format!(
|
||||
"创建项目失败, 已存在名为{}的文件夹",
|
||||
config.project_name
|
||||
));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !config.quiet {
|
||||
console_log(&format!("新建项目: {}", config.project_name));
|
||||
console_log(&format!("目标路径: {:?}", target_path));
|
||||
console_log(&format!("模板名称: {:?}", config.template));
|
||||
}
|
||||
|
||||
// 从mould文件夹下获取模板并复制到目标路径
|
||||
rtools::copy_dir(&mould_path, &target_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
22
src/const_value.rs
Normal file
22
src/const_value.rs
Normal file
@ -0,0 +1,22 @@
|
||||
// 定义一些全局常数变量
|
||||
|
||||
/// 本项目的名称
|
||||
pub const THIS_PROJECT_NAME: &str = "rust-cmaker";
|
||||
|
||||
/// 以本项目名称发言: rust-cmaker: xxxx
|
||||
pub fn console_log(msg: &str) {
|
||||
println!("{}: {}", THIS_PROJECT_NAME, msg);
|
||||
}
|
||||
|
||||
/// 作者信息
|
||||
pub const AUTHOR: &str = "Cloyir mine123456@foxmail.com";
|
||||
|
||||
/// 项目版本
|
||||
pub const VERSION: &str = "v0.0.1-alpha";
|
||||
|
||||
/// -h 指令写在标头的内容
|
||||
pub const ABOUT: &str = "rcm——一个类cargo的c/c++项目构建管理工具";
|
||||
|
||||
/// --help 指令写在标头的内容
|
||||
pub const LONG_ABOUT: &str =
|
||||
"rust-cmaker——一个类cargo的c/c++项目构建管理工具\n什么年代了还在用传统cmake?";
|
||||
12
src/cstore/mod.rs
Normal file
12
src/cstore/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CommandsStore {
|
||||
/// 指定静态库名称
|
||||
#[clap(short, long)]
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(_config: CommandsStore) -> Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
12
src/ctest/mod.rs
Normal file
12
src/ctest/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CommandsTest {
|
||||
/// 每完成一个测试暂停一次
|
||||
#[clap(short, long, default_value = "false")]
|
||||
pause: bool,
|
||||
}
|
||||
|
||||
pub fn run(_config: CommandsTest) -> Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
68
src/main.rs
68
src/main.rs
@ -1,15 +1,18 @@
|
||||
pub mod build_project;
|
||||
pub mod new_project;
|
||||
pub mod const_value;
|
||||
|
||||
pub const PROJECT_NAME: &str = "rust-cmaker";
|
||||
|
||||
use std::path::PathBuf;
|
||||
mod cadd;
|
||||
mod cbuild;
|
||||
mod cnew;
|
||||
mod cstore;
|
||||
mod ctest;
|
||||
mod rtools;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use const_value::{ABOUT, AUTHOR, LONG_ABOUT, VERSION};
|
||||
|
||||
/// 命令行参数
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author = "Cloyir mine123456@foxmail.com", version = "1.0.0", about = None, long_about = None)]
|
||||
#[command(author = AUTHOR, version = VERSION, about = ABOUT, long_about = LONG_ABOUT)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
@ -18,51 +21,28 @@ struct Args {
|
||||
#[derive(Parser, Debug)]
|
||||
enum Commands {
|
||||
/// 在当前目录新建项目
|
||||
#[command(about = "创建一个新项目")]
|
||||
New {
|
||||
/// 项目名称
|
||||
#[clap(name = "new_project_name")]
|
||||
project_name: String,
|
||||
},
|
||||
/// 在此项目内编译
|
||||
#[command(about = "构建当前文件夹下的项目")]
|
||||
Build {
|
||||
/// -O优化选项
|
||||
#[clap(short = 'o', long = "option", name = "0~3", default_value = "0")]
|
||||
build_options: i32,
|
||||
|
||||
/// 编译后是否立即运行
|
||||
#[clap(short, long, default_value = "false")]
|
||||
run: bool,
|
||||
|
||||
/// MinGW编译器地址, 不填默认已配置为环境变量
|
||||
#[clap(short, long, default_value = None)]
|
||||
mingw: Option<String>,
|
||||
},
|
||||
New(cnew::CommandsNew),
|
||||
/// 构建当前目录下的项目
|
||||
Build(cbuild::CommandsBuild),
|
||||
/// 保存项目为库项目并存储到仓库中
|
||||
Store(cstore::CommandsStore),
|
||||
/// 在当前项目下添加库依赖
|
||||
Add(cadd::CommandsAdd),
|
||||
/// 运行单元测试
|
||||
Test(ctest::CommandsTest),
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
// 解析命令行参数
|
||||
let args = Args::parse();
|
||||
// println!("args: {:?}", args);
|
||||
|
||||
match args.command {
|
||||
Commands::New { project_name } => {
|
||||
new_project::new_project(project_name);
|
||||
}
|
||||
Commands::Build {
|
||||
build_options,
|
||||
run,
|
||||
mingw,
|
||||
} => {
|
||||
let mingw = if let Some(t) = mingw {
|
||||
Some(PathBuf::from(t))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
build_project::build_project(build_options, run, mingw)?;
|
||||
}
|
||||
}
|
||||
Commands::New(config) => cnew::run(config),
|
||||
Commands::Build(config) => cbuild::run(config),
|
||||
Commands::Store(config) => cstore::run(config),
|
||||
Commands::Add(config) => cadd::run(config),
|
||||
Commands::Test(config) => ctest::run(config),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
mod text;
|
||||
|
||||
/// 创建新项目
|
||||
pub fn new_project(project_name: String) {
|
||||
println!("新建项目: {}", project_name);
|
||||
let target_path = std::env::current_dir().unwrap().join(&project_name);
|
||||
println!("目标路径: {:?}", target_path);
|
||||
if let Ok(_) = target_path.read_dir() {
|
||||
println!("创建项目失败, 已存在名为{}的文件夹", project_name);
|
||||
return;
|
||||
}
|
||||
let src_path = target_path.join("src");
|
||||
std::fs::create_dir_all(&src_path).unwrap();
|
||||
|
||||
// 新建README.md文件
|
||||
std::fs::write(target_path.join("README.md"), text::get_readme_text()).unwrap();
|
||||
|
||||
// 新建main.cpp文件
|
||||
std::fs::write(src_path.join("main.cpp"), text::get_maincpp_text()).unwrap();
|
||||
|
||||
println!("创建成功");
|
||||
println!("请进入{}文件夹再使用build指令构建项目", project_name);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
"##
|
||||
}
|
||||
29
src/rtools/config.rs
Normal file
29
src/rtools/config.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
38
src/rtools/mod.rs
Normal file
38
src/rtools/mod.rs
Normal file
@ -0,0 +1,38 @@
|
||||
pub mod config;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// 获取rust-cmaker文件夹目录
|
||||
pub fn get_home_path() -> PathBuf {
|
||||
let path = std::env::current_exe().unwrap();
|
||||
let path = path.parent().unwrap().to_path_buf();
|
||||
path.parent().unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
/// 复制文件夹到指定路径
|
||||
pub fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||
if src.is_file() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"源路径必须是一个文件夹",
|
||||
));
|
||||
}
|
||||
|
||||
if !dst.exists() {
|
||||
std::fs::create_dir_all(dst)?;
|
||||
}
|
||||
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let dst_path = dst.join(path.file_name().unwrap());
|
||||
|
||||
if entry.file_type()?.is_dir() {
|
||||
copy_dir(&path, &dst_path)?;
|
||||
} else {
|
||||
std::fs::copy(&path, &&dst_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user