一句话结论:CMake 不会把依赖 DLL 拷到可执行目标目录,导致单元测试报“找不到 xxx.dll”。用
add_custom_command+ 生成器表达式,按 Ninja(单配置)和 Visual Studio(多配置)区分目标目录即可。
我遇到的问题
最近做 sese-core,运行单元测试时又遇到“找不到 xxx.dll”的报错。原因是 CMake 不会直接把依赖项拷贝到对应的可执行目标文件目录下。
复现结构(示例项目)
MyProject
│ CMakeLists.txt
│
├─MyLib
│ CMakeLists.txt
│ MyLib.cpp
│
└─MyTest
CMakeLists.txt
MyTest.cpp
MyTest 依赖 MyLib。生成单元测试 MyTest 时,MyLib.dll 并不会拷贝到 MyTest.exe 的同级目录,运行即报错。
原因:不同生成器的目录结构不同
网上大多数答案只提到用 add_custom_command 拷贝文件(我先前也是这么做),但没考虑到不同生成器构建产生的二进制文件目录结构是有些许不同的:
- Ninja / Makefiles:单配置,直接拷到目标目录即可。
- Visual Studio:一次生成 Debug、Release 等多套配置,二进制目录会多一层同名目录;此时
CMAKE_BUILD_TYPE是空值,无法用来判断构建所用的配置。
解决方案:add_custom_command + 生成器表达式
判断是否使用了 Ninja;未使用则按 VS 的多配置生成器处理,用生成器表达式决定目标目录:
if (${CMAKE_GENERATOR} MATCHES "Ninja")
# 单配置
add_custom_command(
TARGET
MyLib
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:MyLib> ${PROJECT_BINARY_DIR}/MyTest
)
else ()
# 多配置
add_custom_command(
TARGET
MyLib
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:MyLib> ${PROJECT_BINARY_DIR}/MyTest/$<CONFIG>
)
endif ()
后来的改进(2024.06.24)
用最新的 msbuild 时发现存在平台行为不一致:生成的文件会被直接命名为 Debug / Release 等构建类型名称。所以改成封装成宏,支持多层目录之间的拷贝,并针对 Visual Studio 生成器处理:
macro(sese_copy_dll_to_binary_path target dest)
if (${CMAKE_GENERATOR} MATCHES "Ninja")
# 单配置
add_custom_command(
TARGET
${target}
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${target}> "${PROJECT_BINARY_DIR}/${dest}"
)
elseif (${CMAKE_GENERATOR} MATCHES "Visual Studio")
# 多配置
add_custom_command(
TARGET
${target}
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${target}> "${PROJECT_BINARY_DIR}/${dest}/$<CONFIG>/$<TARGET_FILE_NAME:${target}>"
)
endif ()
endmacro()
说明 / 适用范围
- 指定两种个人常用 case:VS Code 默认用 VS 生成器,CLion 默认用 Ninja 生成器。
- 宏支持多层目录:用
add_subdirectory会产生一级新目录,具体情况具体分析。 - 只适配并测试了 Windows 平台,工具链版本可能与各位不同,更多是提供一种思路,而不是完美的解决方案。
相关阅读
- Windows 下 IOCP 的简单使用 —— Windows / C++ 的另一篇
- 看看全部技术笔记
- Shiina Labs 开源项目