Add an external source directory to a CMake build

Yaron Tausky picture Yaron Tausky · Aug 12, 2012 · Viewed 10.1k times · Source

I'm using Google Mock for my project, and the instructions say it's better to build the library along with the project, because different compiler flags can introduce bugs. I don't want to add the gmock/ directory to my repository; I'd rather have the sources as an external dependency and plug it into my build process. Which brings me to my question: how can I instruct CMake to pull the external source directory into the build (i.e. build it in my project's build directory)? I found a similar question here, but the answers requires a rigid directory placement, and I'd rather have that configurable. Any other way to do it?

Answer

Fraser picture Fraser · Aug 12, 2012

You could use ExternalProject_Add for this, since it lets you to download, configure and build gmock from within your project's build tree, and then link to the gmock libraries.

@arrowdodger's answer is probably the more usual way to go about this however. Using his method, you don't normally have the gmock sources in your build tree. This could be good or bad depending on what you want.

Using ExternalProject_Add, the gmock sources are pulled (svn update) every time you build gmock or a dependent target. This makes the build slightly slower, but clearly keeps the sources up to date and is convenient (it's one less dependency to have developers install). For a project like gmock which doesn't change often, the overhead of updating all the time may be too much for you though.

The following CMakeLists.txt works using Visual Studio 2010 & 2012 - it might need adjusted for other platforms. In particular, gtest currently fails to build out the box using Visual Studio 2012 (see this bug report), hence the patch file and PATCH_COMMAND in the ExternalProject_Add call.

cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)
project(Test)

# Create main.cpp which uses gmock
file(WRITE src/main.cpp "#include \"gmock/gmock.h\"\n\n")
file(APPEND src/main.cpp "struct A {\n  virtual void Do() {}\n};\n\n")
file(APPEND src/main.cpp "struct MockA : public A {\n  MOCK_METHOD0(Do, void());\n};\n\n")
file(APPEND src/main.cpp "TEST(A, Do) {\n")
file(APPEND src/main.cpp "  MockA mock_a;\n")
file(APPEND src/main.cpp "  EXPECT_CALL(mock_a, Do()).Times(testing::AtLeast(1));\n")
file(APPEND src/main.cpp "  mock_a.Do();\n}\n\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 643)\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")
  file(APPEND gtest.patch "Index: include/gtest/internal/gtest-tuple.h\n")
  file(APPEND gtest.patch "===================================================================\n")
  file(APPEND gtest.patch "--- include/gtest/internal/gtest-tuple.h (revision 643)\n")
  file(APPEND gtest.patch "+++ include/gtest/internal/gtest-tuple.h (working copy)\n")
  file(APPEND gtest.patch "@@ -1,3 +1,4 @@\n")
  file(APPEND gtest.patch "+#include <tuple> /*\n")
  file(APPEND gtest.patch " // This file was GENERATED by command:\n")
  file(APPEND gtest.patch " //     pump.py gtest-tuple.h.pump\n")
  file(APPEND gtest.patch " // DO NOT EDIT BY HAND!!!\n")
  file(APPEND gtest.patch "@@ -197,8 +198,8 @@\n")
  file(APPEND gtest.patch " class tuple<> {\n")
  file(APPEND gtest.patch "  public:\n")
  file(APPEND gtest.patch "   tuple() {}\n")
  file(APPEND gtest.patch "-  tuple(const tuple& /* t */)  {}\n")
  file(APPEND gtest.patch "-  tuple& operator=(const tuple& /* t */) { return *this; }\n")
  file(APPEND gtest.patch "+  tuple(const tuple& t)  {}\n")
  file(APPEND gtest.patch "+  tuple& operator=(const tuple&) { return *this; }\n")
  file(APPEND gtest.patch " };\n")
  file(APPEND gtest.patch " \n")
  file(APPEND gtest.patch " template <GTEST_1_TYPENAMES_(T)>\n")
  file(APPEND gtest.patch "@@ -946,7 +947,7 @@\n")
  file(APPEND gtest.patch " template <>\n")
  file(APPEND gtest.patch " struct SameSizeTuplePrefixComparator<0, 0> {\n")
  file(APPEND gtest.patch "   template <class Tuple1, class Tuple2>\n")
  file(APPEND gtest.patch "-  static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) {\n")
  file(APPEND gtest.patch "+  static bool Eq(const Tuple1&, const Tuple2&) {\n")
  file(APPEND gtest.patch "     return true;\n")
  file(APPEND gtest.patch "   }\n")
  file(APPEND gtest.patch " };\n")
else()
  file(WRITE gtest.patch "")
endif()

# Enable ExternalProject CMake module
include(ExternalProject)

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

# Add gmock
ExternalProject_Add(
    googlemock
    SVN_REPOSITORY http://googlemock.googlecode.com/svn/trunk/
    TIMEOUT 30
    PATCH_COMMAND svn patch ${CMAKE_SOURCE_DIR}/gtest.patch ${CMAKE_BINARY_DIR}/ThirdParty/src/googlemock/gtest
    # 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_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 for googlemock and googletest
ExternalProject_Get_Property(googlemock source_dir)
include_directories(${source_dir}/include)
include_directories(${source_dir}/gtest/include)

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 googlemock
add_dependencies(MainTest googlemock)

# Specify MainTest's link libraries
ExternalProject_Get_Property(googlemock binary_dir)
target_link_libraries(MainTest
                      debug ${binary_dir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gmock${CMAKE_FIND_LIBRARY_SUFFIXES}
                      optimized ${binary_dir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gmock${CMAKE_FIND_LIBRARY_SUFFIXES})

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 gmock sources to MyTest/build/ThirdParty/src/googlemock, and build them in MyTest/build/ThirdParty/src/googlemock-build. You should then be able to run the MainTest target successfully.

For further info on the ExternalProject_Add command, see this article entitled Building External Projects with CMake 2.8

Here's a gist containing this CMakeLists.txt