I'm building an external library using CMake with ExternalProject_Add. I've used the answer here to generate the following (which captures command line input to CMake in order to pass it to the ExternalProject_Add command):
get_cmake_property(CACHE_VARS CACHE_VARIABLES)
foreach(CACHE_VAR ${CACHE_VARS})
get_property(CACHE_VAR_HELPSTRING CACHE ${CACHE_VAR} PROPERTY HELPSTRING)
if(CACHE_VAR_HELPSTRING STREQUAL "No help, variable specified on the command line.")
get_property(CACHE_VAR_TYPE CACHE ${CACHE_VAR} PROPERTY TYPE)
if(CACHE_VAR_TYPE STREQUAL "UNINITIALIZED")
set(CACHE_VAR_TYPE)
else()
set(CACHE_VAR_TYPE :${CACHE_VAR_TYPE})
endif()
set(CMAKE_ARGS "${CMAKE_ARGS} -D${CACHE_VAR}${CACHE_VAR_TYPE}=\"${${CACHE_VAR}}\"")
endif()
endforeach()
The ExternalProject_Add command looks like this:
ExternalProject_Add(external_lib
URL ${EXTERNALLIB_SOURCE_DIR}
PREFIX ${EXTERNALLIB_PREFIX}
CMAKE_ARGS "${CMAKE_ARGS};-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>"
INSTALL_DIR ${EXTERNALLIB_INSTALL_DIR}
BINARY_DIR "${EXTERNALLIB_PREFIX}/lib"
)
I cannot figure out how to properly pass the CMAKE_ARGS variable to this command. Obviously, the other variables work fine, but the CMAKE_ARGS one seems not to be expanded properly as I know it contains what I want it to contain. Am I doing something wrong syntacticly with CMake?
set(CMAKE_ARGS "${CMAKE_ARGS} -D${CACHE_VAR}${CACHE_VAR_TYPE}=\"${${CACHE_VAR}}\"")
You want to create one big string from all options. Try simple list instead:
list(APPEND CMAKE_ARGS "-D${CACHE_VAR}${CACHE_VAR_TYPE}=${${CACHE_VAR}}")
Related
Adding Eigen via
FetchContent_Declare(
eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.3.9
)
FetchContent_GetProperties(eigen)
if(NOT eigen_POPULATED)
FetchContent_Populate(eigen)
add_subdirectory(${eigen_SOURCE_DIR} ${eigen_BINARY_DIR})
endif()
find_package (Eigen3 3.3 REQUIRED NO_MODULE)
gives me the error
CMake Error at o/b/x64-Debug/_deps/eigen-build/Eigen3Config.cmake:20 (include):
The file
D:/XXX/o/b/x64-Debug/_deps/eigen-build/Eigen3Targets.cmake
was generated by the export() command. It may not be used as the argument
to the include() command. Use ALIAS targets instead to refer to targets by
alternative names. D:\XXX\o/b/x64-Debug/_deps/eigen-build/Eigen3Config.cmake 20
But downloading Eigen manually and adding it works fine
add_subdirectory("${PROJECT_SOURCE_DIR}/extern/eigen")
find_package (Eigen3 3.3 REQUIRED NO_MODULE)
Any ideas ?
In any order:
Eigen only provide ALIAS target on master
ref: https://gitlab.com/libeigen/eigen/-/commit/cf0b5b0344a3bfcf410e95bf22289015a2daf34b#9a2aa4db38d3115ed60da621e012c0efc0172aae_671_599
FetchContent usage could be
include(FetchContent)
FetchContent_Declare(
Eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG master
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE)
set(EIGEN_BUILD_DOC OFF)
# note: To disable eigen tests,
# you should put this code in a add_subdirectory to avoid to change
# BUILD_TESTING for your own project too since variables are directory
# scoped
set(BUILD_TESTING OFF)
set(EIGEN_BUILD_PKGCONFIG OFF)
set( OFF)
FetchContent_MakeAvailable(Eigen)
...
target_link_libraries(YourTarget PRIVATE Eigen3::Eigen)
For find_package() and FetchContent()/add_subdirectory() please see
https://gitlab.kitware.com/cmake/cmake/-/issues/17735
Alright i gave up on FetchContent, i also tried this Local install Eigen in CMAKE not finding target but this didn't work for me either.
I use ExternalProject now (https://github.com/qulacs/qulacs/blob/master/CMakeLists.txt) for fetching and linking eigen
include(ExternalProject)
set(EIGEN_BUILD_DIR ${CMAKE_BINARY_DIR}/eigen)
set(EIGEN_INSTALL_DIR ${CMAKE_SOURCE_DIR}/include/eigen3)
set(EIGEN_INCLUDE_DIR ${EIGEN_INSTALL_DIR})
ExternalProject_Add(
eigen
URL https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.tar.gz
PREFIX ${EIGEN_BUILD_DIR}
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND
${CMAKE_COMMAND} -E copy_directory ${EIGEN_BUILD_DIR}/src/eigen/Eigen ${EIGEN_INCLUDE_DIR}/Eigen
&& ${CMAKE_COMMAND} -E copy_directory ${EIGEN_BUILD_DIR}/src/eigen/unsupported ${EIGEN_INCLUDE_DIR}/unsupported
TEST_COMMAND ""
)
include_directories(SYSTEM ${EIGEN_INCLUDE_DIR})
Additionally add_dependencies has to be used
add_executable(test1 "test1.cpp")
add_dependencies(test1 eigen)
Fetchcontent is great, but it has the unwanted side-effect that your workspace becomes cluttered by all targets of your dependency. If you don't like this then ExternalProject_Add seems to be a reasonable solution. In your case the following should work:
include(ExternalProject)
set(EIGEN_INSTALL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/eigen-install/")
ExternalProject_Add(
eigen
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/eigen-src"
BINARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/eigen-build"
INSTALL_DIR "${EIGEN_INSTALL_DIR}"
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
-DCMAKE_BUILD_TYPE=Release
)
file(MAKE_DIRECTORY ${EIGEN_INSTALL_DIR}/include) # avoid race condition
add_library(eigenlib INTERFACE IMPORTED GLOBAL)
add_dependencies(eigenlib eigen)
target_compile_features(eigenlib INTERFACE cxx_std_14)
set_target_properties(eigenlib PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${EIGEN_INSTALL_DIR}/include/eigen3
)
Note that the dependecy is linked to an interface library eigenlib that you can subsequenly link to your own targets as follows:
add_library(linalg INTERFACE)
target_include_directories(linalg
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
target_link_libraries(linalg INTERFACE eigenlib)
I would like to expand on #Mizux's answer.
In their comment, there is a set( OFF) statement that does nothing. Also, to remove a CMake warning, you might need to do:
include(FetchContent)
FetchContent_Declare(
Eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG master
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(EIGEN_BUILD_DOC OFF)
set(EIGEN_BUILD_PKGCONFIG OFF)
FetchContent_MakeAvailable(Eigen)
This removes the warning for this policy change.
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 !
There is a ready project. In one of the cmake-files there is a source code:
find_package(GTest REQUIRED)
if (NOT GTest_FOUND)
message(FATAL_ERROR "Cannot find Google Test Framework!")
endif()
Gives an error: "Cannot find Google Test Framework!"
How to fix error?
The FindGTest.cmake file uses the environment variable GTEST_ROOT.
You can add this variable to your system or just add it to your CMakeLists.txt file:
set(GTEST_ROOT "c:/path/to/gtest/root" CACHE PATH "path to gtest").
This should solve your problem. It is of course possible to completely add gtest to a project (like Luis Miglietti suggested), but thats maybe not what you want to do as a first try.
This could be useful for you so you don't have to depend on a local google test install, this should work independently if you have google test installed in your machine
You can add this to your cmake file (you should take care of the proper linking / include depending on your project structure)
This will download google test, configure the installation and build it in vendor/gtm/gtest (you could always change this) inside your main build folder. Then you can link gtest to an executable so you can run your tests from there
include(ExternalProject)
find_package(Git REQUIRED)
# Build googletest
ExternalProject_Add(
googletest
PREFIX "vendor/gtm"
GIT_REPOSITORY "https://github.com/google/googletest.git"
GIT_TAG release-1.8.0
TIMEOUT 10
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
UPDATE_COMMAND ""
)
# Build gtest
ExternalProject_Add(
gtest_src
PREFIX "vendor/gtm"
SOURCE_DIR "vendor/gtm/src/googletest/googletest"
INSTALL_DIR "vendor/gtm/gtest"
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/vendor/gtm/gtest
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
)
# Prepare gtest
ExternalProject_Get_Property(gtest_src install_dir)
set(GTEST_INCLUDE_DIR ${install_dir}/include)
set(GTEST_LIBRARY_PATH ${install_dir}/lib/libgtest.a)
file(MAKE_DIRECTORY ${GTEST_INCLUDE_DIR})
add_library(gtest STATIC IMPORTED)
set_property(TARGET gtest PROPERTY IMPORTED_LOCATION ${GTEST_LIBRARY_PATH})
set_property(TARGET gtest APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${GTEST_INCLUDE_DIR})
add_dependencies(gtest_src googletest)
add_dependencies(gtest gtest_src)
Then you can link gtest to an executable with something like
add_executable(tester test/tester.cc)
target_link_libraries(tester gtest)
enable_testing()
add_test(<library> tester)
While CMake provides a FindGTest.cmake module since 2009...
I prefer to incorporate googletest in your CMake project like explaining in the googletest documentation.
https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project
Note: a more detailed explanation http://crascit.com/2015/07/25/cmake-gtest/
I have a project that uses an external library. The project's CMakeLists.txt looks like this:
cmake_minimum_required(VERSION 3.6.0)
project(my-project)
set(CMAKE_CXX_STANDARD 14)
include(ExternalProject)
find_package(Git REQUIRED)
ExternalProject_Add(library
PREFIX ${my-project_SOURCE_DIR}/lib/library
GIT_REPOSITORY https://github.com/vendor/library
GIT_TAG master
UPDATE_COMMAND ""
INSTALL_COMMAND ""
)
link_directories(${my-project_SOURCE_DIR}/lib/library/src/library-build/src)
add_subdirectory(src)
And src/CMakeLists.txt like this:
include_directories(../lib/library/src/library/include)
add_executable(my-project
main.cpp
)
add_dependencies(my-project library)
target_link_libraries(my-project liblibrary.a)
It pulls the library from Git and builds it for the first time without any problems.
I want to edit source code of the library and have the library .a file be recompiled automatically. What is the cleanest way I can achieve that? It currently ignores any updates to the source code, because it already has .a file of the library.
When I try to add
add_subdirectory(lib/library/src/library/src)
to my main CMakeLists.txt, I get the following error:
CMake Error at lib/library/src/library/src/CMakeLists.txt:55 (add_library): add_library cannot create target "library" because another target with the same name already exists. The existing target is a custom target created in source directory "/cygdrive/c/Code/my-project". See documentation for policy CMP0002 for more details.
I guess it's caused by calling
add_library(gram STATIC ${SOURCE_FILES})
in the library CMakeLists.txt and then calling
ExternalProject_Add(library ...)
in the project CMakeLists.txt.
Any ideas?
As your main goal for using ExternalProject_Add is to download the dependency from an external source without configuring and building it, you could define the CMAKE_COMMAND, CONFIGURE_COMMAND and BUILD_COMMAND as empty strings. Same as you already did for the UPDATE_COMMAND and INSTALL_COMMAND. That way, ExternalProject_Add will only clone the repository once.
To overcome the name clash, just use a different one for the first argument of ExternalProject_Add, e.g. library_src:
ExternalProject_Add(library_src
PREFIX ${my-project_SOURCE_DIR}/lib/library
GIT_REPOSITORY https://github.com/vendor/library
GIT_TAG master
UPDATE_COMMAND ""
CONFIGURE_COMMAND ""
CMAKE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(library_src SOURCE_DIR)
add_subdirectory(${SOURCE_DIR})
The second command (ExternalProject_Get_Property) will give you the named properties for the given external project. The output variables are of the same name as the properties. That way, you are immune against changes in the behaviour of ExternalProject_Add where it places the actual source tree.
add this command in ExternalProject_Add maybe help you:
UPDATE_COMMAND ""
https://gitlab.kitware.com/cmake/cmake/issues/16419
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.