Windows 上使用 GTest 进行 cmake:构建开始测试,但找不到共享库

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 方式来正确且自动地执行此操作,但我找不到任何东西。

Ale*_*ing 9

项目布局和文件

\n

这是我的 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\n
Run 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\n
Run 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\n
Run 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}\n
Run 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\n
Run Code Online (Sandbox Code Playgroud)\n

解释

\n

这里有两个特定于 Windows 的 CMake 设置。第一个可以无条件应用:

\n
set_target_properties(myLib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES)\n
Run Code Online (Sandbox Code Playgroud)\n

这是为了方便...Linux 和 macOS 共享库默认导出所有符号,而 Windows 则执行正确的操作仅公开那些显式导出的符号。我认为这对于 StackOverflow 的答案来说太多了,所以这是一个捷径。

\n

更原则性的方法将设定:

\n
set(CMAKE_CXX_VISIBILITY_PRESET hidden)\nset(CMAKE_VISIBILITY_INLINES_HIDDEN YES)\n
Run Code Online (Sandbox Code Playgroud)\n

并使用该GenerateExportHeader模块来控制符号可见性。

\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    )\nendif ()\n
Run Code Online (Sandbox Code Playgroud)\n

这使用了 CMake 3.21 中的一个新功能:$<TARGET_RUNTIME_DLLS:>生成器表达式。在 DLL 平台上,它扩展到给定目标所依赖的 DLL 的传递列表。在本例中,它将是 Google Test 的两个库 ( gtest.dllgtest_main.dll)、TinyXml2 的库 ( tinyxml2.dll) 和内部myLib.dll.

\n

传递该值cmake -E copy_if_different以将这些 DLL 放置在二进制文件旁边unittest。目标目录由 给出$<TARGET_FILE_DIR:unittest>

\n

自定义命令受 保护,if (WIN32)否则$<TARGET_RUNTIME_DLLS:unittest>将为空。在这种情况下,该cmake -E copy_if_different命令将无法获得足够的参数,并且将在构建时失败。

\n

最后COMMAND_EXPAND_LISTS确保返回的分号分隔列表$<TARGET_RUNTIME_DLLS:unittest>被分成多个参数,而不是作为带有转义分号的单个参数传递。

\n

要查看这是否适用于 Windows,请检查相关 GitHub Actions 工作流程的输出日志: https: //github.com/alexreinking/so69978314/runs/6546304512 ?check_suite_focus=true

\n