导语
经过前两节的学习,我们已经了解了CMake的背景理念,也完成了本地的环境安装。从本节开始,我们将真正动手编写代码。就像学习任何一门编程语言时都要从“Hello World”开始一样,我们的CMake之旅也将从一个最简单的可执行程序项目起步。
在这一节中,你不仅会写出人生中第一个CMakeLists.txt,还会完整经历CMake构建的配置(Configure)→生成(Generate)→构建(Build)三大阶段。更重要的是,我会从一开始就带你养成一个黄金习惯:将源码目录与构建目录严格分离。这个习惯将让你在未来管理复杂项目时受益匪浅。
请务必打开你的终端或命令行工具,跟着下面的步骤一步步操作。
最小可运行示例:Hello World
首先,在我们磁盘的任意位置(比如~/projects或D:projects)创建一个新的文件夹,命名为hello_cmake。这个文件夹将承载我们第一个项目的所有内容。
创建源文件
在hello_cmake目录下,新建一个名为main.cpp的文件,输入以下C++代码:
#include <iostream>
int main() {
std::cout << "Hello, CMake World!" << std::endl;
return 0;
}
创建 CMakeLists.txt
在hello_cmake目录下,再新建一个名为CMakeLists.txt的文件(注意大小写,C和M大写,无空格)。这是CMake项目的核心配置文件。输入以下内容:
cmake_minimum_required(VERSION 3.15)
project(HelloCMake VERSION 1.0 LANGUAGES CXX)
add_executable(hello main.cpp)
让我们逐行拆解这个最小配置,理解每一行的职责:
cmake_minimum_required(VERSION 3.15):声明本项目所需的最低CMake版本。如果系统上的CMake版本低于3.15,配置会直接报错退出,避免使用旧版本导致不可预期的行为。对于新手,建议至少设置为3.15(Modern CMake的成熟版本)。project(HelloCMake VERSION 1.0 LANGUAGES CXX):定义项目名称。这会在CMake内部创建一系列与项目相关的变量(如PROJECT_NAME)。VERSION 1.0可选地声明了项目版本号,LANGUAGES CXX则显式指明这是一个C++项目。add_executable(hello main.cpp):声明一个可执行文件目标(Target),名为hello,它由main.cpp这一个源文件编译链接而成。这是CMake中最基础、最常用的命令之一。
目录结构规范:源码与构建分离
在继续构建之前,我们先建立正确的目录组织观念。一个健康的CMake项目目录应该长这样:
hello_cmake/
├── CMakeLists.txt # 项目构建配置的“入口”
├── main.cpp # 源代码
└── build/ # 构建目录(由CMake生成,不放入版本控制)
├── ... # 编译器生成的中间文件、可执行文件等
这里的关键原则是:源码目录(Source Tree)和构建目录(Build Tree)必须分离。
为什么必须分离?
许多初学者会直接在项目根目录下运行cmake .,这会导致编译生成的.o文件、可执行文件、CMake缓存文件等与我们的main.cpp和CMakeLists.txt混在一起,就像这样:
❌ 错误示例(In-source Build)
hello_cmake/
├── CMakeLists.txt
├── main.cpp
├── CMakeCache.txt # 缓存文件混入源码
├── CMakeFiles/ # 中间目录混入源码
├── Makefile # 生成的构建脚本混入源码
└── hello # 可执行文件混入源码
这种源码内构建(In-source Build)有三大弊端:
- 污染版本控制:你需要手动编写很长的
.gitignore文件来忽略生成的垃圾文件,稍有不慎就会误提交几MB的构建产物。 - 难以管理多配置:如果你想同时生成Debug版本和Release版本,它们会互相覆盖,或者你需要在源码目录里创建更混乱的子目录。
- 清理困难:一旦构建出错,你可能分不清哪些文件是源码、哪些是生成的,甚至不敢轻易删除文件。
因此,CMake社区的最佳实践是始终坚持源码外构建(Out-of-source Build)。我们将在下一节演示具体操作。
构建流程详解:三阶段模型
CMake的构建过程不是“一键编译”那么简单,它由三个逻辑上独立的阶段组成。理解这三个阶段,是你排查构建问题的核心基础。
阶段一:配置(Configure)
配置阶段是CMake读取并解析CMakeLists.txt的过程。CMake会:
- 检查编译器是否存在且可用;
- 验证
cmake_minimum_required的版本要求; - 执行
CMakeLists.txt中的命令,创建内部数据结构(如目标、变量、属性等); - 在构建目录下生成
CMakeCache.txt缓存文件,记录本次配置的结果(如编译器路径、用户选项等)。
阶段二:生成(Generate)
生成阶段紧随配置阶段之后(通常在同一个cmake命令中自动完成)。CMake根据你指定的生成器(Generator),产出底层的构建系统文件。例如:
- 如果你使用默认的Unix Makefiles生成器,CMake会生成
Makefile; - 如果你使用Ninja,会生成
build.ninja; - 如果使用Visual Studio生成器,则会生成
.sln和.vcxproj项目文件。
这个阶段完成后,构建目录里已经具备了让底层构建工具(make、ninja、MSBuild等)工作的全部“图纸”。
阶段三:构建(Build)
构建阶段是实际的编译和链接发生的地方。底层构建工具(如Make或Ninja)根据生成阶段产出的图纸,调用编译器将main.cpp翻译为机器码,最终链接成可执行文件hello(或Windows下的hello.exe)。
可以用一个比喻来记忆:
- Configure 像建筑师阅读需求文档(CMakeLists.txt);
- Generate 像建筑师画出施工图纸(Makefile/build.ninja);
- Build 像工人根据图纸盖房子(编译链接出二进制文件)。
命令行基础操作
现在,让我们用命令行来完成第一个项目的配置和构建。请确保终端当前的工作目录是hello_cmake。
Step 1:创建构建目录并进入
mkdir build
cd build
Step 2:配置与生成(Configure & Generate)
运行以下命令。这会在当前目录(build/)下查找上一级目录的CMakeLists.txt,并在当前目录生成构建系统文件:
cmake ..
这里的..表示“源码目录在我的上一级”。如果你看到类似下面的输出,说明配置和生成成功了:
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/projects/hello_cmake/build
此时观察build/目录,你会发现CMake生成了CMakeCache.txt、CMakeFiles/目录和Makefile(在Linux/macOS或MinGW环境下)。
Step 3:构建(Build)
CMake提供了一个与底层构建工具无关的通用构建命令:
cmake --build .
这里的.表示“在当前目录下执行构建”。这条命令会自动调用合适的底层工具(如make或ninja)来完成编译。
构建成功后,你会在build/目录下看到生成的可执行文件:
- Linux/macOS:
hello - Windows:
Debughello.exe(某些生成器会附加配置子目录)或hello.exe
运行它:
./hello # Linux/macOS
# 或
Debughello.exe # Windows (Visual Studio 生成器)
你应该能看到终端输出:Hello, CMake World!
关于 ctest 的基础用法
虽然详细的测试框架会在第七章展开,但ctest作为CMake原生的测试运行工具,值得我们在这里混个脸熟。CMake在生成阶段会准备一个CTestTestfile.cmake。如果项目中定义了测试(通过add_test),你可以在构建完成后运行:
ctest
在我们的Hello World项目中,目前还没有定义任何测试,所以运行ctest会提示没有发现测试。没关系,先记住这条命令,后续我们会频繁使用它。
构建目录管理:In-source vs Out-of-source 实战对比
为了让你直观感受两种构建方式的差异,我们做一个对比实验。
实验 A:Out-of-source(推荐方式)
这就是我们刚才做的。源码目录始终保持干净:
# 在 hello_cmake/build 目录下
cmake ..
cmake --build .
如果你不满意当前的构建结果,或者想换用不同的编译选项重新来过,只需整个删除build目录即可,源码毫发无损:
cd ..
rm -rf build # Linux/macOS
# 或
rmdir /s /q build # Windows CMD
这种“随时删、随时建”的特性是Out-of-source最大的优势。
实验 B:In-source(仅作了解,切勿模仿)
为了对比,我们也可以在源码目录直接配置:
# 注意:这次我们直接在 hello_cmake 目录下操作
cd .. # 如果刚才在build里,先回到hello_cmake
cmake .
执行后,你会发现项目根目录瞬间多出了CMakeCache.txt、CMakeFiles、Makefile等文件,与main.cpp和CMakeLists.txt混在一起。构建产生的目标文件也会散落在CMakeFiles子目录中。
强烈建议:立即清理这个混乱现场,回归Out-of-source模式:
# 在 hello_cmake 目录下清理
rm -rf CMakeCache.txt CMakeFiles Makefile cmake_install.cmake hello
mkdir build
cd build
cmake ..
小结
在本节中,我们完成了从零到一的突破:
- 编写了包含
cmake_minimum_required、project和add_executable的最小CMakeLists.txt; - 建立了源码外构建(Out-of-source Build)的正确观念,养成了使用独立
build/目录的习惯; - 理解了CMake构建的配置→生成→构建三阶段模型;
- 掌握了三条最基础的命令:
cmake ..(配置生成)、cmake --build .(构建)、ctest(运行测试)。
这只是一个开始。下一节,我们将深入CMakeLists.txt的语法基础,学习变量、列表、条件判断和循环,为你的CMake脚本编写能力打下坚实根基。


没有回复内容