4. option命令:option()定义BOOL型缓存选项,与set() + BOOL + CACHE的对比
好,咱们来聊聊 option() 这个命令。说实话,很多新手甚至一些老手,都觉得它就是个语法糖。但我在项目中踩过几次坑之后,发现事情没那么简单。
4.1 先看个最直观的例子
你想想看,如果我想让用户决定要不要开启某个实验性功能,最直接的方式是什么?
# 方式一:用 option()
option(USE_EXPERIMENTAL_FEATURE "启用实验性功能" OFF)
# 方式二:用 set() + BOOL + CACHE
set(USE_EXPERIMENTAL_FEATURE OFF CACHE BOOL "启用实验性功能")
嗯,从功能上看,这两行代码的效果几乎一模一样。它们都会在 CMake 缓存中创建一个 BOOL 类型的变量,默认值是 OFF,用户可以通过 cmake -DUSE_EXPERIMENTAL_FEATURE=ON 来修改。
那为什么还要搞两个写法?
4.2 核心区别:谁说了算?
我个人习惯用 option(),因为它有一个非常重要的特性——它不会覆盖用户已经设置的值。说白了,就是「用户优先」。
举个例子:
# CMakeLists.txt
option(MY_FLAG "我的标志" ON)
# 用户第一次构建时
cmake -DMY_FLAG=OFF ..
# 此时 MY_FLAG = OFF
# 用户第二次构建时,即使不传 -D
cmake ..
# MY_FLAG 仍然是 OFF,不会变回 ON
为什么会这样?因为 option() 内部逻辑是:如果缓存中已经有这个变量了,就别动它。只有第一次构建,或者缓存被清空时,才会用默认值。
而 set() + CACHE 呢?它默认会强制覆盖缓存中的值。除非你加上 FORCE 参数,但那就更暴力了。
核心结论:
option():尊重用户选择,不覆盖已有缓存set() + CACHE:每次运行都会覆盖缓存(除非用FORCE控制)
4.3 避坑指南:我曾经犯过的错
我曾经在一个大型项目中,用 set() + CACHE 定义了一个调试开关:
set(ENABLE_DEBUG_LOG ON CACHE BOOL "启用调试日志")
结果每次我同事重新 cmake 配置时,这个开关都会被重置为 ON。他明明已经改成 OFF 了,但一重新配置又变回去。气得他差点把键盘砸了。
后来我换成 option(),问题就解决了。所以,如果你想让用户能自由切换,就用 option()。
4.4 什么时候用 set() + CACHE?
那 set() + CACHE 就一无是处吗?也不是。我建议在以下场景用它:
- 你想强制某个值,比如项目必须用 C++17,你可以写:
set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++标准")。这样即使用户想改,也会被覆盖回去。 - 你需要非 BOOL 类型,比如
STRING、FILEPATH、PATH等。option()只支持BOOL。 - 你想在子目录中定义缓存变量,并且希望它影响父目录。
option()的作用域有点微妙,而set() + CACHE更可控。
小技巧:如果你用 set() + CACHE,但又想保留用户的选择,可以加个判断:
if(NOT DEFINED CACHE{MY_VAR})
set(MY_VAR "default" CACHE STRING "描述")
endif()
这样只有第一次才会设置默认值,之后用户改了就保留。
4.5 对比表格:一目了然
| 特性 | option() | set() + BOOL + CACHE |
|---|---|---|
| 支持类型 | 仅 BOOL | 所有类型(BOOL, STRING, PATH...) |
| 覆盖行为 | 不覆盖已有缓存 | 默认覆盖(除非加 FORCE) |
| 代码简洁度 | 高 | 低 |
| 适用场景 | 用户可选的开关 | 强制配置、非 BOOL 类型 |
4.6 一个实际项目中的选择
我记得有一次做嵌入式项目,需要让用户选择是否启用浮点运算加速。我用了 option():
option(ENABLE_FPU_ACCELERATION "启用硬件浮点加速" ON)
if(ENABLE_FPU_ACCELERATION)
target_compile_definitions(my_app PRIVATE USE_FPU=1)
target_link_options(my_app PRIVATE -mfloat-abi=hard)
else()
target_compile_definitions(my_app PRIVATE USE_FPU=0)
target_link_options(my_app PRIVATE -mfloat-abi=soft)
endif()
这样用户可以在不同硬件版本之间自由切换。如果用 set() + CACHE,每次重新 cmake 都会重置为 ON,那用户就疯了。
4.7 总结一下
说白了,option() 就是 set() + BOOL + CACHE 的一个「用户友好版」。它帮你处理了「不要覆盖用户选择」这个逻辑。除非你有特殊需求(比如强制某个值、或者需要非 BOOL 类型),否则我建议你无脑用 option()。
嗯,这里要注意:option() 的默认值必须是 ON 或 OFF,不能写 TRUE 或 FALSE,虽然也能用,但不符合规范。我见过有人写 option(XXX "描述" TRUE),结果 CMake 直接报错。
最后送你一句话:能用 option() 就别用 set() + CACHE,除非你真的知道自己在干什么。