CMake + GoogleTest
Asked Answered
H

6

57

I just downloaded googletest, generated its makefile with CMake and built it. Now, I need to use it in my testing project.

With CMake, I have been advised not pointing to gtest libraries directly (using include _directories or link_directories) but use find_package() instead.

The problem is, there is no install target for the gtest makefile generated. I cannot understand how find_package(GTest REQUIRED) could work without some kind of installation. Also, putting the gtest folder as a subfolder in my project is not possible.

Thanks for any help.

Hairball answered 13/3, 2012 at 17:40 Comment(0)
D
66

This is an unusual case; most projects specify install rules.

CMake's ExternalProject_Add module is maybe the best tool for this job. This allows you to download, configure and build gtest from within your project, and then link to the gtest libraries.

I've tested the following CMakeLists.txt on Windows with Visual Studio 10 and 11, and on Ubuntu using GCC 4.8 and Clang 3.2 - it might need adjusted for other platforms/compilers:

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})

If you create this as CMakeLists.txt in an empty directory (say MyTest), then:

cd MyTest
mkdir build
cd build
cmake ..

This should create a basic main.cpp in MyTest/src and create a project file (MyTest/build/Test.sln on Windows)

When you build the project, it should download the gtest sources to MyTest/build/ThirdParty/src/googletest, and build them in MyTest/build/ThirdParty/src/googletest-build. You should then be able to run the MainTest target successfully.

Dollarbird answered 14/3, 2012 at 2:45 Comment(12)
Seems complex to me. Is there no way to reference google test CMakeLists.txt file directly? Or should I add googletest library as a subdirectory instead?Hairball
I don't think it's much more complex than adding googletest as a subdirectory, but if you have the choice then sure - add googletest as a subdirectory of your source tree and pull it in via ADD_SUBDIRECTORY. (You did specify that this wasn't an option in your original question).Dollarbird
Yes, indeed. I would like to use a better solution. But a subdirectory seems better and cleaner than the solution you provided...But I am only starting in CMake world...;)Hairball
I'd agree that it might be slightly simpler, but I don't think it's cleaner. With the ADD_SUBDIRECTORY option, you end up with third party source code in your own source tree, whereas the ExternalProject_Add dumps the third party code in your (disposable) build tree, leaving your source tree with only your own source code. It's up to you though - I wouldn't really strongly recommend one over the other.Dollarbird
+1 for keeping the source code tree clean with your first solution. I will investigate both solutions and see which one is best for me. Thanks a lot for your great help!Hairball
Is there a way to make this method work with libc++? I am trying to test C++11 code compiled with clang using -stdlib=libc++, but the test project fails to link because gtest was compiled with whatever flags it defaults to. I tried adding "-DCMAKE_CXX_FLAGS=" with the relevant flags to the ExternalProject_Add command, but that doesn't seem to work.Omora
@Volte Sorry this took me so long to answer - I forgot! Anyway, I've updated the answer so that it includes a patch step to allow a compiler flag to be passed. In this case, it's a MSVC flag, but I'm sure you could adapt that for your case. Something like set(cxx_base_flags \"\${cxx_base_flags} -stdlib=libc++\")\n") inside a suitable elseif(...) condition.Dollarbird
@Dollarbird - I've been trying to setup GTest to run tests against my C++ project. I am new to C++ and CMake and GTest, so I'm quite confused by now. I've created a GTest project and I have my C++ project. I understand that I should add a section to my CMakeLists.txt file in my C++ project to include GTest stuff. I'm trying to follow your advice and include(ExternalProject) but I'm kind of stuck on a few steps. Could you look at my question here: https://mcmap.net/q/152398/-google-test-separate-project-how-to-get-tests-running-against-the-c-project/1735836Hearth
@Dollarbird - would give my right arm to get this using ExternalProject_Add, but I do not understand why I need the GoogleTest source code. I've already built and put the gtest folder under /usr/include/ and the libraries under /usr/lib/. I have a new question here: https://mcmap.net/q/152399/-googletest-cmake-doesn-39-t-recognize-test_f-like-it-39-s-not-recognizing-gtest-something/1735836. Will you please enlighten me?Hearth
@Dollarbird What is the motivation to create the main.cpp on the fly form cmake instead of writing it in the conventional way?Bushed
@Bushed Really just to make this a completely self-contained example. I wouldn't do this in production code.Dollarbird
It would be nice if someone can update this sample to use git instead of svn.Tum
L
35

It is long past when the original question being asked, but for the benefit of others, it is possible to use ExternalProject to download the gtest source and then use add_subdirectory() to add it to your build. This has the following advantages:

  • gtest is built as part of your main build, so it uses the same compiler flags, etc. and doesn't need to be installed anywhere.
  • There's no need to add the gtest sources to your own source tree.

Used in the normal way, ExternalProject won't do the download and unpacking at configure time (i.e. when CMake is run), but you can get it to do so. I've written a blog post on how to do this which also includes a generalised implementation which works for any external project which uses CMake as its build system, not just gtest. You can find it here:

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

Update: The approach described above is now also part of the googletest documentation.

Laural answered 25/7, 2015 at 4:15 Comment(1)
BRAVO! - Why is this not built-in to CMake? Can you submit it?Weatherspoon
Y
9

My answer is based on the answer from firegurafiku. I modified it in the following ways:

  1. added CMAKE_ARGS to the ExternalProject_Add call so it works with msvc.
  2. gets the gtest source from a file location rather than downloading
  3. added portable (for MSVC and non-MSVC) definition and usage of IMPORTED_LOCATION
  4. Addressed the problem with the call to set_target_properties not working at configure time when the INTERFACE_INCLUDE_DIRECTORIES does not yet exist because the external project has not yet been built.

I prefer keeping gtest as an external project rather than adding its source directly to my project. One reason is because I do not like having the gtest source code included when I am searching my code. Any special build flags that are needed by my code that should also be used when building gtest can be added to the definition of CMAKE_ARGS in the call to ExternalProject_Add

Here is my modified approach:

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)
Yawmeter answered 29/6, 2015 at 16:4 Comment(4)
Thanks for the great corrections to the previous answers. BTW, when in Visual Studio I press "Rebuild" for any of my projects that link with gtest it re-downloads and rebuilds the gtest as well. Is this a normal behavior? Why would "External project" be rebuilt?Kelantan
Details of how CMake ExternalProject behaves is still a bit of a mystery to me. The way I described, gtest is already local, so at least it will not re-downloaded. In general in Visual Studio, Rebuild will rebuild not only the selected project but also every other project on which it depends. There must be something in the cmake generated solution/projects that makes an ExternalProject Add target a dependency. Consequently gtest gets rebuilt. To avoid this, you can do a "Project Only Rebuild" or you can go to Build -> Batch Build and clean the projects that you want to rebuild.Yawmeter
Thanks for the answer! Also, to whom it may concern, if you just do the above your application won't build in Visual Studio with default settings (you will be getting multiple symbols link errors), because gtest library uses /MT runtime instead of default /MD. Here is a FAQ entry about that: github.com/google/googletest/blob/master/googletest/docs/…Kelantan
I thought CMAKE_ARGS -Dgtest_force_shared_crt=ON would cause gtest to be built with /MD?Yawmeter
W
7

There is a bit less complex solution using ExternalProject module and imported libraries feature of cmake. It checks out code from repository, builds it and creates target from built static libraries (they're libgtest.a and libgtest_main.a on my system).

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)

You may want to replace SVN_REVISION or add LOG_CONFIGURE and LOG_BUILD options here. After GTest and GTestMain targets are created, they can be used like this:

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

or, if you have your own main() function:

add_executable(Test
    main.cc
    test1.cc
    test2.cc)
target_link_libraries(Test GTest)
Waine answered 15/6, 2014 at 9:39 Comment(9)
How do you add the include directory?Cespitose
@Jeff: updated answer, sorry for half-year delay. See INTERFACE_INCLUDE_DIRECTORIES property of the target.Waine
I had to delete the line INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDES}" in order to make this work with my own main() functionLabelle
does this solution still suffer from the flag issues addressed here. To summarize the issue: It turns out to be a bad idea to implement google test as an external project because the external project does not share your compiler flags.Greatest
@JansenduPlessis: I think it suffers, but you may pass CMAKE_ARGS and CMAKE_CACHE_ARGS parameters to ExternalProject_Add. Maybe it's a bad idea to use GoogleTest at all while there are header-only libraries like Catch and Boost.Test.Waine
Rather than deleting the INTERFACE_INCLUDE_DIRECTORIES line, create the path so cmake will not choke when setting the target properties that needs it. I use file(MAKE_DIRECTORY ${GTEST_INCLUDES}) before the call to set_target_properies.Yawmeter
@Waine On this line you have hard coded that you are build gtest as a static library add_library(GTestMain IMPORTED STATIC GLOBAL) even though everywhere else you seem to support static or shared. Is the only difference that STATIC should become SHARED ?Cloth
@DavidDoria: I haven't tried that, but I think you should also replace CMAKE_STATIC_LIBRARY_PREFIX with CMAKE_SHARED_LIBRARY_PREFIX, and the same for *_SUFFIX.Waine
@DavidDoria: You would also have to pass CMAKE_ARGS -DBUILD_SHARED_LIBS=TRUE parameter to ExternalProject_Add function call. Otherwise, no *.so files are generated by default.Waine
R
5

The topic is a bit old, but there appeared a new way of including external libraries in 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)

If you want to support the earlier versions of 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()

Then you just add

enable_testing()

add_executable(test ${SOURCES} )

target_link_libraries(test gtest_main ${YOUR_LIBS})

add_test(NAME tests COMMAND test)

Further reading: https://cmake.org/cmake/help/latest/module/FetchContent.html

Responsiveness answered 21/10, 2019 at 14:59 Comment(0)
D
-1

When you get the libgtest-dev package via

sudo apt install libgtest-dev

The source is stored in location /usr/src/googletest

You can simply point your CMakeLists.txt to that directory so that it can find the necessary dependencies

Something like the following

add_subdirectory(/usr/src/googletest gtest)
target_link_libraries(your_executable gtest)
Dinner answered 28/6, 2019 at 6:32 Comment(1)
Consider using FindPackage for thisLysozyme

© 2022 - 2024 — McMap. All rights reserved.