FetchContent vs ExternalProject - c++

I am building a project with Cmake and use FetchContent to manage dependencies. For several reasons I cannot depend on system-wide installed packages, so this package helps a lot. It allows me to do things like this:
cmake_minimum_required(VERSION 3.14)
project(dummy LANGUAGES C CXX)
include(FetchContent)
FetchContent_Declare(nlohmann
GIT_REPOSITORY https://github.com/onavratil-monetplus/json
GIT_TAG v3.7.3
)
FetchContent_MakeAvailable(nlohmann)
add_executable(dummy main.cpp)
target_link_libraries(dummy PUBLIC nlohmann_json::nlohmann_json)
Now this works nicely as long as the repo is a cmake project with CMakeLists.txt. I would love to use similar approach for non-cmake projects, such as Botan library. Apparently
FetchContent_Declare(botan
GIT_REPOSITORY https://github.com/onavratil-monetplus/botan
GIT_TAG 2.17.2
)
FetchContent_MakeAvailable(botan)
does not really do the job, the build doesnt run since its not a cmake project. One would consider adding
CONFIGURE_COMMAND "<SOURCE_DIR>/configure.py --prefix=<BINARY_DIR>"
BUILD_COMMAND "cd <SOURCE_DIR> && make"
or something similar to the declare command, yet the FetchContent docs explicitly says that these particular arguments are ignored when passed to FetchContent.
Now the struggle is obvious - how to properly use FetchContent in this scenario? I was considering using ExternalProject_Add after the fetchcontent, yet then fetchcontent seems useless (ExternalProject can download git repo as well). Moreover, I would like to use some of the targets of botan at config time (if it makes sense).

I'm facing the same problem. Since the Botan library does not use the CMake build system internally, we cannot use the Botan "targets". But it is possible to build the Botan library at CMake configure time and use library and header files. Here is my solution (minimal configuration, works only for MS Visual Studio):
cmake_minimum_required (VERSION 3.20)
if(WIN32)
if(NOT (${CMAKE_BUILD_TYPE} STREQUAL "Release"))
message(FATAL_ERROR "This configuration only works for a Release build")
endif()
# set paths
set(BOTAN_LIB_ROOT_DIR "${CMAKE_SOURCE_DIR}/external/botan")
set(BOTAN_LIB_REPOS_DIR "${BOTAN_LIB_ROOT_DIR}/repos")
set(BOTAN_LIB_FCSTUFF_DIR "${BOTAN_LIB_ROOT_DIR}/cmake-fetchcontent-stuff")
set(BOTAN_LIB_INSTALL_DIR "${BOTAN_LIB_ROOT_DIR}-install")
# download and unpack Botan library
include(FetchContent)
FetchContent_Declare(
botan
GIT_REPOSITORY https://github.com/randombit/botan.git
GIT_TAG 2.19.1
PREFIX ${BOTAN_LIB_FCSTUFF_DIR}
SOURCE_DIR ${BOTAN_LIB_REPOS_DIR}
)
set(FETCHCONTENT_QUIET OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(botan)
# find Python3 Interpreter and run build, testing and installation
if(${botan_POPULATED} AND MSVC AND NOT EXISTS "${BOTAN_LIB_INSTALL_DIR}/lib/botan.lib")
find_package(Python3 COMPONENTS Interpreter)
if(NOT ${Python3_Interpreter_FOUND})
message(FATAL_ERROR "Python3 Interpreter NOT FOUND")
endif()
execute_process(
COMMAND ${Python3_EXECUTABLE} configure.py --cc=msvc --os=windows --prefix=${BOTAN_LIB_INSTALL_DIR}
WORKING_DIRECTORY ${BOTAN_LIB_REPOS_DIR}
COMMAND_ECHO STDOUT
)
execute_process(
COMMAND nmake
WORKING_DIRECTORY ${BOTAN_LIB_REPOS_DIR}
COMMAND_ECHO STDOUT
)
execute_process(
COMMAND nmake check
WORKING_DIRECTORY ${BOTAN_LIB_REPOS_DIR}
COMMAND_ECHO STDOUT
)
execute_process(
COMMAND nmake install
WORKING_DIRECTORY ${BOTAN_LIB_REPOS_DIR}
COMMAND_ECHO STDOUT
)
endif()
endif()
add_executable (main "main.cpp")
if(WIN32)
target_include_directories(main PUBLIC "${BOTAN_LIB_INSTALL_DIR}/include/botan-2")
target_link_libraries(main PUBLIC "${BOTAN_LIB_INSTALL_DIR}/lib/botan.lib")
configure_file("${BOTAN_LIB_INSTALL_DIR}/bin/botan.dll"
"${CMAKE_CURRENT_BINARY_DIR}/botan.dll"
COPYONLY
)
install(TARGETS main DESTINATION bin)
install(FILES "${BOTAN_LIB_INSTALL_DIR}/bin/botan.dll" DESTINATION bin)
endif()
Here is the full version: https://github.com/weenchvd/cmake-build-botan-lib

Related

Integrate pre-compiled libraries into C++ codebase with CMake ExternalProject

I want to integrate CasADi into a CMake-based C++ codebase as an ExternalProject. For this purpose, I would like to use pre-compiled libraries because building from source is not recommended. So far, I have only managed to write the following:
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)
and I noticed that all the binaries are correctly downloaded in the specified folder. However, I do not know how to link my targets to CasADi, nor how to find the package.
There is a natural problem with ExternalProject_Add:
ExternalProject_Add executes commands only on build.
Hence, download will not happen at the configure stage of your project which makes it difficult to use find_package, because the files cannot be found during your first configure run.
Take this CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(untitled)
set(CMAKE_CXX_STANDARD 17)
add_executable(untitled main.cpp)
include(ExternalProject)
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)
find_package(casadi HINTS ${CMAKE_BINARY_DIR}/external/casadi/src/casadi-3.5.5/casadi)
target_link_libraries(untitled casadi)
In order to use it you have to do the following:
Configure your project
mkdir build
cd build
cmake ..
Build (download) casadi-3.5.5
cmake --build . --target casadi-3.5.5
Reconfigure your project, because now find_package will find the needed files
cmake ..
Build your targets
cmake --build .
If you want a one step build, there are ways to get around this problem
Use FetchContent
Create a sub-cmake-project in a subfolder with all the ExternalProject_Add commands and execute the approriate build (download) steps manually in your own CMakeLists.txt via execute_process calls: https://stackoverflow.com/a/37554269/8088550
Here is an example for the second option, which might be better since FetchContent doesn't have the full functionality of ExternalProject.
main.cpp
#include <casadi/casadi.hpp>
int main()
{
casadi_printf("This works!");
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(untitled)
set(CMAKE_CXX_STANDARD 17)
# some default target
add_executable(untitled main.cpp)
# Configure and build external project
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/external)
execute_process(
COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}/external
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external
)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/external
)
# find and link externals
find_package(casadi REQUIRED HINTS ${CMAKE_BINARY_DIR}/external/external/casadi/src/casadi-3.5.5/casadi)
target_link_libraries(untitled casadi)
external/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(external)
include(ExternalProject)
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)
The point is to have another cmake project under external/CMakeLists.txt, which gets configured and build via execute_process calls from the main cmake project. Do note, that you can now have find_package(casadi REQUIRED ...) at configure stage, because the download will happen just before.

Dealing with nested cmake projects using FetchContent_

I am looking to automate the compilation of a given c++ library (in this case, cpprestsdk). I am looking to build the library using cmake.
Like many other projects, this has dependencies. Namely, it requires OpenSSL, Boost, ZLIB and websocketpp.
I'm looking for a way in which I can provide a CMakeLists file that will fetch and build all of the prerequisites and allow cpprestsdk to build without necessarily having the libraries pre-installed on a computer.
The way I am fetching cpprestsdk is as follows:
FetchContent_Declare(cpprestsdk
GIT_REPOSITORY https://github.com/microsoft/cpprestsdk.git
GIT_TAG master)
FetchContent_GetProperties(cpprestsdk)
if(NOT cpprestsdk_POPULATED)
FetchContent_Populate(cpprestsdk)
add_subdirectory(${cpprestsdk_SOURCE_DIR} ${cpprestsdk_BINARY_DIR})
endif()
Of course, since this does not populate it's own dependencies, it will not be able to build. Although websocketpp has an embedded release, it is only used if not found. For this reason, I will use it as an example.
FetchContent_Declare(websocketpp
GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git
GIT_TAG master
)
FetchContent_GetProperties(websocketpp)
if(NOT websocketpp_POPULATED)
FetchContent_Populate(websocketpp)
add_subdirectory(${cpprestsdk_SOURCE_DIR} ${cpprestsdk_BINARY_DIR})
endif()
This will fetch websocketpp, but as it is only the configure stage, nothing will be built. For this reason, cpprestsdk will not be able to find the dependency and instead fallback to the embedded release.
My question is therefore: Is there any way I can force a build after fetching a package? Or remove the library requirement during the configure stage? I think this may be possible using ExternalProject instead, however I'm not too sure how I would set that up either.
I use a subroutine to fetch the source code, then use execute_process to build and install the target.
After this, even find_package can locate the library.
Fetch subroutine:
cmake_minimum_required(VERSION 3.16)
# suppress CMP0042
cmake_policy(SET CMP0042 NEW)
set(CMAKE_MACOSX_RPATH 1)
# this must exist or cmake will complain about not presenting a project()
project(${PK_NAME}_download NONE)
include(FetchContent)
FetchContent_Declare(
${PK_NAME}
GIT_REPOSITORY "${PK_GIT}"
GIT_TAG "${PK_GIT_TAG}"
PREFIX "${#PK_NAME#_ROOT}"
)
if(NOT ${PK_NAME}_POPULATED)
FetchContent_MakeAvailable(${PK_NAME})
endif()
Download, build and install:
# copy the template to place
configure_file(${CMAKE_CURRENT_LIST_DIR}/FetchLib.cmake ${${PK_NAME}_ROOT}/CMakeLists.txt)
# download source code
execute_process(
COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -DCMAKE_INSTALL_PREFIX:PATH=${${PK_NAME}_ROOT} ${SUB_PARAM}
.
RESULT_VARIABLE result
WORKING_DIRECTORY ${${PK_NAME}_ROOT}
)
if(result)
message(FATAL_ERROR "CMake step for ${PK_NAME} failed: ${result}")
endif()
set(CMAKE_INSTALL_PREFIX "${${PK_NAME}_ROOT}")
# build
execute_process(
COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${${PK_NAME}_ROOT}
)
if(result)
message(FATAL_ERROR "Build step for ${PK_NAME} failed: ${result}")
endif()
# install
execute_process(
COMMAND ${CMAKE_COMMAND} --install .
RESULT_VARIABLE result
WORKING_DIRECTORY ${${PK_NAME}_ROOT}
)
if(result)
message(FATAL_ERROR "Install step for ${PK_NAME} failed: ${result}")
endif()
You are looking for ExternalProject_add
I often use this approach to build externals projects, it provides me automatic control over external projects that are inserted in my projects.
You can include like this:
ExternalProject_add(cpprestsdkDownload
GIT_REPOSITORY https://github.com/microsoft/cpprestsdk
GIT_TAG master
CMAKE_ARGS
-DWERROR:BOOL="0"
-DBUILD_SAMPLES:BOOL="0"
-DBUILD_TESTS:BOOL="0" )
You can use your custom configure,build and install commands to ! Have Fun !

Building mongo-cxx-driver using CMake ExternalProject_Add

I am trying to build mongo-cxx-driver in a CMake based project. This project is supposed to build on Windows, macOS and in an ubuntu container and I want to ensure that my software on all these platforms will use the same driver version so I cannot afford installing the driver and its dependencies via apt-get, brew etc. So I am left with one option: ExternalProject_Add. But I am having difficulty making that work given how libmongoc is setup.
Below is the CMake module I currently have.
include(ExternalProject)
set(libmongoc_CMAKE_ARGS
"-DCMAKE_BUILD_TYPE:STRIING=${CMAKE_BUILD_TYPE}"
"-DENABLE_TESTS:BOOL=OFF"
"-DENABLE_STATIC:BOOL=OFF"
"-DENABLE_EXAMPLES:BOOL=OFF"
"-DENABLE_EXTRA_ALIGNMENT:BOOL=OFF"
)
set(mongocxx_CMAKE_ARGS
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_BUILD_TYPE:STRIING=${CMAKE_BUILD_TYPE}"
"-DBUILD_SHARED_LIBS:BOOL=ON"
"-DENABLE_TESTS:BOOL=OFF"
"-DENABLE_EXAMPLES:BOOL=OFF"
"-DBSONCXX_POLY_USE_BOOST:BOOL=ON"
"-DBSONCXX_POLY_USE_MNMLSTC:BOOL=OFF"
"-Dlibbson-1.0_DIR:PATH=${OTS_DEPDENDENCIES_DIR}/libmongoc/src/libbson"
)
if (NOT TARGET libmongoc)
ExternalProject_Add(
libmongoc
GIT_REPOSITORY "https://github.com/mongodb/mongo-c-driver.git"
GIT_TAG "1.12.0"
SOURCE_DIR "${OTS_DEPDENDENCIES_DIR}/libmongoc"
BINARY_DIR "${OTS_DEPDENDENCIES_DIR}/libmongoc"
CMAKE_ARGS "${libmongoc_CMAKE_ARGS}"
INSTALL_COMMAND ""
)
endif()
if (NOT TARGET mongocxx)
ExternalProject_Add(
mongocxx
GIT_REPOSITORY "https://github.com/mongodb/mongo-cxx-driver.git"
GIT_TAG "r3.3.1"
SOURCE_DIR "${OTS_DEPDENDENCIES_DIR}/mongocxx"
BINARY_DIR "${OTS_DEPDENDENCIES_DIR}/mongocxx"
CMAKE_ARGS "${mongocxx_CMAKE_ARGS}"
INSTALL_COMMAND ""
DEPENDS libmongoc
)
endif()
Note the CMAKE option libbson-1.0_DIR given as one of the CMAKE_ARGS for mongo-cxx-driver. I am skeptic about it and I believe that may be the culprit. With it I get the following error:
CMake Error at ${OTS_DEPENDENCIES_DIR}/libmongoc/src/libbson/libbson-1.0-config.cmake:30 (message):
File or directory
${OTS_DEPENDENCIES_DIR}/include/libbson-1.0
referenced by variable BSON_INCLUDE_DIRS does not exist !
Call Stack (most recent call first):
${OTS_DEPENDENCIES_DIR}/libmongoc/src/libbson/libbson-1.0-config.cmake:46 (set_and_check)
src/bsoncxx/CMakeLists.txt:81 (find_package)
which kind of makes sense because src/bsoncxx/CMakeLists.txt:81 reads:
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
set_and_check (BSON_INCLUDE_DIRS "${PACKAGE_PREFIX_DIR}/include/libbson-1.0")
which makes CMake end up looking for libbson-1.0 in ${OTS_DEPDENDENCIES_DIR}/include that does not exist. If only, I could tell cmake, "hey don't run this find_package" I can give you the path to INCLUDE_DIR, LIBRARIES and DEFINITIONS myself.
If I remove this option, I get the following error:
CMake Error at src/bsoncxx/CMakeLists.txt:81 (find_package):
By not providing "Findlibbson-1.0.cmake" in CMAKE_MODULE_PATH this project
has asked CMake to find a package configuration file provided by
"libbson-1.0", but CMake did not find one.
Could not find a package configuration file provided by "libbson-1.0"
(requested version 1.10.0) with any of the following names:
libbson-1.0Config.cmake
libbson-1.0-config.cmake
Add the installation prefix of "libbson-1.0" to CMAKE_PREFIX_PATH or set
"libbson-1.0_DIR" to a directory containing one of the above files. If
"libbson-1.0" provides a separate development package or SDK, be sure it
has been installed.
which is not very odd either because CMake tries to find_package libbson-1.0 but cannot figure out where it is installed.
preliminary remarks
While looking at the details, here are few preliminary comments:
Use different directory for SOURCE_DIR and BINARY_DIR
Instead of CMAKE_ARG, prefer CMAKE_CACHE_ARGS
libbson-1.0_DIR should NOT be set to a source directory, instead if should be set to a build directory containing a config-file package (link below provide more details about this concept)
Make sure to always specify the type of CMake argument (-DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER} instead of -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER})
Do not set CMAKE_BUILD_TYPE for multi-configuration generator
Regarding this last point, this means that you should do the following:
set(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS)
if(NOT DEFINED CMAKE_CONFIGURATION_TYPES)
list(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS
-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
)
endif()
In this post, you can learn how you could structure your project: Correct way to use third-party libraries in cmake project
working project allowing to compile the mongocxx "test.cpp"
Below is the content of CMakeLists.txt and test.cpp allowing to build an executable named <build-dir>/Test-build/test_mongocxx :
CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
set(CMAKE_CXX_STANDARD 11)
project(Test)
option(${PROJECT_NAME}_SUPERBUILD "Build ${PROJECT_NAME} and the projects it depends on." ON)
if(${PROJECT_NAME}_SUPERBUILD)
include(ExternalProject)
set(common_cmake_cache_args
-DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER}
)
if(NOT DEFINED CMAKE_CONFIGURATION_TYPES)
list(APPEND common_cmake_cache_args
-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
)
endif()
ExternalProject_Add(libmongoc
GIT_REPOSITORY "https://github.com/mongodb/mongo-c-driver.git"
GIT_TAG "1.12.0"
GIT_PROGRESS 1
GIT_SHALLOW 1
SOURCE_DIR "${CMAKE_BINARY_DIR}/libmongoc"
BINARY_DIR "${CMAKE_BINARY_DIR}/libmongoc-build"
INSTALL_DIR "${CMAKE_BINARY_DIR}/libmongoc-install"
CMAKE_CACHE_ARGS
${common_cmake_cache_args}
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}/libmongoc-install
-DENABLE_TESTS:BOOL=OFF
-DENABLE_STATIC:BOOL=OFF
-DENABLE_EXAMPLES:BOOL=OFF
-DENABLE_EXTRA_ALIGNMENT:BOOL=OFF
#INSTALL_COMMAND ""
)
set(libmongoc-1.0_DIR "${CMAKE_BINARY_DIR}/libmongoc-install/lib/cmake/libmongoc-1.0/")
set(libbson-1.0_DIR "${CMAKE_BINARY_DIR}/libmongoc-install/lib/cmake/libbson-1.0/")
ExternalProject_Add(libmongocxx
GIT_REPOSITORY "https://github.com/mongodb/mongo-cxx-driver.git"
GIT_TAG "r3.3.1"
GIT_PROGRESS 1
GIT_SHALLOW 1
SOURCE_DIR "${CMAKE_BINARY_DIR}/libmongocxx"
BINARY_DIR "${CMAKE_BINARY_DIR}/libmongocxx-build"
INSTALL_DIR "${CMAKE_BINARY_DIR}/libmongocxx-install"
CMAKE_CACHE_ARGS
${common_cmake_cache_args}
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}/libmongocxx-install
-DBUILD_SHARED_LIBS:BOOL=ON
-DENABLE_TESTS:BOOL=OFF
-DENABLE_EXAMPLES:BOOL=OFF
-DBSONCXX_POLY_USE_BOOST:BOOL=OFF
-DBSONCXX_POLY_USE_MNMLSTC:BOOL=ON
-DBSONCXX_POLY_USE_STD:BOOL=OFF
-Dlibmongoc-1.0_DIR:PATH=${libmongoc-1.0_DIR}
-Dlibbson-1.0_DIR:PATH=${libbson-1.0_DIR}
DEPENDS
libmongoc
)
set(libmongocxx_DIR "${CMAKE_BINARY_DIR}/libmongocxx-install/lib/cmake/libmongocxx-3.3.1/")
set(libbsoncxx_DIR "${CMAKE_BINARY_DIR}/libmongocxx-install//lib/cmake/libbsoncxx-3.3.1/")
function(ExternalProject_AlwaysConfigure proj)
# This custom external project step forces the configure and later
# steps to run.
_ep_get_step_stampfile(${proj} "configure" stampfile)
ExternalProject_Add_Step(${proj} forceconfigure
COMMAND ${CMAKE_COMMAND} -E remove ${stampfile}
COMMENT "Forcing configure step for '${proj}'"
DEPENDEES build
ALWAYS 1
)
endfunction()
ExternalProject_Add(${PROJECT_NAME}
SOURCE_DIR "${CMAKE_SOURCE_DIR}"
BINARY_DIR "${CMAKE_BINARY_DIR}/${PROJECT_NAME}-build"
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
CMAKE_CACHE_ARGS
${common_cmake_cache_args}
-D${PROJECT_NAME}_SUPERBUILD:BOOL=OFF
-Dlibbsoncxx_DIR:PATH=${libbsoncxx_DIR}
-Dlibmongocxx_DIR:PATH=${libmongocxx_DIR}
INSTALL_COMMAND ""
DEPENDS
libmongocxx
)
ExternalProject_AlwaysConfigure(${PROJECT_NAME})
return()
endif()
message(STATUS "Configuring inner-build")
find_package(libmongocxx REQUIRED)
add_executable(test_mongocxx test.cpp)
target_link_libraries(test_mongocxx PUBLIC ${LIBMONGOCXX_LIBRARIES})
target_include_directories(test_mongocxx PUBLIC ${LIBMONGOCXX_INCLUDE_DIRS})
target_compile_definitions(test_mongocxx PUBLIC ${LIBMONGOCXX_DEFINITIONS})
test.cpp (copied from https://mongodb.github.io/mongo-cxx-driver/mongocxx-v3/installation/#step-6-test-your-installation):
#include <iostream>
#include <bsoncxx/builder/stream/document.hpp>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
int main(int, char**) {
mongocxx::instance inst{};
mongocxx::client conn{mongocxx::uri{}};
bsoncxx::builder::stream::document document{};
auto collection = conn["testdb"]["testcollection"];
document << "hello" << "world";
collection.insert_one(document.view());
auto cursor = collection.find({});
for (auto&& doc : cursor) {
std::cout << bsoncxx::to_json(doc) << std::endl;
}
}

How to clone and integrate external (from git) cmake project into local one

I faced with a problem when I was trying to use Google Test.
There are lot of manuals on how to use ExternalProject_Add for the adding gtest into the project, however most of these describe a method based on downloading zip archive with gtest and build it.
As we know gtest is github-hosted and cmake-based project. So I'd like to find native cmake way.
If this would be a header-only project, I'd write something like:
cmake_minimum_required(VERSION 2.8.8)
include(ExternalProject)
find_package(Git REQUIRED)
ExternalProject_Add(
gtest
PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/ext
GIT_REPOSITORY https://github.com/google/googletest.git
TIMEOUT 10
UPDATE_COMMAND ${GIT_EXECUTABLE} pull
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD ON
)
ExternalProject_Get_Property(gtest source_dir)
set(GTEST_INCLUDE_DIR ${source_dir}/googletest/include CACHE INTERNAL "Path to include folder for GTest")
set(GTEST_ROOT_DIR ${source_dir}/googletest CACHE INTERNAL "Path to source folder for GTest")
include_directories(${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR} ${GTEST_ROOT_DIR})
message(${GTEST_INCLUDE_DIR})
and add this script from my cmake project like:
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/")
include(AddGTest)
....
add_dependencies(${PROJECT_NAME} gtest)
However this requires a build step.
How should this be implemented?
By adding BUILD_COMMAND into ExternaProject_Add and linking with produced libs?
Or by including gtest's cmakelists into my project, something like this:
add_subdirectory (${CMAKE_SOURCE_DIR}\ext\src\gtest\googletest\CMakeLists.txt)
this is not correct way because on the moment of the project load the folder does not exist.
Any other ways?
What is a correct/prefer way?
I would go with the first approach. You don't need to specify a build command because cmake is used by default. This could look like:
cmake_minimum_required(VERSION 3.0)
project(GTestProject)
include(ExternalProject)
set(EXTERNAL_INSTALL_LOCATION ${CMAKE_BINARY_DIR}/external)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION}
)
include_directories(${EXTERNAL_INSTALL_LOCATION}/include)
link_directories(${EXTERNAL_INSTALL_LOCATION}/lib)
add_executable(FirstTest main.cpp)
add_dependencies(FirstTest googletest)
target_link_libraries(FirstTest gtest gtest_main pthread)
I don't know if this is the correct/preferred way if there even is one. If you wanted to implement your second approach you would have to download the code with execute_process first.

CMake ExternalProject_Add and parallel builds

With the following CMakeLists.txt build script:
include( ExternalProject )
ExternalProject_Add( framework SOURCE_DIR ${framework_SOURCE}
PREFIX framework_build
INSTALL_DIR ${framework_DISTRIBUTION} )
...
add_library( ${PROJECT_NAME} SHARED ${BUILD_MANIFEST} )
add_dependencies( ${PROJECT_NAME} framework )
When I attempt to perform a parallel build (make -j5) it will occasionally fail due to a build artefact from the framework not being present. The order of the build, fixed by add_dependencies, is not being adhered to.
Have I misunderstood the documentation around add_dependencies?
Output from cmake --graphviz=graph.dot
Ok, so an updated version of CMake has warned me that the framework dependency is not present. ExternalProject_Add and add_dependencies can not be used with each other, as ExternalProject_Add has not actually built and therefore registered the framework as a high-level target.
Note:
Anyone encountering this problem in future. I've found another SO post by #matiu that resolved my issue.
Maybe ExternalProject_Add_StepDependencies could solve that and create a dependency between the externalproject_add and the imported target?
This is a minimal working example adding Google test as a dependency.
cmake_minimum_required(VERSION 2.8)
project(ExampleProject)
# Set the build type if it isn't already
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
include(ExternalProject)
set(GOOGLE_TEST GoogleTest)
set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/${GOOGLE_TEST}")
ExternalProject_Add(${GOOGLE_TEST}
GIT_REPOSITORY https://chromium.googlesource.com/external/googletest
PREFIX ${GTEST_PREFIX}
CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
INSTALL_COMMAND ""
)
# Specify include directory
ExternalProject_Get_Property(${GOOGLE_TEST} source_dir)
include_directories(${source_dir}/include)
set(LIBPREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}")
set(LIBSUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTEST_LOCATION "${GTEST_PREFIX}/src/${GOOGLE_TEST}-build")
set(GTEST_LIBRARY "${GTEST_LOCATION}/${LIBPREFIX}gtest${LIBSUFFIX}")
set(EXECUTABLE_NAME ${CMAKE_PROJECT_NAME})
add_executable(${EXECUTABLE_NAME} main.cpp)
add_dependencies(${EXECUTABLE_NAME} ${GOOGLE_TEST})
target_link_libraries(
${EXECUTABLE_NAME}
${GTEST_LIBRARY}
-lpthread
)
enable_testing()
set(TEST_NAME ${EXECUTABLE_NAME})
add_test(${EXECUTABLE_NAME} ${TEST_NAME})
And this is the dependency graph:
In this case without add_dependencies a parallel build will always fail, because of missing the dependency.