Modern CMake 最佳实践:如何优雅地接管第三方子模块的 option 开关
起因:一个“永远关不掉”的巨型开关
最近在维护一个嵌入式多平台驱动仓库时,发现第三方提供的一个 SDK 子模块 默认打开了一个超级重的功能(编译时间从 9 分钟直接飙到 15 分钟+,产出体积也多出好几 MB)。
子模块里的写法非常“复古”:
option(ENABLE_HEAVY_FEATURE "Enable very heavy legacy feature" ON)
if(ENABLE_HEAVY_FEATURE)
add_definitions(-DHEAVY_FEATURE) # 全局污染
endif()
而我们主项目 95% 的配置压根不需要这个功能,CI 每次都白白浪费大量时间。
最 Modern、最推荐的接管方式(2025 年写法)
在顶级项目(或统一放第三方开关的文件)里,在 add_subdirectory 之前写一行:
# 第三方模块统一控制区
set(ENABLE_HEAVY_FEATURE OFF CACHE BOOL
"[VendorSDK] Enable heavy legacy feature (significantly increases build time and binary size)")
然后正常添加子模块:
add_subdirectory(external/vendor-sdk-v2.3.0)
效果立竿见影:
- 子模块里
option()的默认值被彻底忽略 - CMake GUI / ccmake / 命令行都能看到我们自己的描述
- CI 想临时打开时只需要加
-DENABLE_HEAVY_FEATURE=ON - 再也没有全局宏污染
为什么必须用 set(… CACHE …)?
| 写法 | 是否推荐 | 后果 |
|---|---|---|
父目录再次 option(ENABLE_HEAVY_FEATURE ...) |
不推荐 | CMake 直接报错 |
父目录 set(ENABLE_HEAVY_FEATURE ON)(无 CACHE) |
勉强可用 | 普通变量会被子模块的 option() 重新变成缓存变量,行为混乱 |
父目录 set(... CACHE BOOL ...) |
强烈推荐 | 完全接管默认值、描述、类型,最清晰 |
只靠命令行 -D |
推荐配合使用 | 最灵活,适合 CI 多配置构建 |
我们现在的统一写法(企业级项目标配)
# cmake/options-thirdparty.cmake
set(ENABLE_HEAVY_FEATURE OFF CACHE BOOL "[Vendor] Heavy legacy mode (+6 min build)")
set(ENABLE_EXPERIMENTAL_MODULE ON CACHE BOOL "[Vendor] Next-gen experimental driver")
set(ENABLE_DEBUG_SYMBOLS OFF CACHE BOOL "[Vendor] Keep private debug symbols" OFF)
# 所有 add_subdirectory 之前统一 include 一次
include(cmake/options-thirdparty.cmake)
这样整个团队和所有 CI 看到的都是同一份干净的开关列表,再也不可能出现“我以为关了结果其实没关”的情况。
小结
接管第三方子模块的 option,只需要记住最核心的一句话:
在 add_subdirectory 之前,用
set(XXX xxx CACHE BOOL "你的描述")强行设定即可。