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 "你的描述") 强行设定即可。