CMake + GoogleTest

57
我刚刚下载了googletest,使用CMake生成了它的makefile并编译了它。现在,我需要在我的测试项目中使用它。
使用CMake时,我被建议不要直接指向gtest库(使用 include_directories 或 link_directories ),而是使用 find_package()代替。
问题是,为生成的gtest makefile没有安装目标。我无法理解 find_package(GTest REQUIRED)如何在没有某种安装的情况下运行。此外,将gtest文件夹放置在我的项目子文件夹中是不可能的。
谢谢任何帮助。
6个回答

66
这是一个不同寻常的情况,大多数项目都会指定安装规则。
CMake的ExternalProject_Add模块可能是完成此任务的最佳工具。这允许您从项目内部下载、配置和构建gtest,然后链接到gtest库。
我已在Windows上使用Visual Studio 10和11以及Ubuntu上使用GCC 4.8和Clang 3.2测试了以下CMakeLists.txt - 它可能需要针对其他平台/编译器进行调整:
cmake_minimum_required(VERSION 2.8.7 FATAL_ERROR)
project(Test)

# Create main.cpp which uses gtest
file(WRITE src/main.cpp "#include \"gtest/gtest.h\"\n\n")
file(APPEND src/main.cpp "TEST(A, B) { SUCCEED(); }\n")
file(APPEND src/main.cpp "int main(int argc, char **argv) {\n")
file(APPEND src/main.cpp "  testing::InitGoogleTest(&argc, argv);\n")
file(APPEND src/main.cpp "  return RUN_ALL_TESTS();\n")
file(APPEND src/main.cpp "}\n")

# Create patch file for gtest with MSVC 2012
if(MSVC_VERSION EQUAL 1700)
  file(WRITE gtest.patch "Index: cmake/internal_utils.cmake\n")
  file(APPEND gtest.patch "===================================================================\n")
  file(APPEND gtest.patch "--- cmake/internal_utils.cmake   (revision 660)\n")
  file(APPEND gtest.patch "+++ cmake/internal_utils.cmake   (working copy)\n")
  file(APPEND gtest.patch "@@ -66,6 +66,9 @@\n")
  file(APPEND gtest.patch "       # Resolved overload was found by argument-dependent lookup.\n")
  file(APPEND gtest.patch "       set(cxx_base_flags \"\${cxx_base_flags} -wd4675\")\n")
  file(APPEND gtest.patch "     endif()\n")
  file(APPEND gtest.patch "+    if (MSVC_VERSION EQUAL 1700)\n")
  file(APPEND gtest.patch "+      set(cxx_base_flags \"\${cxx_base_flags} -D_VARIADIC_MAX=10\")\n")
  file(APPEND gtest.patch "+    endif ()\n")
  file(APPEND gtest.patch "     set(cxx_base_flags \"\${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32\")\n")
  file(APPEND gtest.patch "     set(cxx_base_flags \"\${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN\")\n")
  file(APPEND gtest.patch "     set(cxx_exception_flags \"-EHsc -D_HAS_EXCEPTIONS=1\")\n")
else()
  file(WRITE gtest.patch "")
endif()

# Enable ExternalProject CMake module
include(ExternalProject)

# Set the build type if it isn't already
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

# Set default ExternalProject root directory
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/ThirdParty)

# Add gtest
ExternalProject_Add(
    googletest
    SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk/
    SVN_REVISION -r 660
    TIMEOUT 10
    PATCH_COMMAND svn patch ${CMAKE_SOURCE_DIR}/gtest.patch ${CMAKE_BINARY_DIR}/ThirdParty/src/googletest
    # Force separate output paths for debug and release builds to allow easy
    # identification of correct lib in subsequent TARGET_LINK_LIBRARIES commands
    CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
               -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs
               -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs
               -Dgtest_force_shared_crt=ON
    # Disable install step
    INSTALL_COMMAND ""
    # Wrap download, configure and build steps in a script to log output
    LOG_DOWNLOAD ON
    LOG_CONFIGURE ON
    LOG_BUILD ON)

# Specify include dir
ExternalProject_Get_Property(googletest source_dir)
include_directories(${source_dir}/include)

# Add compiler flag for MSVC 2012
if(MSVC_VERSION EQUAL 1700)
  add_definitions(-D_VARIADIC_MAX=10)
endif()

# Add test executable target
add_executable(MainTest ${PROJECT_SOURCE_DIR}/src/main.cpp)

# Create dependency of MainTest on googletest
add_dependencies(MainTest googletest)

# Specify MainTest's link libraries
ExternalProject_Get_Property(googletest binary_dir)
if(MSVC)
  set(Suffix ".lib")
else()
  set(Suffix ".a")
  set(Pthread "-pthread")
endif()
target_link_libraries(
    MainTest
    debug ${binary_dir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gtest${Suffix}
    optimized ${binary_dir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gtest${Suffix}
    ${Pthread})

如果您在一个空目录(比如 MyTest)中创建了这个文件并命名为 CMakeLists.txt,那么:
cd MyTest
mkdir build
cd build
cmake ..

这将在MyTest/src中创建一个基本的main.cpp文件,并在Windows平台上创建一个项目文件(MyTest/build/Test.sln)。

在构建项目时,它应该会下载gtest源代码到MyTest/build/ThirdParty/src/googletest,并在MyTest/build/ThirdParty/src/googletest-build中构建它们。然后您应该能够成功运行MainTest目标。


4
对我来说似乎有些复杂。有没有直接引用 Google Test 的 CMakeLists.txt 文件的方法?或者我应该将 Google Test 库作为子目录添加进来? - Korchkidu
3
我认为这并不比将googletest添加为子目录更复杂,但如果可以选择的话,当然可以将googletest作为您源代码树的子目录,并通过ADD_SUBDIRECTORY引入它。(在您最初的问题中,您明确指出这不是一个选项)。 - Fraser
9
我同意这可能会更简单一些,但我不认为这更干净。 通过使用ADD_SUBDIRECTORY选项,你的源代码树中将包含第三方源代码,而ExternalProject_Add则将第三方代码放在(可丢弃的)构建树中,因此你的源代码树仅包含自己的源代码。但最终选择取决于您 - 我并不真的强烈推荐其中之一。 - Fraser
@Fraser,为什么要通过cmake动态创建main.cpp文件,而不是按照传统方式编写它? - 1v0
@1v0 真的只是为了让这个例子完全自包含。在生产代码中我不会这样做。 - Fraser
显示剩余7条评论

35
虽然原始问题已经过去很久,但为了他人的利益,可以使用ExternalProject来下载gtest源代码,然后使用add_subdirectory()将其添加到您的构建中。这样做有以下优点:
  • gtest作为您主要构建的一部分进行构建,因此使用相同的编译器标志等,并且不需要在任何地方安装。
  • 无需将gtest源代码添加到您自己的源代码树中。
通常情况下,ExternalProject不会在配置时(即运行CMake时)进行下载和解压缩,但可以让它执行此操作。我已经写了一篇博客文章介绍如何做到这一点,并提供了一个通用实现,适用于任何使用CMake作为构建系统的外部项目,而不仅仅是gtest。您可以在这里找到它:

https://crascit.com/2015/07/25/cmake-gtest/

更新:上述方法现在也包含在googletest文档中。 (链接)

太棒了!为什么这不是CMake内置的功能呢?你可以提交它吗? - Zak

9
我的回答基于 firegurafiku 的回答。我做了以下修改:
  1. ExternalProject_Add 调用中添加了 CMAKE_ARGS,以便它可以与 msvc 一起使用。
  2. 从文件位置获取 gtest 源代码,而不是下载。
  3. 添加了可移植(适用于 MSVC 和非 MSVC)的 IMPORTED_LOCATION 的定义和使用。
  4. 解决了 set_target_properties 调用在配置时无法工作的问题,因为 INTERFACE_INCLUDE_DIRECTORIES 还不存在,因为外部项目尚未构建。
我更喜欢将 gtest 作为外部项目保留,而不是直接将其源代码添加到我的项目中。原因之一是因为当我搜索我的代码时,我不想包含 gtest 源代码。任何特殊的构建标志,如果在构建 gtest 时也需要使用,则可以添加到对 ExternalProject_Add 调用中的 CMAKE_ARGS 定义中。
这是我修改后的方法:
include(ExternalProject)

# variables to help keep track of gtest paths
set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest")
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build")
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include")

# external project download and build (no install for gtest)
ExternalProject_Add(GTestExternal
    URL ${CMAKE_CURRENT_SOURCE_DIR}/../googletest
    PREFIX "${GTEST_PREFIX}"

    # cmake arguments
    CMAKE_ARGS -Dgtest_force_shared_crt=ON

    # Disable install step
    INSTALL_COMMAND ""

    # Wrap download, configure and build steps in a script to log output
    LOG_DOWNLOAD ON
    LOG_CONFIGURE ON
    LOG_BUILD ON
    )

# variables defining the import location properties for the generated gtest and
# gtestmain libraries
if (MSVC)
    set(GTEST_IMPORTED_LOCATION
        IMPORTED_LOCATION_DEBUG           "${GTEST_LOCATION}/Debug/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}"
        IMPORTED_LOCATION_RELEASE         "${GTEST_LOCATION}/Release/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}"
        )
    set(GTESTMAIN_IMPORTED_LOCATION
        IMPORTED_LOCATION_DEBUG           "${GTEST_LOCATION}/Debug/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}"
        IMPORTED_LOCATION_RELEASE         "${GTEST_LOCATION}/Release/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}"
        )
else()
    set(GTEST_IMPORTED_LOCATION
        IMPORTED_LOCATION                 "${GTEST_LOCATION}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}")
    set(GTESTMAIN_IMPORTED_LOCATION
        IMPORTED_LOCATION                 "${GTEST_LOCATION}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
endif()

# the gtest include directory exists only after it is build, but it is used/needed
# for the set_target_properties call below, so make it to avoid an error
file(MAKE_DIRECTORY ${GTEST_INCLUDES})

# define imported library GTest
add_library(GTest IMPORTED STATIC GLOBAL)
set_target_properties(GTest PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES     "${GTEST_INCLUDES}"
    IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}"
    ${GTEST_IMPORTED_LOCATION}
    )

# define imported library GTestMain
add_library(GTestMain IMPORTED STATIC GLOBAL)
set_target_properties(GTestMain PROPERTIES
    IMPORTED_LINK_INTERFACE_LIBRARIES GTest
    ${GTESTMAIN_IMPORTED_LOCATION}
    )

# make GTest depend on GTestExternal
add_dependencies(GTest GTestExternal)

#
# My targets
#

project(test_pipeline)
add_executable(${PROJECT_NAME} test_pipeline.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_link_libraries(${PROJECT_NAME} ${TBB_LIBRARIES})
target_link_libraries(${PROJECT_NAME} GTest)

感谢对之前答案的精彩修正。 顺便问一下,当我在 Visual Studio 中按下“重新生成”按钮来重新生成与 gtest 相关联的任何项目时,它会重新下载和重建 gtest。这是正常行为吗?为什么“外部项目”也要重新构建? - Aleksei Petrenko
1
CMake ExternalProject的详细行为对我来说仍然有些神秘。就我所描述的方式,gtest已经是本地的了,因此至少不会重新下载。一般来说,在Visual Studio中,Rebuild将重建所选项目以及其依赖的所有其他项目。在cmake生成的解决方案/项目中必须有某些东西使ExternalProject Add目标成为依赖项。因此,gtest将被重新构建。为避免这种情况,您可以进行“仅项目重建”,或者转到“生成”->“批量生成”并清除要重新构建的项目。 - Phil
谢谢你的回答! 此外,如果您只是按照上述步骤进行操作,则在使用默认设置的 Visual Studio 中无法构建应用程序(您将收到多个符号链接错误),因为 gtest 库使用 /MT 运行时而不是默认的 /MD。这里有一个关于此的常见问题解答:https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#i-am-building-my-project-with-google-test-in-visual-studio-and-all-im-getting-is-a-bunch-of-linker-errors-or-warnings-help - Aleksei Petrenko
我以为 CMAKE_ARGS -Dgtest_force_shared_crt=ON 会导致 gtest 使用 /MD 编译。 - Phil

7

使用ExternalProject模块和cmake的导入库功能,有一种不太复杂的解决方案。它从代码库中检出代码,构建它并从构建的静态库创建目标(在我的系统上它们是libgtest.alibgtest_main.a)。

find_package(Threads REQUIRED)
include(ExternalProject)

set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest")
ExternalProject_Add(GTestExternal
    SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk
    SVN_REVISION -r HEAD
    TIMEOUT 10
    PREFIX "${GTEST_PREFIX}"
    INSTALL_COMMAND "")

set(LIBPREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}")
set(LIBSUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build")
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include")
set(GTEST_LIBRARY  "${GTEST_LOCATION}/${LIBPREFIX}gtest${LIBSUFFIX}")
set(GTEST_MAINLIB  "${GTEST_LOCATION}/${LIBPREFIX}gtest_main${LIBSUFFIX}")

add_library(GTest IMPORTED STATIC GLOBAL)
set_target_properties(GTest PROPERTIES
    IMPORTED_LOCATION                 "${GTEST_LIBRARY}"
    INTERFACE_INCLUDE_DIRECTORIES     "${GTEST_INCLUDES}"
    IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}")

add_library(GTestMain IMPORTED STATIC GLOBAL)
set_target_properties(GTestMain PROPERTIES
    IMPORTED_LOCATION "${GTEST_MAINLIB}"
    IMPORTED_LINK_INTERFACE_LIBRARIES
        "${GTEST_LIBRARY};${CMAKE_THREAD_LIBS_INIT}")

add_dependencies(GTest GTestExternal)

您可能想要替换SVN_REVISION或在此处添加LOG_CONFIGURELOG_BUILD选项。创建GTestGTestMain目标后,可以像这样使用它们:

add_executable(Test
    test1.cc
    test2.cc)
target_link_libraries(Test GTestMain)

或者,如果你有自己的 main() 函数:

add_executable(Test
    main.cc
    test1.cc
    test2.cc)
target_link_libraries(Test GTest)

2
你如何添加包含目录? - Jeff
1
@Jeff:更新了答案,抱歉半年的延迟。请查看目标的INTERFACE_INCLUDE_DIRECTORIES属性。 - firegurafiku
这个解决方案是否仍然存在此处所述的标志问题(https://github.com/iauns/cpm-google-test/blob/master/CMakeLists.txt)?总结一下问题:事实证明,将Google测试实现为外部项目是一个坏主意,因为外部项目不共享您的编译器标志。 - Jansen du Plessis
1
@JansenduPlessis:我认为它有些问题,但是你可以将CMAKE_ARGSCMAKE_CACHE_ARGS参数传递给ExternalProject_Add。也许在存在像CatchBoost.Test这样的头文件库时,使用GoogleTest可能不是一个好主意。 - firegurafiku
1
不要删除INTERFACE_INCLUDE_DIRECTORIES行,而是创建路径,这样cmake在设置需要它的目标属性时就不会出错。在调用“set_target_properties”之前,我使用“file(MAKE_DIRECTORY ${GTEST_INCLUDES})”。 - Phil
显示剩余4条评论

5
这个主题有点旧,但在CMake中出现了一种新的引入外部库的方式。
#Requires CMake 3.16+
include(FetchContent)

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_MakeAvailable(googletest)

如果您想支持较早版本的cmake:
# Requires CMake 3.11+
include(FetchContent)

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
  FetchContent_Populate(googletest)
  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
endif()

然后,您只需要添加。
enable_testing()

add_executable(test ${SOURCES} )

target_link_libraries(test gtest_main ${YOUR_LIBS})

add_test(NAME tests COMMAND test)

进一步阅读:https://cmake.org/cmake/help/latest/module/FetchContent.html

-1

当您通过以下方式获取libgtest-dev软件包时

sudo apt install libgtest-dev

源代码存储在位置 /usr/src/googletest

您可以简单地将您的 CMakeLists.txt 指向该目录,以便它能够找到必要的依赖项

类似以下内容

add_subdirectory(/usr/src/googletest gtest)
target_link_libraries(your_executable gtest)

考虑使用FindPackage来实现此功能。 - Sergei Krivonos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接