如何在CMake中用CONFIG生成器表达式提取复杂配置片段
这确实是CMake多配置场景下挺头疼的一个问题——尤其是当你的配置是「硬件类型+运行模式」这类组合结构,还带例外情况的时候。我之前处理过类似的场景,分享几个实用的解决方案:
方案1:预定义配置映射表+生成器表达式批量展开
这个方案的核心是把所有支持的配置集中定义成一个映射表,关联每个配置对应的硬件类型和模式,然后通过循环批量生成配置相关的变量,最后用生成器表达式在构建时匹配。
首先,先把所有合法的配置列出来,同时绑定对应的hw和mode:
# 格式:<配置名> "<硬件类型>;<模式>" set(SUPPORTED_CONFIGS A_1 "A;1" A_2 "A;2" B_1 "B;1" B_2 "B;2" B_3 "B;3" C_2 "C;2" C_3 "C;3" )
接下来,遍历这个映射表,为每个配置设置编译器路径、sysroot,或者标记需要引入的源文件:
foreach(config_entry IN LISTS SUPPORTED_CONFIGS) # 拆分配置项:提取配置名、硬件类型、模式 list(GET config_entry 0 config_name) list(GET config_entry 1 hw_mode_pair) list(GET hw_mode_pair 0 hw_type) list(GET hw_mode_pair 1 mode_type) # 为当前配置设置编译器路径 if(hw_type STREQUAL "A") set(CMAKE_CXX_COMPILER_${config_name} "/this/g++" PARENT_SCOPE) else() set(CMAKE_CXX_COMPILER_${config_name} "/that/g++" PARENT_SCOPE) endif() # 标记当前配置是否需要引入mode2.cpp if(mode_type STREQUAL "2") set(NEED_MODE2_${config_name} ON PARENT_SCOPE) else() set(NEED_MODE2_${config_name} OFF PARENT_SCOPE) endif() endforeach()
最后,在添加源文件或设置属性时,用生成器表达式动态匹配当前配置的标记:
add_executable(MyApp main.cpp # 只有当前配置标记了需要mode2时才引入 $<$<BOOL:${NEED_MODE2_$<CONFIG>>}:mode2.cpp> )
这个方案的优势是所有配置逻辑集中在一处,新增硬件/模式时,只需要修改SUPPORTED_CONFIGS列表,不需要到处调整条件判断,维护性很好。
方案2:自定义配置注册函数+批量遍历组合
如果你的配置组合数较多(比如未来可能新增更多硬件类型),手动列SUPPORTED_CONFIGS会比较麻烦,可以用自定义函数自动遍历所有可能的组合,并跳过不支持的例外配置。
先封装一个注册单个配置的函数:
function(register_target_config config_name hw mode) # 跳过不支持的例外配置 if((hw STREQUAL "A" AND mode STREQUAL "3") OR (hw STREQUAL "C" AND mode STREQUAL "1")) return() endif() # 设置编译器路径 if(hw STREQUAL "A") set(CMAKE_CXX_COMPILER_${config_name} "/this/g++" PARENT_SCOPE) else() set(CMAKE_CXX_COMPILER_${config_name} "/that/g++" PARENT_SCOPE) endif() # 设置mode2源文件标记 set(NEED_MODE2_${config_name} $<STREQUAL:${mode},2> PARENT_SCOPE) endfunction()
然后批量遍历所有硬件和模式的组合,自动注册合法配置:
# 遍历所有可能的硬件类型 foreach(hw IN ITEMS A B C) # 遍历所有可能的模式 foreach(mode IN ITEMS 1 2 3) register_target_config(${hw}_${mode} ${hw} ${mode}) endforeach() endforeach()
后续添加新硬件(比如D)或者新模式(比如4)时,只需要修改foreach的ITEMS列表,函数会自动处理例外规则,完全不需要手动维护配置列表,扩展性拉满。
方案3:进阶:利用自定义目标动态处理(适合复杂场景)
如果你的场景需要更动态的逻辑(比如构建时才确定配置片段),可以考虑利用CMAKE_CONFIGURATION_TARGETS为每个配置生成自定义目标,在目标中执行脚本拆分配置片段。不过这个方案相对复杂,一般前两个方案已经能覆盖绝大多数需求,这里只做简单提及:
你可以为每个配置生成一个自定义目标,在目标的COMMAND中调用CMake脚本拆分CONFIG变量,然后设置对应的编译参数或源文件。但这种方式会增加构建流程的复杂度,除非必要不建议使用。
总的来说,方案1是最平衡的选择,兼顾了可读性和维护性;如果未来会频繁新增硬件/模式,方案2的自动遍历+函数封装会更省心。两种方案都完美适配Visual Studio、Xcode这类多配置生成器,不需要依赖string(REGEX MATCH)这类仅在配置阶段生效的命令。
内容的提问来源于stack exchange,提问作者Adam Badura

