一、为什么要规范工程结构
小 demo 可以一个 main.c 加一条 gcc 命令解决,但真实项目会有多个模块、多个库、测试、安装和部署。工程结构不清晰,后期会很难维护。
一个比较常见的 C/C++ 项目结构:
1 2 3 4 5 6 7 8 9 10 11
| my_app/ ├── CMakeLists.txt ├── include/ │ └── my_app/ │ └── version.h ├── src/ │ ├── main.c │ └── version.c ├── tests/ │ └── test_version.c └── README.md
|
二、顶层 CMakeLists
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
| cmake_minimum_required(VERSION 3.16)
project(my_app C)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON)
add_library(my_app_core src/version.c )
target_include_directories(my_app_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include )
target_compile_options(my_app_core PRIVATE -Wall -Wextra )
add_executable(my_app src/main.c )
target_link_libraries(my_app PRIVATE my_app_core)
|
这样把核心逻辑放进库,主程序只负责入口。
三、头文件和源文件
include/my_app/version.h:
1 2 3 4 5 6
| #ifndef MY_APP_VERSION_H #define MY_APP_VERSION_H
const char *my_app_version(void);
#endif
|
src/version.c:
1 2 3 4 5
| #include "my_app/version.h"
const char *my_app_version(void) { return "1.0.0"; }
|
src/main.c:
1 2 3 4 5 6 7
| #include <stdio.h> #include "my_app/version.h"
int main(void) { printf("my_app version %s\n", my_app_version()); return 0; }
|
四、构建
1 2
| cmake -S . -B build cmake --build build
|
运行:
清理:
五、Debug 和 Release
Debug:
1 2
| cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug cmake --build build-debug
|
Release:
1 2
| cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release cmake --build build-release
|
开发时用 Debug,发布时用 Release。
六、添加测试
最简单的测试可以先不用测试框架。
tests/test_version.c:
1 2 3 4 5 6
| #include <string.h> #include "my_app/version.h"
int main(void) { return strcmp(my_app_version(), "1.0.0") == 0 ? 0 : 1; }
|
CMake:
1 2 3 4 5 6 7 8 9
| enable_testing()
add_executable(test_version tests/test_version.c )
target_link_libraries(test_version PRIVATE my_app_core)
add_test(NAME test_version COMMAND test_version)
|
运行:
1
| ctest --test-dir build --output-on-failure
|
七、安装规则
1 2 3 4 5 6 7
| install(TARGETS my_app RUNTIME DESTINATION bin )
install(DIRECTORY include/ DESTINATION include )
|
安装到指定目录:
1
| cmake --install build --prefix /tmp/my_app_install
|
结果:
1 2
| /tmp/my_app_install/bin/my_app /tmp/my_app_install/include/...
|
八、常用脚本
scripts/build.sh:
1 2 3 4 5 6
| #!/usr/bin/env bash set -euo pipefail
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug cmake --build build ctest --test-dir build --output-on-failure
|
这样新同事不用记一堆命令。
九、建议
- 不要把构建产物放进源码目录。
- 公共头文件放
include。
- 内部实现放
src。
- 可测试逻辑放 library,入口程序尽量薄。
- 每次提交前至少跑一次构建和测试。
- 嵌入式项目也可以复用这套结构,只是换 toolchain file。
十、总结
CMake 的价值不只是替代 Makefile,而是把目标、依赖、测试和安装规则清晰表达出来。项目越大,规范工程结构越重要。