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。简单吧?

小技巧: 我习惯把每个子目录的CMakeLists.txt写得尽量短。一个文件只做一件事,这样后期维护起来不头疼。

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)。

核心要点: 配置(cmake -S . -B build)和构建(cmake --build build)是两步。配置只做一次,除非你改了CMakeLists.txt。构建可以反复执行,只重新编译修改过的文件。

3.4 理解构建目录与源码目录分离

为什么要分离?我举个例子。有一次我在一个项目里直接 cmake .,结果所有临时文件都散落在源码目录里。后来想换个编译器版本,得把整个目录删了重新来——但源码也混在里面,差点误删。

分离之后,你的源码目录干干净净:

my_project/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── main.cpp
└── build/          # 这个目录随时可以删掉重建

想清理?直接 rm -rf build 就行。源码毫发无损。想换个构建类型(Debug/Release)?再建一个 build_debug 目录,配置时指定 -DCMAKE_BUILD_TYPE=Debug 即可。

下面这张图帮你理清整个流程:

源码目录 CMakeLists.txt src/main.cpp src/CMakeLists.txt cmake -S . -B build 构建目录 CMakeCache.txt Makefile / build.ninja src/ cmake --build build 输出产物 my_app (可执行文件) 关键原则:源码目录与构建目录完全分离 • 源码目录只存放 .cpp、.h、CMakeLists.txt 等源文件 • 构建目录存放所有临时文件(.o、.exe、缓存等) • 删除构建目录不会影响源码,可以随时重建 • 支持多个构建目录(如 build_debug、build_release)共存

3.5 避坑指南:新手最容易犯的错

我曾经见过一个同事,直接在源码目录里执行 cmake .,然后所有构建文件都散落在源码里。后来他想换个构建类型,发现根本没法清理干净——因为不知道哪些文件是CMake生成的,哪些是自己写的。

这里我总结几个常见坑:

  • 不要在源码目录里直接构建:永远用 -B 指定一个独立的构建目录。
  • 不要手动修改构建目录里的文件:比如 CMakeCache.txt,改错了CMake会疯掉。想改配置?用 cmake -D 参数重新配置。
  • 不要忘记重新配置:如果你改了 CMakeLists.txt,记得重新执行 cmake -S . -B build。只执行 cmake --build build 不会检测到构建脚本的变化。
警告: 如果你在Windows上使用Visual Studio生成器,构建目录里会生成一个 .sln 文件。千万别双击打开它然后手动编译——虽然也能用,但你就失去了CMake的跨平台一致性。用 cmake --build build 才是正道。

3.6 实战检验:跑起来看看

好了,现在我们来验证一下。执行完上面的命令后,运行生成的可执行文件:

./build/src/my_app

你应该看到:

Hello from my first CMake project!

如果看到了,恭喜你——你的第一个CMake项目已经跑通了。如果没看到,检查一下:

  • 当前目录是不是项目根目录?
  • build/ 目录是否存在?
  • 有没有拼写错误?比如 cmake 写成了 cmakee

嗯,这些低级错误我当年都犯过。别急,慢慢来。

3.7 小结:你学到了什么

这一章我们做了三件事:

  1. 写了一个最简单的 CMakeLists.txt,包含顶层和子目录。
  2. cmake -S . -B build 配置项目,用 cmake --build build 构建。
  3. 理解了为什么要把构建目录和源码目录分开——说白了就是为了干净、灵活、可重复。

我个人觉得,这三点是CMake的基石。后面的所有高级特性(库、测试、安装、打包)都建立在这个基础上。所以别急着跳,先把这一步练熟。

下次你新建一个C++项目,第一件事就是创建 build/ 目录。养成习惯,受益终身。


公众号:蓝海资料掘金营,微信deep3321