2、第一个CMake项目:构建一个最简单的Hello World项目、理解CMakeLists.txt的基本结构、配置与构建分离(out-of-source build)

说实话,很多初学者学CMake,上来就被各种变量、函数、模块吓住了。其实没必要。CMake最核心的思想,说白了就两件事:描述项目和生成构建文件。今天我们就从零开始,亲手搭一个Hello World项目。你跟着我走一遍,就会发现这东西其实挺朴素的。

2.1 项目结构:先搭个架子

我个人习惯,每个CMake项目都单独建一个目录。这样干净,不会跟其他项目混在一起。咱们先创建一个文件夹,就叫 hello_cmake。

hello_cmake/
├── CMakeLists.txt
└── main.cpp

对,就两个文件。一个C++源文件,一个CMake的配置文件。别小看这个结构,它已经是一个完整的CMake项目了。

main.cpp 的内容很简单:

#include <iostream>

int main() {
    std::cout << "Hello, CMake World!" << std::endl;
    return 0;
}

嗯,这里要注意:main.cpp 里不要写任何CMake相关的东西。它就是纯C++代码。CMake只管怎么编译它,不管它里面写了什么。

2.2 CMakeLists.txt:项目的“说明书”

接下来是重头戏——CMakeLists.txt。这是CMake项目的核心配置文件。CMake读的就是它,然后根据它生成Makefile或者Visual Studio工程文件。

咱们的 CMakeLists.txt 长这样:

cmake_minimum_required(VERSION 3.16)
project(HelloCMake)
add_executable(hello main.cpp)

三行代码,搞定。我来逐行解释一下。

指令 作用 我的理解
cmake_minimum_required(VERSION 3.16) 指定CMake最低版本 告诉使用者:“你的CMake版本不能低于3.16,否则别想编译我的项目。” 我在项目中遇到过因为版本太低导致某些特性用不了的情况,所以这个版本号别乱写,要跟你实际用的CMake版本匹配。
project(HelloCMake) 定义项目名称 给项目起个名字。这个名字会影响到后面生成的一些变量,比如 PROJECT_NAME、PROJECT_SOURCE_DIR 等。
add_executable(hello main.cpp) 声明一个可执行文件 告诉CMake:“我要编译一个叫 hello 的可执行文件,它的源代码是 main.cpp。” 如果有多个源文件,可以继续往后加,用空格隔开。
小提示: cmake_minimum_required 最好放在第一行。CMake在处理文件时,会先检查版本号,如果版本不满足,直接报错退出,不会继续往下执行。这样可以避免后面出现莫名其妙的错误。

2.3 配置与构建分离:为什么我强烈推荐out-of-source build

好,现在项目文件准备好了。接下来要编译它。但这里有个关键问题:在哪里执行CMake命令?

有两种做法:

  • In-source build(源码内构建):直接在项目根目录下执行 cmake .,生成的中间文件(Makefile、CMakeCache.txt等)跟源代码混在一起。
  • Out-of-source build(源码外构建):在项目根目录外单独建一个 build 目录,在 build 目录里执行 cmake ..,生成的中间文件全部放在 build 目录里。

我建议你从一开始就养成out-of-source build的习惯。为什么?

  • 干净:源代码目录里只有 .cpp 和 .h 文件,没有乱七八糟的 CMakeFiles、CMakeCache.txt、Makefile 等。想清理构建产物,直接 rm -rf build 就行,不会误删源代码。
  • 灵活:可以同时维护多个构建目录,比如 build-debug、build-release、build-windows,每个目录对应不同的配置。切换构建时,只需要换个目录,不需要重新配置整个项目。
  • 安全:不会因为误操作把构建文件提交到版本控制里。你想想看,如果 CMakeFiles 目录被提交到Git,那每次构建都会产生大量变更,烦不烦?
注意: 我曾经见过有人直接在源码目录里执行 cmake .,然后 make,结果编译出来的可执行文件跟源代码混在一起。后来他想清理,不小心把 main.cpp 也删了……嗯,从那以后我再也不敢用in-source build了。

2.4 动手实践:配置与构建

好,理论说完了,咱们来实际操作一遍。

  1. 在 hello_cmake 目录下创建 build 目录:mkdir build
  2. 进入 build 目录:cd build
  3. 执行CMake配置:cmake ..
  4. 执行构建:cmake --build .

执行完第三步,你会看到类似这样的输出:

-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc - works
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ - works
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/hello_cmake/build

看到 Configuring done 和 Generating done,说明配置成功了。CMake在 build 目录里生成了Makefile(或者其他构建系统的文件)。

第四步 cmake --build . 等价于在 build 目录里执行 make。它会调用编译器,把 main.cpp 编译成可执行文件 hello。

构建完成后,在 build 目录下会多出一个 hello 文件(Windows下是 hello.exe)。运行它:

./hello
Hello, CMake World!

搞定。第一个CMake项目,跑起来了。

2.5 知识体系:一张图看懂CMake构建流程

为了让你更直观地理解整个流程,我画了一张图。它展示了从源代码到可执行文件的完整路径,以及配置与构建分离的核心思想。

CMake 配置与构建分离流程 CMakeLists.txt main.cpp 源代码目录 (src) cmake .. CMake 配置 生成 Makefile cmake --build . 编译器 (g++/cl) hello (可执行文件) 构建目录 (build/) 核心思想:配置与构建分离 • 配置阶段:CMake读取CMakeLists.txt,生成构建文件 • 构建阶段:构建系统调用编译器,生成可执行文件 好处 • 源代码目录干净,不受构建产物污染

2.6 常见问题与避坑

第一次跑CMake,可能会遇到几个小坑。我帮你提前踩一遍。

坑1:CMake版本太低
如果你在 cmake_minimum_required 里写了 3.16,但系统装的CMake是 3.10,CMake会直接报错退出。解决办法:要么升级CMake,要么把版本号改低一点。但我建议升级,因为新版本有很多好用的特性。
坑2:忘记创建build目录
如果你直接在项目根目录下执行 cmake .,那就是in-source build。虽然也能编译成功,但后面清理起来很麻烦。我建议你养成习惯:先 mkdir build && cd build,再 cmake ..。
坑3:源文件路径写错了
在 add_executable 里,源文件路径是相对于 CMakeLists.txt 的。如果你把 main.cpp 放在 src 子目录里,就要写成 add_executable(hello src/main.cpp)。路径写错了,CMake会提示找不到文件。

2.7 小结

这一章我们做了三件事:

  • 写了一个最简单的 CMakeLists.txt,理解了它的三行核心指令。
  • 实践了out-of-source build,把配置和构建分离开。
  • 成功编译并运行了第一个CMake项目。

别看这个项目小,它背后的思想——配置与构建分离——是CMake所有高级功能的基础。后面的章节里,我们会在这个基础上不断加东西:添加库、管理依赖、跨平台编译……但核心流程不会变。

好,这一章就到这儿。你可以在自己的机器上动手试试,把每一步都跑通。遇到问题别慌,回头看看上面的避坑指南,多半能找到答案。