Building GoogleTest and GoogleMock directly in a CMake project

UPDATED December 2015:
Since the original article was written, gtest and gmock have been merged and moved into a single repository on Github under the name GoogleTest. I’ve updated the content here to reflect the changes and the article now also covers both gtest and gmock. I’ve also revised the general purpose implementation to make it more flexible, expanded its documentation and made it available on Github under a MIT license. I hope you find it useful!


Using gtest/gmock with CMake is awesome. Not so awesome is when you don’t have a pre-built gtest/gmock available to use. This article demonstrates a convenient way to add them with automated source download and have them build directly as part of your project using add_subdirectory(). Unlike other common approaches, no manual information has to be provided other than the package to download. The approach is general enough to be applied to any CMake-based external project, not just gtest/gmock.

Before proceeding, I should highlight that if you are only interested in gtest and you do have a pre-built gtest available (e.g. it is provided by the system or you are happy to manually build it outside of your project), then CMake makes it trivial to bring gtest into your project with the find_package() command. See the FindGTest module for more information on this approach, which is simple and provides some nice extra features for defining tests.

The conventional approach

I’ll focus for the moment on gtest, since it’s a little simpler than gmock, but the concepts are similar for both. When a fully integrated download and build of gtest is required, typical advice for building it as part of your CMake project is based around using ExternalProject. The gtest library is created as part of your build, but not in a way which makes the CMake targets available to you automatically. This means you end up manually adding additional CMake code to define the libraries, import targets, etc. to make it seem like gtest is a fully integrated part of your build. This is unfortunate, since this is precisely the sort of thing CMake is supposed to be doing for you.

In reality, the typical way ExternalProject is used results in a separate, self-contained sub-build which your CMake project essentially sees as a black box. A simplified version of the regular ExternalProject approach looks something like this (adapted from a more complete example implementation available here, but using a now outdated URL):

include(ExternalProject)
ExternalProject_Add(googletest
  URL             https://googletest.googlecode.com/files/gtest-1.7.0.zip
  URL_HASH        SHA1=f85f6d2481e2c6c4a18539e391aa4ea8ab0394af
  INSTALL_COMMAND ""
)

ExternalProject_Get_Property(googletest binary_dir)
add_library(gtest      UNKNOWN IMPORTED)
add_library(gtest_main UNKNOWN IMPORTED)
set_target_properties(gtest PROPERTIES
  IMPORTED_LOCATION ${binary_dir}/libgtest.a
)
set_target_properties(gtest_main PROPERTIES
  IMPORTED_LOCATION ${binary_dir}/libgtest_main.a
)
add_dependencies(gtest      googletest)
add_dependencies(gtest_main googletest)

The annoying part of the above is the need to manually create the gtest and gtest_main import libraries. The above is just assuming a particular platform, etc., but to make this fully general for all platforms, compilers, etc. would make the above considerably more complex. CMake has all the required information already, but it is buried in the external project. What we really want is to have gtest included as part of our build directly, not built as a separate, external project.

Getting CMake to do all the work instead

When used in the normal way, ExternalProject performs its download and build steps during the main project’s build phase, but what we really want is to have the download and unpacking steps performed at configure time (ie when CMake is run) and then pull in the source directory with add_subdirectory() rather than having ExternalProject build it. This would make gtest/gmock a fully integrated part of our build and give us access to all the targets, etc. that CMake already defines for us.

While ExternalProject doesn’t natively allow us to perform the download and unpacking steps at configure time, we can make it do so. We achieve this by invoking ExternalProject as an external build which we perform at configure time. While this sounds convoluted, it is actually relatively straightforward.

We first create a template CMakeLists.txt file to use for the external build (we will use simple examples here which are specific to GoogleTest, but the general implementation provided on Github is set up to support any project). It’s contents look something like this:

cmake_minimum_required(VERSION 2.8.2)

project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
  GIT_REPOSITORY    https://github.com/google/googletest.git
  GIT_TAG           master
  SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
  BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
  CONFIGURE_COMMAND ""
  BUILD_COMMAND     ""
  INSTALL_COMMAND   ""
  TEST_COMMAND      ""
)

Assuming the above was saved into a file called CMakeLists.txt.in, we would use it like so in our main project’s CMakeLists.txt:

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" .
  WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" )
execute_process(COMMAND "${CMAKE_COMMAND}" --build .
  WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" )

# Prevent GoogleTest from overriding our compiler/linker options
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This adds
# the following targets: gtest, gtest_main, gmock
# and gmock_main
add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src"
                 "${CMAKE_BINARY_DIR}/googletest-build")

# The gtest/gmock targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if(CMAKE_VERSION VERSION_LESS 2.8.11)
  include_directories("${gtest_SOURCE_DIR}/include"
                      "${gmock_SOURCE_DIR}/include")
endif()

# Now simply link your own targets against gtest, gmock,
# etc. as appropriate

A few key features should be noted. The configure_file() command copies our template CMakeLists.txt.in file to the build area and the target file name must be CMakeLists.txt (the add_subdirectory() command requires this). The configure_file() command also does variable substitution, so the actual value of CMAKE_BINARY_DIR will be replaced with the current value when the file is copied. Another key feature is the way we invoke CMake to setup and execute a sub-build of the CMakeLists.txt file we just copied (this is what the two execute_process() commands are doing). These are simply forcing CMake to immediately fully process the CMakeLists.txt file we copied rather than waiting until build time. This is the crucial feature of the technique.

It should be noted that GoogleTest will modify the compiler/linker options internally unless it is explicitly told not to. This is unfortunate, but easily handled by setting the inaccurately named gtest_force_shared_crt option to TRUE (all this does is prevent GoogleTest from modifying options rather than force it to use shared libraries!). Looking through the GoogleTest project reveals that setting BUILD_SHARED_LIBS to TRUE also has the same effect, but this latter option is not specific to GoogleTest and may have other side effects.

With the above, our project now has gtest, gtest_main, gmock and gmock_main targets directly available to it. CMake knows what library file names are relevant, it will use the same build settings as the rest of our project (e.g. build type Debug or Release, which compiler, etc.) and it will work on all platforms, compilers and CMake generators without having to specify anything manually. It will also add the relevant header search path to any target linking against any of these library targets.

CMake also manages the git clone and checkout steps for us and ensures it doesn’t repeat those steps unless it is required, such as if you change the URL of the git repository, change the git tag or delete the build output directory. In short, CMake is doing all the work for you, as it should!

Generalising for any external project

There’s no inherent assumption in the above which restricts this approach to just gtest or gmock. It can be generalised to support any external project which uses CMake as its build system. This essentially amounts to parameterising the project name and the download, source and binary directories inside a CMake function. It can also be made to support more than just git clone/checkout as a download method, it can easily support anything ExternalProject itself supports. I’ve already gone ahead and done all the work for you, so just grab it from the Github project associated with this article. It even includes a simple example with gtest and gmock test cases to show how to use it. Enjoy!

42 thoughts on “Building GoogleTest and GoogleMock directly in a CMake project

  1. Hello 🙂 Very nice stuff.

    I’ve tried doing the same for gmock, but failing miserably with really weird errors.
    Have you tried using this technique for gmock?

    br
    Frederic

    Like

  2. Hi Craig. By any change have in this time from your last reply configured gmock, it is just that I need to use it whit a similar technique you used for gtest, but since I am a beginner in this matters it looks quit complicated for me, any help you share I will appreciated a lot.

    Like

  3. I’m fairly new to CMake, but this seems like a nice approach. I’m trying to do this with libtins (http://libtins.github.io/) and running into problems, because there the cmake file uses CMAKE_SOURCE_DIR, assuming it to be the top level of the library src hierarchy, which obviously fails when it is loaded via add_subdirectory. Is there a way to workaround this that doesn’t involve patching the library?

    Like

    • From a quick look at libtins, it seems to only use CMAKE_SOURCE_DIR in two places. One is to add something to CMAKE_MODULE_PATH and the other is to test whether the gtest sources are in its source tree. The latter case you should be able to ignore if you are already adding gtest via your own top level project, or if you prefer to let libtins download/own gtest, you would need to make your libtins download step use git and do a clone/checkout which pulled in the git submodules (I’ll leave that as an exercise for you and your favourite search engine).

      For the case where libtins is adding a path to CMAKE_MODULE_PATH, you can do this yourself in your top level project’s CMakeLists.txt (i.e. add the path the libtins is trying to but will get wrong). You would need to do something like this (assuming you’ve followed a similar pattern for libtins as what this article does for gtest):


      list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}/libtins-src/cmake/Modules")
      add_subdirectory(${CMAKE_BINARY_DIR}/libtins-src
      ${CMAKE_BINARY_DIR}/libtins-build
      EXCLUDE_FROM_ALL )

      Like

  4. I am trying to use this, but getting confused in how to add other things to the CMakeLists.txt file. Or should I make another CMakeLists.txt and put this one inside a src/gtest folder? Thoughts? Guidance? I am using your generic implementation linked in the article.

    Like

    • In the generic implementation’s zip file, the CMakeLists.txt included there is just an example. It is meant to show how a top level CMakeLists.txt might be structured to implement the technique discussed in the article. The call to add_dl_project() can be modified to download whatever it is you want to download rather than gtest. Alternatively, if you want to keep gtest and add another project to download as well, then simply add another call to add_dl_project() later in the CMakeLists.txt file. In most cases, just a call to add_dl_project() with the appropriate URL and URL_HASH specified should be enough. Just make sure each call to add_dl_project() uses a different value for the PROJ argument.

      If you need to use GIT, SVN or something else instead of URL and URL_HASH, you would need to modify the add_dl_project.cmake and add_dl_project.CMakeLists.cmake.in files to handle that. I left them supporting just URL and URL_HASH to keep it simple.

      If that doesn’t get you going in the right direction, perhaps provide more information on where you are getting stuck.

      Like

      • copy that. Mostly new usage issues. Lots that you can do, and this one is very useful. I am trying to setup my projects CMakeLists.txt and wanting to make sure of googleTest and GoogleMock (they moved them to github, btw). I thought it would be cool for someone (on my team) to be able to use this to get google test the right way (ie built for/in project and not installed). The other challenge is, what if I want this on a build server like Jenkins, is it sufficient enough to get all the libraries and such for building during CI. I am working this out right now and will post my progress.

        Like

      • Ah…it is the out of source problem. I am looking in to how to do that. I added a folder
        RootProject
        ->test
        –>”This is where I put the googletest code”
        ->library1
        ->library2

        RootProjectBuild

        I generate an eclipse project after that has two projects…one for the source folder that is connected to my repo, and another that is the build folder.

        Thoughts on best way to approach this?

        Like

      • So, I guess out of source isn’t the issue I should focus on..it is the top-level CMakeLists.txt
        Once I add a folder to the rootFolder, and put a CMakeLists.txt at the top level, and maybe add_subdirectory(test) , i get an error that that googletest-src isn’t an existing directory.
        Do I need to modify the source shown in DownloadProject.cmake?

        “`
        add_subdirectory given source
        “/home/wegunterjr/Documents/projects/gTestCrascit_DownloadOnlyBuild/DownloadProject/googletest-src”
        which is not an existing directory.

        CMake Error at test/CMakeLists.txt:27 (target_include_directories):
        Cannot specify include directories for target “gtest” which is not built by
        this project.

        CMake Error at test/CMakeLists.txt:28 (target_include_directories):
        Cannot specify include directories for target “gmock_main” which is not
        built by this project.

        “`

        Like

      • aha…I found where in the Download.cmake where it was naming just the top level of the project, and made a change to look in the test folder.

        Like

  5. It’s a very clever solution and an excellent post. Thanks.

    Notice that it won’t work if you are cross-compiling, i.e. using -DCMAKE_TOOLCHAIN_FILE=. It is unlikely that you would want to cross-compile your unit testing projects, though you may use gtest for other kind of tests. Anyways, it wouldn’t work to link third party libraries into your cross-compiled binaries.

    Like

    • Sorry for the delay in replying. I don’t see why it wouldn’t work when cross compiling too. The download step doesn’t care what compiler you are using (the DownloadProject general implementation explicitly disables all languages for the ExternalProject build) and when you bring in the externally downloaded source into your main project with add_subdirectory(), it will use the same compiler settings as your main build. If there’s some other scenario you were thinking of though, by all means please clarify.

      Like

  6. Pingback: Enhanced source file handling with target_sources() | Crascit

  7. Pingback: Jenkins and Unit Testing with Google Test on raspberry pi | Open Source Solutions

  8. I really like the solution and applied it successfully. However, it appears to have one drawback. It automatically adds all the files from google test and mock to the default ‘install’ target, i.e. when I run ‘make install’ (or ‘ninja install’), it installs a lot of headers from gtest/gmock and event the static libraries.

    Do you see any way how to prevent this?

    Like

    • Indeed, that’s an unfortunate downside. The usual way to control what gets installed is by specifying components in your install() commands. If you’re using cpack, then all you need to do is request just those components you want included and that should allow you to exclude any gtest or gmock content (see the CPACK_COMPONENTS_ALL variable of the CPackComponent module). If you’re just doing a straight make install, then you may want to consider the approach discussed in this stackoverflow answer where defining some custom install targets looks close to what you need.

      Another alternative which might work (untested) is you could try using the EXCLUDE_FROM_ALL flag in your call to add_subdirectory() when pulling in the googletest code. This might remove that subdir’s contents from the install, I’m not sure. I haven’t been able to find anything in the CMake docs which would confirm it either way. Note, however, that EXCLUDE_FROM_ALL has problems with the Xcode generator, so if you need to support that then this may not be a suitable approach for you. This issue may be worth a read.

      Like

      • Thanks for the quick reply. Using EXCLUDE_FROM_ALL for the subdirectory did the trick. I didn’t know that it can also be applied to a call to add_subdirectory instead of individual targets.

        Like

  9. This is a very nice solution. But I am having some problems (am sure simple ones, but can’t figure out myself). Any thoughts? I think the second error is because of the first one.

    I see that gtest-download directory contains CMakeLists.txt copied over from the template. I put the https link in double quotes, but that does not help or hurt.

    CMake Error at C:/Program Files/CMake/share/cmake-3.6/Modules/ExternalProject.cmake:1745 (message):
    error: could not find git for clone of googletest
    Call Stack (most recent call first):
    C:/Program Files/CMake/share/cmake-3.6/Modules/ExternalProject.cmake:2473 (_ep_add_download_command)
    CMakeLists.txt:6 (ExternalProject_Add)
    — Configuring incomplete, errors occurred!
    See also “C:/MyProject/build/gtest-download/CMakeFiles/CMakeOutput.log”.

    Microsoft (R) Build Engine version 14.0.24720.0
    Copyright (C) Microsoft Corporation. All rights reserved.
    MSBUILD : error MSB1009: Project file does not exist.
    Switch: ALL_BUILD.vcxproj

    CMake Error at CMakeLists.txt:36 (add_subdirectory):
    The source directory
    C:/MyProject/build/gtest-src
    does not contain a CMakeLists.txt file.

    Any idea, why the download is not happening? Thanks.

    Like

    • The first error message is coming from inside the ExternalProject_Add() function at the point where it is checking whether it could find git. Inside ExternalProject_Add()‘s implementation, there is a call to find_package(Git) which should find Git, but in your case it would seem that no git command/package could be found. Check if you actually have a git client installed and whether it is on the PATH. Everything after that error doesn’t really have meaning, since if it can’t find git, it can’t download the package and naturally everything after that would fail.

      Like

      • Thanks Scott. I did figure out that the PATH was not set correctly. Wish I had looked into it before posting … But thanks though for the response and a simple, clean solution to adding external projects.

        Like

        • Quick question: after the initial set up of Gtest in Release/Debug configuration, I am wondering if I can execute some of the commands only on demand – saving a few seconds. I am thinking of introducing a project wide CMake variable, say, set (DONWLOAD_GTEST FALSE), and then only if it is true execute the 3 commands: External Project Add, CMake generator and CMake build for google test. It seems to work without interfering in the build of my own project and use of Gtest. Do you have a better idea? Thanks again for your time.

          Like

          • It is pretty common to use a cache variable to enable/disable part of a build. I’d recommend you have a look at using the option() command rather than a raw set() command. Then you just need to ensure your project honours that setting everywhere it needs to (i.e. only download/build gtest and only build and add the tests relying on gtest if the option is on). It sounds like this is more or less what you are doing.

            Like

  10. Hi Craig,

    Love this solution, might even use it as an alternative to git submodules. I was wondering, however, why you chose to put the sources inside the binary directory?

    Cheers!

    Like

    • The sources are downloaded at build time, making them a build artefact. The source tree should not be modified by anything created as part of the build. A developer may have one source tree but multiple build trees, e.g. for different build types like Debug, Release, etc. or perhaps with different sets of options which could theoretically result in different source dependencies being downloaded. The builds need to be fully independent of each other and you can’t do that if they put things in the source tree.

      In general, an out of source build should not modify the source tree even without this download functionality. A developer should be able to simply delete their build directory to completely remove the build and anything created by it.

      Like

  11. Craig,

    Thanks for the informative article! I’m looking for more stability in my libs, so the zip/tarball-based option is better for me. However, it seems as though any code I try (yours or otherwise) downloads empty files, and subsequently tries to unarchive them, causing a segfault. I’ve tried cleaning out all the cmake files, creating new projects, running the downloads on different networks, and manually placing the files myself, but nothing seems to work. For reference, I’m using the EAP version of CLion, if that makes a difference. Any help would be much appreciated!

    Thanks!

    Like

    • I can’t really offer much guidance without seeing the code. I suggest you post a question on stackoverflow with as much detail as you can (sample CMakeLists.txt, etc). There are people on there who may be able to help and it is a better medium for investigating questions like this.

      Like

  12. Thanks for the post. This is the only reasonable way to deal with gtest. In general I am trying to stay away from it, but once the project stuck using it, this is the way to make it a bit less painfull.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s