结论是:add_subdirectory() 是模块化 cmake 项目的核心机制,必须指向含 cmakelists.txt 的子目录,参数仅认有效路径;子目录作用域隔离,需独立声明 project() 和编译标准,目标通过 target_link_libraries() 精确链接,头文件可见性由 public/interface 控制。

直接说结论:用 add_subdirectory() 拆分,不是靠手写路径或 include(),更不是把所有源码塞进一个 CMakeLists.txt 里硬凑。
怎么写 add_subdirectory()?参数别乱填
它只认子目录下有 CMakeLists.txt 的路径,其他都无效。常见错误是写错相对路径(比如漏掉 ../ 或多加斜杠),或者路径指向空目录。
-
add_subdirectory(core)—— 正确:当前目录下存在core/CMakeLists.txt -
add_subdirectory(./core)—— 不必要,.是冗余的 -
add_subdirectory(core/)—— 可以,但尾部斜杠不推荐,易和 Git 忽略规则冲突 -
add_subdirectory(/abs/path/core)—— 极少用,破坏可移植性,CI 会挂 - 如果子目录没
CMakeLists.txt,CMake 直接报错:Cannot find CMakeLists.txt
子目录里的 CMakeLists.txt 怎么写?别暴露变量
add_subdirectory() 会新建作用域,父目录定义的变量(比如 CMAKE_CXX_STANDARD)默认传不进去。你不能指望子目录自动继承编译标准或宏定义。
- 每个子目录
CMakeLists.txt都要自己写cmake_minimum_required()和project()(哪怕只是占位) - 头文件搜索路径必须显式加:
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) - 别在子目录里用
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ...)—— 容易和父目录冲突,统一由根目录控制输出位置 - 子目录中定义的
target(如add_library(core))在父目录不可见,必须用target_link_libraries()显式链接
依赖关系怎么连?target_link_libraries 是唯一正解
模块之间不能靠文件路径“猜”依赖,也不能靠全局变量传递链接名。CMake 要求目标名精确匹配,且必须已声明。
- 父目录中写:
target_link_libraries(app PRIVATE core utils),前提是core和utils已在各自子目录中通过add_library()定义 - 如果子目录目标名拼错(比如写成
corelib却链接core),报错:target 'core' is not defined -
PUBLIC/INTERFACE影响头文件可见性:子模块要用到父模块的头文件?得在target_include_directories()里标PUBLIC,否则编译失败 - 跨两级依赖(A→B→C)不用手动链 C,只要 B 链了 C,A 链 B 就自动获得 C 的头文件和链接信息(前提是 B 声明了
PUBLIC或INTERFACE)
最容易被忽略的是作用域隔离——你以为子目录能读父目录的 set() 变量,其实不能;你以为链接时能省略 PRIVATE 等关键字,其实 CMake 3.20+ 默认要求明确指定。这些细节不处理,项目一加新模块就编译崩。











