2、第一个CMake项目:CMakeLists.txt基础结构、cmake_minimum_required、project()命令、add_executable()命令、构建与运行
好,咱们正式开始动手写第一个CMake项目。
说实话,很多初学者一上来就被各种复杂的CMake脚本吓住了。其实没必要。CMake的核心逻辑很简单——你告诉它「我要编译什么」,它帮你生成对应的构建系统(比如Makefile或Visual Studio工程)。
这一章,我们就从零开始,搭建一个最基础的C++可执行程序。你跟着我走一遍,就会发现CMake其实挺亲切的。
2.1 CMakeLists.txt 长什么样?
每个CMake项目,根目录下都必须有一个 CMakeLists.txt 文件。这是CMake的「剧本」,所有构建规则都写在这里。
一个最简的 CMakeLists.txt 长这样:
cmake_minimum_required(VERSION 3.16)
project(HelloWorld)
add_executable(hello main.cpp)
就三行。是不是比你想象中简单?
我个人习惯把这三行称为「CMake三板斧」——版本声明、项目声明、目标声明。缺一不可。
2.2 cmake_minimum_required —— 版本红线
第一行命令,指定CMake的最低版本。
cmake_minimum_required(VERSION 3.16)
为什么要写这个?
因为CMake版本之间有不兼容的改动。比如3.10版本支持的特性,在3.5版本里可能压根不认识。你写了个新语法,别人用旧版CMake一跑,直接报错。
我踩过的坑: 有一次我在项目里用了 target_precompile_headers,结果同事的CI环境装的是CMake 3.12,根本不认识这个命令。排查了半天,最后发现是版本问题。从那以后,我每个项目都会明确写上 cmake_minimum_required。
3.16 或 3.20,这两个版本比较稳定,功能也够用。
2.3 project() —— 给项目起个名
第二行,声明项目名称。
project(HelloWorld)
这个命令做了几件事:
- 定义项目名称(这里叫
HelloWorld) - 自动设置一些变量,比如
PROJECT_NAME、PROJECT_SOURCE_DIR、PROJECT_BINARY_DIR - 可以指定语言(默认是C和C++)
你还可以加上语言版本:
project(HelloWorld LANGUAGES CXX)
这样CMake就只检测C++编译器,不检测C编译器了。如果你的项目纯C++,这样写更干净。
另外,project() 还支持设置版本号:
project(HelloWorld VERSION 1.0.0)
这样你就可以在代码里通过 PROJECT_VERSION 拿到版本号了。我一般在做库项目时才会用这个,小工具项目倒不强制。
2.4 add_executable() —— 生成可执行文件
第三行,告诉CMake:我要编译一个可执行程序。
add_executable(hello main.cpp)
第一个参数 hello 是目标名称,也就是最终生成的可执行文件名(Windows下是 hello.exe,Linux/macOS下是 hello)。
第二个参数 main.cpp 是源文件。如果有多个源文件,可以这样写:
add_executable(hello main.cpp utils.cpp logger.cpp)
嗯,这里要注意:源文件路径是相对于 CMakeLists.txt 所在目录的。如果你把源文件放在 src/ 子目录下,就要写成 src/main.cpp。
add_executable 创建的是一个「目标」(target)。CMake里一切围绕目标展开——链接库、设置编译选项、添加依赖,都是针对目标操作的。这是CMake现代写法的精髓。
2.5 完整的项目结构
我们来搭一个完整的目录结构:
my_project/
├── CMakeLists.txt
└── main.cpp
main.cpp 内容:
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
CMakeLists.txt 内容:
cmake_minimum_required(VERSION 3.16)
project(HelloWorld)
add_executable(hello main.cpp)
就这些。是不是很简单?
2.6 构建与运行
文件写好了,怎么编译?
CMake推荐「out-of-source构建」,也就是在项目目录外单独建一个 build 目录,所有编译产物都放里面。这样不会污染源码目录。
步骤很简单,就两条命令:
mkdir build
cd build
cmake ..
cmake --build .
解释一下:
cmake ..:读取上级目录的CMakeLists.txt,生成构建系统(默认是Makefile)cmake --build .:执行构建,相当于make
构建完成后,在 build 目录下就能看到 hello(或 hello.exe)了。运行它:
./hello
输出:
Hello, CMake!
搞定。
cmake .. 会生成 .sln 解决方案文件,直接用IDE打开编译就行。CMake跨平台的优势就在这里——同一份 CMakeLists.txt,在Windows、Linux、macOS上都能用。
2.7 常见问题与避坑
我刚开始用CMake时,也踩过一些坑。这里列几个最常见的:
| 问题 | 原因 | 解决方法 |
|---|---|---|
CMake Error: The source directory does not appear to contain CMakeLists.txt |
你跑 cmake .. 时,当前目录不对 |
确保你在 build 目录下执行命令 |
fatal error: iostream: No such file or directory |
编译器没装对,或者 CMakeLists.txt 里没指定语言 |
检查 project() 是否包含 CXX |
add_executable given source file "main.cpp" which is not an existing file |
源文件路径写错了 | 检查文件名和路径大小写(Linux区分大小写!) |
CMakeLists.txt,一定要重新运行 cmake .. 再 cmake --build .。只运行 cmake --build . 不会自动重新配置。我曾经因为这个浪费了半小时,死活找不到新加的源文件。
2.8 本章小结
这一章我们做了三件事:
- 写了一个三行的
CMakeLists.txt - 理解了
cmake_minimum_required、project()、add_executable()的作用 - 用 out-of-source 方式构建并运行了程序
说白了,CMake没那么神秘。它就是把你手动敲编译命令的过程,变成了一个可配置、可复用的脚本。
下一章,我们会给这个项目加上库依赖,看看怎么把代码拆成多个模块。到时候你会发现,CMake的真正威力才开始展现。
main.cpp 里加一个函数,再创建一个 utils.cpp 和 utils.h,把函数挪过去。然后修改 add_executable,把 utils.cpp 加进去。编译运行,看看能不能正常工作。