欢迎拜访 :雾里看山-CSDN博客
本篇主题 :源码目录、构建目录与 out-of-source build
发布时间 :2026.9.4
隶属专栏 :CMake

目录
- 这一篇要回答的问题
- 三类目录要分清
- [什么是 out-of-source build](#什么是 out-of-source build)
- [什么是 in-source build](#什么是 in-source build)
- [为什么强烈推荐 out-of-source](#为什么强烈推荐 out-of-source)
-
- [1. 源码树保持干净](#1. 源码树保持干净)
- [2. 多配置可以并存](#2. 多配置可以并存)
- [3. 删构建目录 = 干净重来](#3. 删构建目录 = 干净重来)
- [4. 多生成器切换更方便](#4. 多生成器切换更方便)
- [5. CI 友好](#5. CI 友好)
- [怎么做一次标准的 out-of-source build](#怎么做一次标准的 out-of-source build)
- [build 目录里到底有什么](#build 目录里到底有什么)
- [in-source build 也不是完全不能做](#in-source build 也不是完全不能做)
- 几个常见的工程组织模板
-
- [模板一:单层源码 + build](#模板一:单层源码 + build)
- [模板二:源码分层 + build](#模板二:源码分层 + build)
- 模板三:多配置并存
- 模板四:源码与构建完全分开到不同顶级目录
- [.gitignore 应该忽略什么](#.gitignore 应该忽略什么)
- 缓存文件什么时候删
- 常见问题
-
- [问题 1:源码目录里多了一堆乱七八糟的文件](#问题 1:源码目录里多了一堆乱七八糟的文件)
- [问题 2:多个 build 目录互相覆盖](#问题 2:多个 build 目录互相覆盖)
- [问题 3:IDE 自动建了 build 目录怎么办](#问题 3:IDE 自动建了 build 目录怎么办)
- 一个小实验
- 这一篇抓什么
- 总结
这一篇要回答的问题
前面三篇我们已经能把 CMakeLists.txt 写起来并跑通了。但有一个问题还没真正讲透:源码应该放在哪,构建产物又应该放在哪。
CMake 实际上非常鼓励把"源码"和"构建产物"完全分开,这一篇就把这件事讲清楚:
- 源码目录和构建目录的边界
- 什么是 in-source build,什么是 out-of-source build
- 为什么要强烈推荐 out-of-source
- 缓存文件
CMakeCache.txt在哪一层、起什么作用 - 怎么保持源码树干净
三类目录要分清
在 CMake 工程里,至少会涉及三种目录概念:
| 概念 | 含义 | 典型目录 |
|---|---|---|
| 源码目录 | 你写的 .cpp/.h、CMakeLists.txt 所在地 |
hello/ |
| 构建目录 | cmake 生成的中间文件、目标文件、最终产物 | hello/build/ |
| 安装目录 | make install 之后产物部署的目标位置 |
/usr/local 或自定义 |
这一篇重点讲前两个。安装目录到后面讲 install 那一篇再展开。
什么是 out-of-source build
out-of-source build 就是把构建产物放到源码目录之外的另一个目录里。
最常见的形式:
myproj/
├── CMakeLists.txt
├── main.cpp
├── include/
└── build/ ← 构建目录
├── build.ninja
├── CMakeCache.txt
├── hello ← 产物
└── ...
构建的时候:
bash
cmake -S myproj -B myproj/build -G Ninja
cmake --build myproj/build
什么是 in-source build
in-source build 是把构建产物直接生成在源码目录里:
myproj/
├── CMakeLists.txt
├── main.cpp
├── hello ← 直接出现在源码树里
├── CMakeCache.txt
└── ...
这种目录结构看起来很"紧凑",但会带来不少麻烦,这一点下面讲。
为什么强烈推荐 out-of-source
这一节是这一篇的核心。
1. 源码树保持干净
out-of-source 时:
- 源码目录只有你写的
.cpp/.h - 没有任何
.o、可执行文件、缓存
这带来的好处非常直接:
- 拷贝源码到版本控制或别人的机器时,不会有"莫名其妙的多余文件"
- 不会被构建产物干扰阅读代码
- IDE 索引、git 状态、文件搜索都更清爽
2. 多配置可以并存
如果想在同一份源码下同时存在 Debug 和 Release 构建,最直接的方式就是建两个独立的构建目录:
bash
cmake -S . -B build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
最终目录结构:
myproj/
├── CMakeLists.txt
├── main.cpp
├── build-debug/
└── build-release/
互不干扰,调试时切来切去很方便。
3. 删构建目录 = 干净重来
出问题时(比如 CMakeLists.txt 改了某些策略,或缓存数据陈旧),只要删掉 build/ 目录重新配置就行:
bash
rm -rf build
cmake -S . -B build -G Ninja
如果是 in-source build,删除过程会牵涉到一堆位于源码树内部的中间文件,很容易误删源码。
4. 多生成器切换更方便
比如:
- 一会儿想用 Ninja
- 一会儿想用 Visual Studio 工程
只要建不同的构建目录:
bash
cmake -S . -B build-ninja -G Ninja
cmake -S . -B build-vs -G "Visual Studio 17 2022"
in-source 模式下,切换生成器一般意味着先清场再重新生成,繁琐。
5. CI 友好
CI 上一般每次都会拉新代码,然后建一个全新的构建目录。如果源码目录混着产物,反而要专门写脚本去清------out-of-source 时只要:
bash
rm -rf build
cmake -S . -B build -G Ninja
cmake --build build
怎么做一次标准的 out-of-source build
推荐写法
bash
# 假设当前在项目根目录
cmake -S . -B build -G Ninja
cmake --build build
-S .:源码目录是当前目录-B build:构建目录是build,会自动创建
老式写法
bash
mkdir build
cd build
cmake -G Ninja ..
效果一样,但 -S/-B 的写法更明确,也不需要 cd。
运行构建产物
bash
./build/hello # Linux / macOS
build\hello.exe # Windows
build 目录里到底有什么
执行完 cmake -S . -B build 之后,build/ 里会有一堆东西。常见的几个:
build/
├── build.ninja # Ninja 构建文件
├── CMakeCache.txt # 缓存(非常重要)
├── CMakeFiles/ # CMake 内部中间数据
├── hello # 最终可执行文件
├── hello.o # 目标文件
└── ...
下面挑几个关键文件讲一下。
CMakeCache.txt
这个文件存放所有缓存变量:
- 上一次
cmake调用传入的参数 - 用户通过
-D设置的值 - 工具链检测结果、路径、编译器 ID 等
这意味着:
- 第一次
cmake -S . -B build -G Ninja之后,build目录"记住了"这次配置 - 下次再
cmake -S . -B build,很多值会从缓存读取,而不会重新探测 - 如果想真正"重新探测",应该先删缓存或整个 build 目录
CMakeFiles/
CMake 自己的内部数据目录,包括:
- 各 target 的依赖追踪信息
- 中间生成脚本
- 一些临时文件
不要手工去改里面的内容。
真正的构建文件
- Ninja:通常是
build.ninja - Makefiles:通常是
Makefile - Visual Studio:会生成
.sln和.vcxproj
in-source build 也不是完全不能做
公平地说,in-source 也有它的便利:
- 命令短:直接
cmake . && make - 一些非常老的小工程还在这么用
但只要工程稍大一点,in-source 的缺点就会盖过便利性。所以:
- 学习 CMake 时:可以简单体验一下 in-source 的现象
- 真实工程:直接养成 out-of-source 的习惯
几个常见的工程组织模板
下面列几个常见模板,方便对照。
模板一:单层源码 + build
myproj/
├── CMakeLists.txt
├── main.cpp
└── build/
最简单的小项目。
模板二:源码分层 + build
myproj/
├── CMakeLists.txt
├── include/
├── src/
└── build/
适合开始拆分了的小项目。
模板三:多配置并存
myproj/
├── CMakeLists.txt
├── src/
├── build-debug/
└── build-release/
Debug / Release 并存,CI 上也很常见。
模板四:源码与构建完全分开到不同顶级目录
workspace/
├── projects/
│ └── myproj/
│ ├── CMakeLists.txt
│ └── src/
└── builds/
└── myproj-debug/
这种结构适合一机多项目,把所有构建产物统一集中。
.gitignore 应该忽略什么
既然推荐 out-of-source,那源码目录里大部分时间不该出现构建产物。但作为兜底,工程里还是建议加 .gitignore:
gitignore
# 构建目录
build/
build-*/
/out/
# 一些常见的中间文件
*.o
*.obj
*.exe
*.dll
*.so
*.dylib
# IDE / 编辑器
.vs/
.vscode/
.idea/
cmake-build-*/
# CMake 自带
CMakeCache.txt
CMakeFiles/
CMakeScripts/
cmake_install.cmake
Makefile
build.ninja
.deps/
这样即使偶尔忘了 out-of-source,源码目录也不会被污染得很乱。
缓存文件什么时候删
CMakeCache.txt 是构建历史的一部分,但有时候需要清掉重配。
建议删除缓存的场景
- 修改了
toolchain或编译器配置 - 切换了生成器
- 报奇怪的策略(policy)错误
- 修改了
cmake_minimum_required提高到要求重新检测的范围
操作方法
最稳的方式:删整个 build 目录。
bash
rm -rf build
cmake -S . -B build -G Ninja
或者只删缓存:
bash
rm build/CMakeCache.txt
cmake -S . -B build
后者保留其他中间数据,重新探测一些变量,但偶尔会出现"半新半旧"的状态。
常见问题
问题 1:源码目录里多了一堆乱七八糟的文件
多半是早期用过 in-source build 的残留。删除对应的中间文件,并清掉 CMakeCache.txt 等,再改成 out-of-source。
问题 2:多个 build 目录互相覆盖
检查是否两个 build 目录共用同一个目标位置,或者 IDE 自动生成 cmake-build-debug/ 之类。
问题 3:IDE 自动建了 build 目录怎么办
VSCode / CLion 默认会建 build/ 或 cmake-build-debug/ 之类,这些都是允许的,只要不要建到源码子目录里就行。
一个小实验
如果你想自己感受一下 out-of-source 的好处,可以做这个实验:
- 在源码目录里随便改动
main.cpp - 切换到
build/,执行ninja - 看增量构建的速度:只重编
main.cpp,不会重头再编译 - 删掉
build/,重新配置一次
你会发现:
- 源码目录一直很干净
build/是一个可以随时抛弃的"工作现场"- 重新配置并不慢
这就足够了。
这一篇抓什么
这一篇真正要带走的,是这几件事:
- 强烈推荐 out-of-source build
- 源码目录只放源代码和
CMakeLists.txt - 构建目录可以随意删,重建成本很低
CMakeCache.txt决定了一次配置的记忆- 多配置并存(Debug/Release)靠多个构建目录实现
.gitignore要覆盖常见的中间文件
总结
源码目录、构建目录与 out-of-source build 这一篇真正要抓住的,是这一组主线:
- 源码目录和构建目录应该分开
- 源码目录永远保持干净
- 构建目录是"随时可丢"的工作现场
- 删
build/是最常见的"从头再来"方式 - 工程化思维下,out-of-source 是默认而不是例外
下一篇会讲 变量、缓存变量、option 与 message,把 CMake 的配置系统讲清楚。
⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!