源码目录、构建目录与 out-of-source build

欢迎拜访雾里看山-CSDN博客

本篇主题 :源码目录、构建目录与 out-of-source build

发布时间 :2026.9.4

隶属专栏CMake

目录

这一篇要回答的问题

前面三篇我们已经能把 CMakeLists.txt 写起来并跑通了。但有一个问题还没真正讲透:源码应该放在哪,构建产物又应该放在哪

CMake 实际上非常鼓励把"源码"和"构建产物"完全分开,这一篇就把这件事讲清楚:

  • 源码目录和构建目录的边界
  • 什么是 in-source build,什么是 out-of-source build
  • 为什么要强烈推荐 out-of-source
  • 缓存文件 CMakeCache.txt 在哪一层、起什么作用
  • 怎么保持源码树干净

三类目录要分清

在 CMake 工程里,至少会涉及三种目录概念:

概念 含义 典型目录
源码目录 你写的 .cpp/.hCMakeLists.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. 多配置可以并存

如果想在同一份源码下同时存在 DebugRelease 构建,最直接的方式就是建两个独立的构建目录:

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 的好处,可以做这个实验:

  1. 在源码目录里随便改动 main.cpp
  2. 切换到 build/,执行 ninja
  3. 看增量构建的速度:只重编 main.cpp,不会重头再编译
  4. 删掉 build/,重新配置一次

你会发现:

  • 源码目录一直很干净
  • build/ 是一个可以随时抛弃的"工作现场"
  • 重新配置并不慢

这就足够了。

这一篇抓什么

这一篇真正要带走的,是这几件事:

  • 强烈推荐 out-of-source build
  • 源码目录只放源代码和 CMakeLists.txt
  • 构建目录可以随意删,重建成本很低
  • CMakeCache.txt 决定了一次配置的记忆
  • 多配置并存(Debug/Release)靠多个构建目录实现
  • .gitignore 要覆盖常见的中间文件

总结

源码目录、构建目录与 out-of-source build 这一篇真正要抓住的,是这一组主线:

  • 源码目录和构建目录应该分开
  • 源码目录永远保持干净
  • 构建目录是"随时可丢"的工作现场
  • build/ 是最常见的"从头再来"方式
  • 工程化思维下,out-of-source 是默认而不是例外

下一篇会讲 变量、缓存变量、optionmessage,把 CMake 的配置系统讲清楚。

⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!

相关推荐
渡我白衣3 小时前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
如意猴3 小时前
【C++】001--C++入门(1)
开发语言·c++
hetao17338373 小时前
2026-09-01~09-04 hetao1733837 的刷题记录
c++·算法
HugoStudio_SWAN6 小时前
洛谷 P1420 / P1179 / B4262 最长连号、数字统计与词频统计——统计的三种面孔
c++·学习·程序人生·算法
raindayinrain7 小时前
c++并发
c++
j7~7 小时前
【C++】智能指针的使用及其原理--详解
c++·c++11·内存泄漏·智能指针·raii·boost智能指针
Persistent的粽子!8 小时前
C++:类与对象(一)
开发语言·c++·经验分享·笔记
库玛西9 小时前
深入浅出Linux select网络模型:从底层位图原理到C++面向对象高级封装
linux·服务器·网络·c++·ubuntu
2402_882893869 小时前
深入浅出 unordered_map 与 unordered_set——从使用到底层差异
c++·哈希·unordered_map·unordered_set