add_subdirectory()必须在父级CMakeLists.txt中调用,路径相对于当前文件目录;子目录需含CMakeLists.txt且变量不自动继承;头文件路径须用target_include_directories()显式声明,禁用include_directories();EXCLUDE_FROM_ALL易致CI构建遗漏,推荐option控制模块启用。

直接用 add_subdirectory() 就能拆,但拆错位置、漏配头文件路径、搞混变量作用域,项目就编不过。
add_subdirectory() 的调用位置和路径写法
它必须出现在父级 CMakeLists.txt 中,且路径是相对于当前 CMakeLists.txt 所在目录的 —— 不是相对于项目根目录,也不是相对于构建目录。
- 正确:
add_subdirectory(core)(当前目录下有core/CMakeLists.txt) - 错误:
add_subdirectory(../core)(CMake 不允许向上跳转,会报source directory does not exist) - 错误:
add_subdirectory(/abs/path/core)(绝对路径虽语法合法,但破坏可移植性,CI/他人机器上大概率失败) - 如果子目录不在当前目录同级,比如在
src/core,就写add_subdirectory(src/core)
子目录里必须有 CMakeLists.txt,且不能依赖父目录变量
add_subdirectory() 会进入子目录作用域执行其 CMakeLists.txt,所有在父目录定义的变量(比如 PROJECT_NAME、自定义 LIB_SOURCES)默认不可见。
- 常见错误:父目录设了
set(INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include),子目录里直接用${INCLUDE_DIRS}—— 结果为空,#include找不到头文件 - 正确做法:子目录自己用
CMAKE_CURRENT_SOURCE_DIR或显式传参(通过set(... PARENT_SCOPE),但慎用,易混乱) - 更推荐:每个子目录独立管理自己的源码、头路径、target,靠
target_include_directories()向外暴露接口
头文件路径不配,main.cpp 就找不到 Circle.h
即使 add_subdirectory(Shape) 成功,main.cpp 里写 #include "Circle.h" 还是会报错 —— 编译器根本不知道去哪找这个头文件。
- 必须在父目录 target 上声明包含路径:
target_include_directories(demoapp PUBLIC Shape/include) -
PUBLIC表示:既供demoapp自己用,也传递给链接它的下游 target(如果有) - 路径是相对于
CMAKE_CURRENT_SOURCE_DIR的,所以Shape/include指的是当前目录/Shape/include - 别用
include_directories(),它全局生效、作用域模糊,CMake 官方已标记为 legacy
EXCLUDE_FROM_ALL 容易误用,尤其在 CI 场景
加了 EXCLUDE_FROM_ALL 后,子目录里的 target 默认不会被 make 或 cmake --build . 构建,除非显式指定目标名或存在依赖链。
- 典型陷阱:本地开发时手动
make shape_lib没问题,但 CI 脚本只跑make,结果子模块完全没编,链接时报undefined reference - 如果你需要按需启用模块(比如测试模块),更稳妥的方式是用 option 控制:
option(ENABLE_TESTS "Build tests" OFF),再配合if(ENABLE_TESTS)包裹add_subdirectory(tests) -
EXCLUDE_FROM_ALL在 Ninja 生成器下行为不稳定,某些 CMake 版本会忽略它
真正麻烦的不是怎么写 add_subdirectory(),而是子目录之间依赖怎么传、头文件路径怎么对齐、以及 build 目录里生成的中间文件会不会互相污染 —— 这些细节不盯住,模块化反而比单文件更难 debug。


















