3、第一个CMake项目:从零开始编写CMakeLists.txt、使用cmake命令配置与构建、理解构建目录与源码目录分离
说实话,我见过太多新手一上来就把构建产物和源码混在一起。结果想清理一下,手一抖把源码删了——嗯,这种事我当年也干过。今天我们就从零开始,写一个真正的CMake项目,把「源码目录」和「构建目录」彻底分开。
3.1 项目结构:先搭好架子
我个人习惯,每个CMake项目都长这样:
my_project/
├── CMakeLists.txt # 顶层构建文件
├── src/
│ ├── CMakeLists.txt # 源码目录的构建文件
│ └── main.cpp # 你的C++源文件
└── build/ # 构建目录(一开始是空的)
注意这个 build/ 目录。它不在源码树里,而是我们手动创建的。为什么要这么做?你想想看,如果构建产物(.o文件、可执行文件、缓存文件)全塞在源码目录里,那你的项目会变得多乱?
先写一个最简单的 main.cpp:
// src/main.cpp
#include <iostream>
int main() {
std::cout << "Hello from my first CMake project!" << std::endl;
return 0;
}
就这几行,别嫌简单。万丈高楼平地起嘛。
3.2 编写CMakeLists.txt:从顶层到底层
顶层 CMakeLists.txt 是项目的入口。我一般先写这个:
# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyFirstProject VERSION 1.0.0 LANGUAGES CXX)
# 添加子目录
add_subdirectory(src)
这里有三行,每一行都有讲究:
- cmake_minimum_required:指定CMake最低版本。我建议至少3.16,因为很多现代特性从这开始支持。
- project:定义项目名、版本号、语言。这里我用了CXX(C++),如果你用C就写C。
- add_subdirectory:告诉CMake去处理
src/目录下的CMakeLists.txt。
然后写 src/CMakeLists.txt:
# src/CMakeLists.txt
add_executable(my_app main.cpp)
就一行。把 main.cpp 编译成可执行文件 my_app。简单吧?
3.3 配置与构建:两条命令搞定
好,现在进入正题。打开终端,进入项目根目录,执行:
cd my_project
cmake -S . -B build
这条命令的意思是:源码目录是当前目录(.),构建目录是 build/。CMake会读取 CMakeLists.txt,生成构建系统文件(比如Makefile或Ninja文件)到 build/ 里。
配置成功后,你会看到类似这样的输出:
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/my_project/build
然后构建:
cmake --build build
这条命令会进入 build/ 目录,调用底层构建工具(比如make)编译代码。最终生成的可执行文件在 build/src/my_app(Linux/macOS)或 build\src\Debug\my_app.exe(Windows)。
3.4 理解构建目录与源码目录分离
为什么要分离?我举个例子。有一次我在一个项目里直接 cmake .,结果所有临时文件都散落在源码目录里。后来想换个编译器版本,得把整个目录删了重新来——但源码也混在里面,差点误删。
分离之后,你的源码目录干干净净:
my_project/
├── CMakeLists.txt
├── src/
│ ├── CMakeLists.txt
│ └── main.cpp
└── build/ # 这个目录随时可以删掉重建
想清理?直接 rm -rf build 就行。源码毫发无损。想换个构建类型(Debug/Release)?再建一个 build_debug 目录,配置时指定 -DCMAKE_BUILD_TYPE=Debug 即可。
下面这张图帮你理清整个流程:
3.5 避坑指南:新手最容易犯的错
我曾经见过一个同事,直接在源码目录里执行 cmake .,然后所有构建文件都散落在源码里。后来他想换个构建类型,发现根本没法清理干净——因为不知道哪些文件是CMake生成的,哪些是自己写的。
这里我总结几个常见坑:
- 不要在源码目录里直接构建:永远用
-B指定一个独立的构建目录。 - 不要手动修改构建目录里的文件:比如
CMakeCache.txt,改错了CMake会疯掉。想改配置?用cmake -D参数重新配置。 - 不要忘记重新配置:如果你改了
CMakeLists.txt,记得重新执行cmake -S . -B build。只执行cmake --build build不会检测到构建脚本的变化。
.sln 文件。千万别双击打开它然后手动编译——虽然也能用,但你就失去了CMake的跨平台一致性。用 cmake --build build 才是正道。
3.6 实战检验:跑起来看看
好了,现在我们来验证一下。执行完上面的命令后,运行生成的可执行文件:
./build/src/my_app
你应该看到:
Hello from my first CMake project!
如果看到了,恭喜你——你的第一个CMake项目已经跑通了。如果没看到,检查一下:
- 当前目录是不是项目根目录?
build/目录是否存在?- 有没有拼写错误?比如
cmake写成了cmakee?
嗯,这些低级错误我当年都犯过。别急,慢慢来。
3.7 小结:你学到了什么
这一章我们做了三件事:
- 写了一个最简单的
CMakeLists.txt,包含顶层和子目录。 - 用
cmake -S . -B build配置项目,用cmake --build build构建。 - 理解了为什么要把构建目录和源码目录分开——说白了就是为了干净、灵活、可重复。
我个人觉得,这三点是CMake的基石。后面的所有高级特性(库、测试、安装、打包)都建立在这个基础上。所以别急着跳,先把这一步练熟。
下次你新建一个C++项目,第一件事就是创建 build/ 目录。养成习惯,受益终身。