MiB*_*MiB 7 windows dll cmake shared-libraries
我创建了一个项目,该项目分为几个目录,每个目录在add_library(SubdirectoryProject SHARED ${ALL_FILES})CMake 中创建一个共享库。
我有一个test我使用的子目录
add_executable(unittest ${ALL_FILES})
# Dependencies
target_link_libraries(unittest
PUBLIC
GTest::GTest
Boost::log
Boost::json
magic_enum::magic_enum
SubdirectoryProject
)
find_package(GTest REQUIRED)
enable_testing()
include(GoogleTest)
gtest_discover_tests(unittest)
Run Code Online (Sandbox Code Playgroud)
一切都构建得很好。但是,在构建测试时,cmake(或 GTest)也会尝试运行已编译的unittest二进制文件来发现测试。在 Windows 上,这不起作用,因为测试二进制文件位于其自己的构建文件夹中,而来自其他子项目的构建(共享)DLL 位于各自的文件夹中。Windows 没有 rpath。因此,执行测试二进制文件失败,并出现找不到库的错误。
那么该怎么办?在发现测试之前我真的必须手动复制 DLL 吗?我觉得必须有一种 cmake 方式来正确且自动地执行此操作,但我找不到任何东西。
这是我的 MRE 项目。为了方便起见,我已在https://github.com/alexreinking/so69978314上发布了此内容,但所需的所有内容都包含在当前答案中。
\n这是目录结构:
\n$ tree\n.\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 CMakeLists.txt\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 include\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 myLib.h\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 src\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 myLib.cpp\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 test\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 test.cpp\n\n3 directories, 4 files\nRun Code Online (Sandbox Code Playgroud)\n以下是文件内容:
\n# CMakeLists.txt\ncmake_minimum_required(VERSION 3.22)\nproject(so69978314)\n\nif (PROJECT_IS_TOP_LEVEL)\n include(CTest)\nendif ()\n\noption(BUILD_SHARED_LIBS "Build shared libraries instead of static libraries" ON)\n\nfind_package(tinyxml2 REQUIRED)\n\nadd_library(myLib src/myLib.cpp include/myLib.h)\nadd_library(myLib::myLib ALIAS myLib)\ntarget_link_libraries(myLib PRIVATE tinyxml2::tinyxml2)\ntarget_include_directories(\n myLib PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")\nset_target_properties(myLib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES)\n\nif (BUILD_TESTING)\n find_package(GTest REQUIRED)\n include(GoogleTest)\n\n add_executable(unittest test/test.cpp)\n target_link_libraries(unittest PRIVATE GTest::gtest GTest::gtest_main myLib::myLib)\n\n if (WIN32)\n add_custom_command(\n TARGET unittest POST_BUILD\n COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:unittest> $<TARGET_FILE_DIR:unittest>\n COMMAND_EXPAND_LISTS\n )\n endif ()\n\n gtest_discover_tests(unittest)\nendif ()\n\nRun Code Online (Sandbox Code Playgroud)\n// include/myLib.h\n#ifndef MYLIB_H\n#define MYLIB_H\n\nbool validate_xml(const char *xml);\n\n#endif\nRun Code Online (Sandbox Code Playgroud)\n// src/myLib.cpp\n#include "myLib.h"\n\n#include <tinyxml2.h>\n\nbool validate_xml(const char *xml) {\n tinyxml2::XMLDocument doc;\n doc.Parse(xml);\n\n return doc.ErrorID() == 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n// test/test.cpp\n#include <myLib.h>\n\n#include <gtest/gtest.h>\n\nnamespace {\n\nTEST(MyLibTest, Success) { ASSERT_TRUE(validate_xml("<element/>")); }\nTEST(MyLibTest, Fail) { ASSERT_FALSE(validate_xml("<foo></bar>")); }\n\n} // namespace\nRun Code Online (Sandbox Code Playgroud)\n这里有两个特定于 Windows 的 CMake 设置。第一个可以无条件应用:
\nset_target_properties(myLib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES)\nRun Code Online (Sandbox Code Playgroud)\n这是为了方便...Linux 和 macOS 共享库默认导出所有符号,而 Windows 则执行正确的操作,仅公开那些显式导出的符号。我认为这对于 StackOverflow 的答案来说太多了,所以这是一个捷径。
\n更原则性的方法将设定:
\nset(CMAKE_CXX_VISIBILITY_PRESET hidden)\nset(CMAKE_VISIBILITY_INLINES_HIDDEN YES)\nRun Code Online (Sandbox Code Playgroud)\n并使用该GenerateExportHeader模块来控制符号可见性。
现在,对各种共享库进行测试的真正核心是:
\nif (WIN32)\n add_custom_command(\n TARGET unittest POST_BUILD\n COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:unittest> $<TARGET_FILE_DIR:unittest>\n COMMAND_EXPAND_LISTS\n )\nendif ()\nRun Code Online (Sandbox Code Playgroud)\n这使用了 CMake 3.21 中的一个新功能:$<TARGET_RUNTIME_DLLS:>生成器表达式。在 DLL 平台上,它扩展到给定目标所依赖的 DLL 的传递列表。在本例中,它将是 Google Test 的两个库 ( gtest.dll、gtest_main.dll)、TinyXml2 的库 ( tinyxml2.dll) 和内部myLib.dll.
传递该值cmake -E copy_if_different以将这些 DLL 放置在二进制文件旁边unittest。目标目录由 给出$<TARGET_FILE_DIR:unittest>。
自定义命令受 保护,if (WIN32)否则$<TARGET_RUNTIME_DLLS:unittest>将为空。在这种情况下,该cmake -E copy_if_different命令将无法获得足够的参数,并且将在构建时失败。
最后COMMAND_EXPAND_LISTS确保返回的分号分隔列表$<TARGET_RUNTIME_DLLS:unittest>被分成多个参数,而不是作为带有转义分号的单个参数传递。
要查看这是否适用于 Windows,请检查相关 GitHub Actions 工作流程的输出日志: https: //github.com/alexreinking/so69978314/runs/6546304512 ?check_suite_focus=true
\n