VS16下CMake生成器表达式无法正常展开问题咨询
问题原因
这个问题的核心是参数传递逻辑不符合CMake的跨平台规则,和生成器的命令解析机制差异直接相关:
- 你用
list(JOIN)把库列表拼接成空格分隔的单个字符串后,这个值在CMake解析阶段就已经是一个独立的单值参数,不再是多元素列表。 - Linux下的Make、Ninja生成器最终调用shell执行命令,shell默认会按空格拆分传入的字符串,所以多个库路径会被拆成独立参数,歪打正着满足了
copy_if_different的参数要求,看起来运行正常。但这种行为是shell的副作用,只要库路径本身包含空格,哪怕在Linux下也会复制失败。 - Visual Studio生成器的后期生成事件不走shell的空格拆分逻辑,会把传入的带空格的完整字符串(包含VS自动加的引号)当成一个完整的文件路径,自然找不到对应文件,复制失败。
- 你尝试直接用
$<JOIN>拼接列表不生效,一是因为JOIN生成器表达式的输出本身还是单字符串,解决不了参数合并的问题;二是嵌套生成器表达式时如果没有开启列表展开,CMake不会正确解析列表变量,会直接输出原始表达式文本。 - 额外说明:你贴的代码里
$<TARGET_FILE_DIR:estareng>和前面定义的目标名bar不一致,属于笔误,实际使用时要替换成正确的目标名。
修复方案
核心原则是不要提前把库列表拼接成单字符串,让CMake在生成构建脚本时自动把每个库路径作为独立参数传递给复制命令,最可靠的跨平台写法如下:
cmake_minimum_required(VERSION 3.17) project(foo) add_executable(bar foobar.cpp) # 原有查找库的逻辑保持不变,不需要做JOIN操作 list(APPEND DBG_LIBRARIES_LIST "lib1" "lib2") list(APPEND REL_LIBRARIES_LIST "lib1" "lib2") add_custom_command(TARGET bar POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different # 直接通过生成器表达式选择对应配置的库列表,不要提前拼接成字符串 $<IF:$<CONFIG:Debug>,${DBG_LIBRARIES_LIST},${REL_LIBRARIES_LIST}> # 替换成你实际的目标名,这里对应前面定义的bar $<TARGET_FILE_DIR:bar> # 关键参数:告知CMake展开命令参数中的列表为独立的命令行参数 COMMAND_EXPAND_LISTS )
这个写法对所有CMake支持的生成器都有效,哪怕库路径本身包含空格、特殊字符也能正确处理,不会出现参数合并的问题。
如果你的CMake版本低于3.8(COMMAND_EXPAND_LISTS是CMake 3.8版本引入的参数),可以换用循环逐个复制库文件的写法,同样能跨平台生效:
# 分别处理Debug和Release库 foreach(dbg_lib IN LISTS DBG_LIBRARIES_LIST) add_custom_command(TARGET bar POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $<$<CONFIG:Debug>:${dbg_lib}> $<TARGET_FILE_DIR:bar> ) endforeach() foreach(rel_lib IN LISTS REL_LIBRARIES_LIST) add_custom_command(TARGET bar POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $<$<CONFIG:Release>:${rel_lib}> $<TARGET_FILE_DIR:bar> ) endforeach()
内容的提问来源于stack exchange,提问作者Psyhich
相关产品推荐
相关产品推荐

