build a mini project starter in Rust

最近沉迷写c++以及cmake构建项目(不是吧,这也能沉迷?),主要关注的是一个项目的架构,以及一些辅助工具,比如生成文档,代码检查(sanitize)等,打算使用Rust写一个非常简单的c++项目的generator

项目源码:build-my-own-x/mini-project-starter at main · drowning-in-codes/build-my-own-x (github.com)

crates.io:mini-project-starter - crates.io: Rust Package Registry

image-20240915235220102

常见的c++项目组织方式

项目组织方式因人而异,但一些基本点还是相同的,比如参考Rust一些模块组织方式,由于c++代码需要有头文件(不知道c++26之后的未来会不会尽可能地使用模块替代头文件这种组织方式)

由于在其他语言中,一个模块基本上就是单独一个文件,而c++中是cpp源文件和对应的头文件,这个模块最好(或者说必须)放在对应名字的目录下,比如我的项目下写了一个logger模块,它一种组织方式如下,src目录下放置需要的库,app目录下放可执行程序或者最后生成的库.相当于在src目录下放置多个模块目录,每个目录中放源文件和头文件

1
2
3
4
5
6
7
8
9
10
11
12
- root_dir\
- project\
- src\
- logger\
- logger.cpp
- logger.hpp
- include\ (optional)
- project\
- app\
- main.cpp
- include\
- main.hpp (optional)

或者稍微改改,将模块的头文件放在include中,也就是头文件和源文件不放在同一个目录

1
2
3
4
5
6
7
8
9
10
- root_dir\
- project\
- src\
- logger.cpp
- include\
- project\
- logger\
- logger.hpp
- app\
- main.cpp

此外还有一种,将所有cpp文件都放在src中,头文件放在include中,差异就是它的源文件是放在一起的,没有放在一个所谓模块目录下.

1
2
3
4
5
6
7
8
9
10
- root_dir\
- project\
- src\
- logger.cpp
- main.cpp
- include\
- project\
- main.hpp
- logger\
- logger.hpp

上面几种方式其实都行,我们看看rust是怎么组织的,由于没有头文件,在src目录下有main.rs,而garden.rs是模块,garden目录下的vegetables.rs是模块的子模块.

1
2
3
4
5
6
7
8
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│   └── vegetables.rs
├── garden.rs
└── main.rs

上面组织方式的核心差别,是针对头文件,要么在include目录下按照模块名分几个目录,而不是将头文件放在一起,要么在src目录下分多个需要的库目录,每个目录包括源文件和头文件.

mini-project-starter

命令行解析

使用Clap库解析参数,

1
2
mini-project-starter new -p <DIR> # 在一个目录下创建项目
mini-project-starter init # 在当前目录下初始化项目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
pub mod utils;
use clap::{command, Arg, Command};
use utils::{init_project, new_project};
fn main() {
let starter_config = command!()
.propagate_version(true)
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("new")
.about("Create a new project in the specified directory")
.arg(
Arg::new("project_root_dir")
.short('p')
.long("project_root_dir")
.value_name("DIR")
.value_parser(clap::builder::NonEmptyStringValueParser::new())
.help("The root dir of the project")
.required(true),
),
)
.subcommand(Command::new("init").about("Init the project in the current directory"))
.get_matches();

match starter_config.subcommand() {
Some(("new", sub_matches)) => {
new_project(sub_matches.get_one::<String>("project_root_dir").unwrap())
}
Some(("init", _)) => init_project(),
_ => println!("Please specify a subcommand"),
}
}

然后让用户输入项目的一些信息,这些信息可以替换后面CMakeLists.txt中的项目信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
let root_dir_name = default_root_dir_name
.file_name()
.unwrap()
.to_str()
.unwrap();
let project_name = Input::<String>::new()
.with_prompt(r"Enter your project name.(project root folder name if leave blank)")
.with_initial_text(root_dir_name.to_string())
.interact_text()
.unwrap_or(root_dir_name.to_string());

// 项目版本
let project_version = Input::<String>::new()
.with_prompt("Enter your project version.(default: 0.1.0)")
.with_initial_text("0.1.0".to_string())
.interact_text()
.unwrap_or("0.1.0".to_string());

// 项目描述
let project_desc = Input::<String>::new()
.with_prompt("Enter your project description.(default: A new project)")
.with_initial_text("A new project".to_string())
.interact_text()
.unwrap_or("A new project".to_string());

// 选择项目类型
let project_types = vec![ ProjectType::Cpp];
let project_type_selection = Select::with_theme(&theme)
.with_prompt("choose the peoject type")
.items(&project_types)
.interact()
.unwrap();

// 选择生成目标类型
let target_type = vec![
TargetType::Executable,
TargetType::StaticLibrary,
TargetType::DynamicLibrary,
];
let target_type_selection = Select::with_theme(&theme)
.with_prompt("choose the target type")
.items(&target_type)
.interact()
.unwrap();

// 选择包管理器
let package_manager_list = vec![PackageManager::VCPKG, PackageManager::CPM,PackageManager::None];
let package_manager_selection = Select::with_theme(&theme)
.with_prompt("choose the package manager ")
.items(&package_manager_list)
.interact()
.unwrap();
let package_manager = package_manager_list[package_manager_selection].clone();

创建一个项目需要拷贝一些目录和文件,如果是目录注意不要重复创建,而文件可以直接truncate

1
2
3
4
5
6
7
8
9
10
11
12
// 创建src目录与main.cpp
match fs::create_dir_all(root_dir.join(project_name).join("src")) {
Ok(_)=>{},
Err(e)=>{
eprintln!("{}:{}",e.kind(),e);
}
}
let src_file = File::create(root_dir.join(project_name).join("src/main.cpp")).unwrap();
match fs::read_to_string(env::current_dir().unwrap().join("templates/main.cpp")) {
Ok(contents) => write!(&src_file, "{}", contents).unwrap(),
Err(e) => eprintln!("Error reading file: {}", e),
}

CMakeLists.txt中,添加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cmake_minimum_required(VERSION 3.29)

set(CMAKE_C_COMPILER "C:/Program Files/LLVM/bin/clang.exe")
set(CMAKE_CXX_COMPILER "C:/Program Files/LLVM/bin/clang++.exe")

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# include(${CMAKE_SOURCE_DIR}/cmake/sanitier.cmake)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

include(sanitizer)
include(copy_target)

project(@PROJECT_NAME@ LANGUAGES C CXX VERSION @PROJECT_VERSION@ PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@")

FILE(GLOB_RECURSE LIB_SOURCE src/*.cpp src/*.c)
FILE(GLOB_RECURSE HEADER include/*.h include/*.hpp)
source_group(headers FILES ${HEADER})
add_library(@PROJECT_NAME@ STATIC ${SOURCES})

target_include_directories(@PROJECT_NAME@ PRIVATE include)

option(ENABLE_SANITIZER "Enable sanitizer" OFF)
if(ENABLE_SANITIZER)
add_sanitier(@PROJECT_NAME@)
endif()

option(ENABLE_COPY_TARGET "Enable copy target" ON)
if(ENABLE_COPY_TARGET)
copy_target(@PROJECT_NAME@)
endif()

针对包管理器,我设置了可以使用vcpkg或者cpm,如果是cpm可以在网上下载对应cmake脚本,如果是vcpkg,在CMakePresets.json中添加对应toolchainfile.

最后还有测试目录,cmake脚本,scripts辅助脚本,docs文档等等,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- .gitignore
- cmake
- FindSomeLib.cmake
- something_else.cmake
- README.md
- LICENCE.md
- tests
- CMakeLists.txt
- testlib.cpp
- docs
- CMakeLists.txt
- extern
- googletest
- scripts
- helper.py

还不错的视频参考

  1. CMake, Tests and Tooling for C/C++ Projects
  2. Update to Modern C++
-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道