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!

搞定。

小技巧: 如果你用的是Visual Studio,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 本章小结

这一章我们做了三件事:

  1. 写了一个三行的 CMakeLists.txt
  2. 理解了 cmake_minimum_required、project()、add_executable() 的作用
  3. 用 out-of-source 方式构建并运行了程序

说白了,CMake没那么神秘。它就是把你手动敲编译命令的过程,变成了一个可配置、可复用的脚本。

下一章,我们会给这个项目加上库依赖,看看怎么把代码拆成多个模块。到时候你会发现,CMake的真正威力才开始展现。

课后练习: 试着在 main.cpp 里加一个函数,再创建一个 utils.cpp 和 utils.h,把函数挪过去。然后修改 add_executable,把 utils.cpp 加进去。编译运行,看看能不能正常工作。