Cmake Fetchcontent not finding dependency - cmake

Im trying to use two external git projects in my project, where one depends on the other. However I cannot make it work.
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.11.0
)
FetchContent_MakeAvailable(spdlog)
FetchContent_Declare(
spdlog_setup
GIT_REPOSITORY https://github.com/guangie88/spdlog_setup.git
GIT_TAG v0.3.2
)
FetchContent_MakeAvailable(spdlog_setup)
The second FetchContent fails because it cannot find the spdlog lib.
If I look at the CMakeLists of that depenency I see:
if (EXISTS ${CMAKE_SOURCE_DIR}/deps/spdlog/CMakeLists.txt)
add_subdirectory(deps/spdlog)
else()
# allow usage of installed dependency
find_package(spdlog ${SPDLOG_MIN_VERSION} REQUIRED)
add_library(${PROJECT_NAME}_spdlog INTERFACE IMPORTED)
endif()
I fails in the else statement. How can I fix this?

I found that OVERRIDE_FIND_PACKAGE does the job. If specifying this for spdlog, cmake uses this target in the cmake file of the spdlog_setup project.

Related

could not find GTest but googletest was fetched

I'm using googletest in my project. To add it I used the following code to my CMake
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()
The problem is whenever I build my project I get a following error
Could NOT find GTest (missing: GTEST_LIBRARY GTEST_MAIN_LIBRARY)
This is caused by this line of code in one of the tests
find_package(GTest REQUIRED)
I'm not the owner of the test so I can't change this line. I also can't install googletest on the machine where we run our tests. The only option is to use it as a dependency. I'm not really sure how to fix this problem.
Based on the reading of this page, I tried changing module path
set(CMAKE_MODULE_PATH ${googletest_BINARY_DIR}/googletest/generated)
But it didn't work.
Has anyone encountered a problem like that? How did you solve it?

Disable install for FetchContent

Let's say I have the following code:
include(FetchContent)
FetchContent_Declare(cmark
GIT_REPOSITORY https://github.com/commonmark/cmark.git
GIT_TAG 0.29.0
)
FetchContent_MakeAvailable(cmark)
target_link_libraries(hello_world cmark::cmark_static)
install(TARGETS hello_world DESTINATION bin)
That works correctly, but whenever I run make install, it also installs all the cmark files (like include/cmark_version.h, lib/pkgconfig/libcmark.pc, etc).
Is there any way to disable installing files from packages with FetchContent?
The macro FetchContent_MakeAvailable includes subproject with use of add_subdirectory command. And this command has as special option - EXCLUDE_FROM_ALL - for disable inner install calls.
So, you may replace call FetchContent_MakeAvailable with:
FetchContent_GetProperties(cmark)
if(NOT cmark_POPULATED)
FetchContent_Populate(cmark)
add_subdirectory(${cmark_SOURCE_DIR} ${cmark_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
(This is actually an exact alternative to FetchContent_GetProperties call noted in the FetchContent documentation but with additional EXCLUDE_FROM_ALL parameter.)

Transitive dependencies through Cmake External Projects

Is there a way to make an external dependency depend of two others external dependencies of a project ?
Here is my situation
core depdends on boost_program_options and on quetzal
quetzal depends on gdal and boost_filesystem and boost_unit_test_framework
How to orchestrate these dependencies ? I have been trying the following approach:
The root CMakelist.txt contains:
list(APPEND BOOST_COMPONENTS_REQUIRED program_options)
add_subdirectory(external/upstream)
ExternalProject_Add(
${PROJECT_NAME}_core
DEPENDS
boost_external
gdal_external
quetzal_external
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/FTD_core
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
CMAKE_CACHE_ARGS
-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
-DCMAKE_LIBRARY_PATH:PATH=${STAGED_INSTALL_PREFIX}
# Forward externals to the core project
-DCMAKE_INCLUDE_PATH:PATH=${BOOST_INCLUDEDIR}
-DCMAKE_LIBRARY_PATH:PATH=${BOOST_LIBRARYDIR}
-DCMAKE_INCLUDE_PATH:PATH=${GDAL_INCLUDE_DIR}
-DCMAKE_LIBRARY_PATH:PATH=${GDAL_LIBRARY}
-DCMAKE_INCLUDE_PATH:PATH=${QUETZAL_INCLUDE_DIR}
BUILD_ALWAYS
1
INSTALL_COMMAND
""
)
The external/upstream/quetzal/CMakeLists.txt contains:
list(APPEND BOOST_COMPONENTS_REQUIRED unit_test_framework filesystem)
include(ExternalProject)
ExternalProject_Add(quetzal_external
DEPENDS
boost_external
gdal_external
GIT_REPOSITORY https://github.com/Becheler/quetzal.git
GIT_TAG master
CMAKE_ARGS
-DCMAKE_LIBRARY_PATH:PATH=${STAGED_INSTALL_PREFIX}
-DCMAKE_INCLUDE_PATH:PATH=${BOOST_INCLUDEDIR}
-DCMAKE_LIBRARY_PATH:PATH=${BOOST_LIBRARYDIR}
-DCMAKE_INCLUDE_PATH:PATH=${GDAL_INCLUDE_DIR}
-DCMAKE_LIBRARY_PATH:PATH=${GDAL_LIBRARY}
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
-DCMAKE_PREFIX_PATH=${CMAKE_CURRENT_BINARY_DIR}
-DBUILD_TESTS=Off
UPDATE_COMMAND ""
)
external/upstream/boost/CMakeLists.txt and external/upstream/gdal/CMakeLists.txt seem to behave adequately. But for some reason external/upstream/quetzal/CMakeLists.txt finds the system boost installation (1.71), when I want it to find the locally built boost installation (1.65).
I think something is wrong with my way to forward the boost dependency to the quetzal dependency, but I don't even know if the general approach is correct or not.

How to find static version of zlib in CMake?

I'm on cmake version 3.12.1 and want to build a static executable that uses ZLIB. I have both the static (libz.a) and shared (libz.so) libraries on my machine. How can I tell find_package(ZLIB) to return the static version? Maybe there's another way to find libz.a as well?
My present workaround is to specify:
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
Then:
target_link_libraries (my_binary z lib1 lib2)
Critique on this approach is also welcome!
As of CMake 3.24, use: set(ZLIB_USE_STATIC_LIBS "ON")
Source
Your approach is valid given the limitations of the CMake module called by find_package(ZLIB), specifically FindZLIB.cmake. While other FindXXX.cmake modules have a special option for grabbing static libraries, the zlib module does not.
There are already a few questions on SO about this topic, but some are older than others, so there are a few options.
You can instead apply the -static flag on a more granular level (rather than editing the global CMAKE_EXE_LINKER_FLAGS variable) by adding it to your target_link_libraries call. This way it will apply only to that target -- useful if you are building other non-static targets.
You could also tell CMake to search for static libraries explicitly by setting CMAKE_FIND_LIBRARY_SUFFIXES. When find_package is called, CMake can search for libraries ending in .a using this:
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
find_package(ZLIB REQUIRED)
If you have control over installing zlib, for example, you are installing dependencies in a Continuous Integration setup, I would recommend to just remove the zlib dynamic library.
zlib doesn't have the option to build statically or dynamically, it automatically generates both versions. However FindZlib.cmake prioritizes the dynamic version.
I find the following approach to be better in case you don't have access to modify third parties repositories CMakeLists.txt that needs zlib:
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(_compiler_is_msvc ON)
endif()
option(ZLIB_FORCE_STATIC "Remove the dynamic libraries after zlib install" ON)
mark_as_advanced(ZLIB_FORCE_STATIC)
set(OUTPUT_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR} CACHE PATH "Base folder where builds and source folder will be installed: i.e. OUTPUT_BUILD_DIR/zlib")
if(_compiler_is_msvc)
set(ZLIB_GIT_TAG cacf7f1d4e3d44d871b605da3b647f07d718623f) # Version 1.2.11
message(STATUS "ZLIB_VERSION: ${ZLIB_GIT_TAG} : Version 1.2.11")
set(ZLIB_BUILD_DIR ${OUTPUT_BUILD_DIR}/zlib-build)
set(ZLIB_INSTALL_DIR ${OUTPUT_BUILD_DIR}/zlib)
set(ZLIB_SRC_FOLDER_NAME zlib-src)
set(ZLIB_SRC_DIR ${OUTPUT_BUILD_DIR}/${ZLIB_SRC_FOLDER_NAME})
set(ZLIB_GIT_REPOSITORY "https://github.com/madler/zlib")
ExternalProject_Add(ep_zlib
GIT_REPOSITORY ${ZLIB_GIT_REPOSITORY}
GIT_TAG ${ZLIB_GIT_TAG}
# GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
CMAKE_GENERATOR ${CMAKE_GENERATOR}
SOURCE_DIR ${ZLIB_SRC_DIR}
BINARY_DIR ${ZLIB_BUILD_DIR}
CMAKE_ARGS
-DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
-DCMAKE_BUILD_TYPE:STRING=${SGEXT_CMAKE_BUILD_TYPE}
-DBUILD_SHARED_LIBS:BOOL=OFF
-DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR}
)
if(ZLIB_FORCE_STATIC)
ExternalProject_Add_Step(
ep_zlib zlib_remove_dll
COMMENT "Remove zlib.lib and zlib.dll, leaves only zlibstatic.lib"
DEPENDEES install
COMMAND ${CMAKE_COMMAND} -E remove -f ${ZLIB_INSTALL_DIR}/lib/zlib.lib ${ZLIB_INSTALL_DIR}/bin/zlib.dll
)
endif()
endif()
The last step removes the dynamic version, so the default FindZLIB will find the static library.
The best solution I found was to name the library explicitly when calling CMake:
cmake -DZLIB_LIBRARY=/usr/lib/x86_64-linux-gnu/libz.a /path/to/source
I would not recommend the solution proposed by #phcerdan because in my case the installed shared library was colliding with an already installed version, so the only solution was to make sure it never gets installed in the first place. The key idea is to disable completely the targets installation using SKIP_INSTALL_LIBRARIES, and instead to "install" the static library manually. Nonetheless, my solution is quite similar:
EXTERNALPROJECT_ADD(zlib_external
GIT_REPOSITORY https://github.com/madler/zlib.git
GIT_TAG v1.2.11
CMAKE_ARGS
-DSKIP_INSTALL_FILES=ON # Disable install of manual and pkgconfig files
-DSKIP_INSTALL_LIBRARIES=ON # Do not install libraries automatically. It will be handled manually to avoid installing shared libs
-DBUILD_SHARED_LIBS=OFF
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX}
-DCMAKE_C_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL}
${EXTERNALPROJECT_BUILD_TYPE_CMD}
INSTALL_DIR ${CMAKE_INSTALL_PREFIX}
)
if(NOT WIN32)
set(zlib_BUILD_LIB_PATH "<BINARY_DIR>/libz.a")
set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/libz.a")
else()
set(zlib_BUILD_LIB_PATH "<BINARY_DIR>/Release/zlibstatic.lib")
set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/zlibstatic.lib")
endif()
ExternalProject_Add_Step(
zlib_external zlib_install_static_only
COMMENT "Manually installing only static library"
DEPENDEES install
COMMAND ${CMAKE_COMMAND} -E copy ${zlib_BUILD_LIB_PATH} ${zlib_PATH}
)

CMake and Ninja - "missing and no known rule to make it"

I have this CMakeLists.txt file:
cmake_minimum_required(VERSION 3.8)
include(${CMAKE_CURRENT_SOURCE_DIR}/src/Something.cmake)
add_executable(execute main.cpp)
add_dependencies(somethingInterface Something)
add_dependencies(execute somethingInterface)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_compile_options(execute
PRIVATE
-std=c++11
-g
)
add_library(library SHARED IMPORTED)
set_target_properties(library PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/library.so)
target_link_libraries(execute
PRIVATE
library
)
The library shared imported will be created in file Something.cmake, but tt must be built first.
It was a add_custom_command(TARGET POST_BUILD...) in file Something.cmake.
I don't have any problem in using CMake builds here, but when I am using Ninja there an error.
ninja: error: 'library.so', needed by 'execute', missing and no known rule to make it
Or do you have any suggestion on how can you do this?
I think Ninja has a requirement that "library.so" must exist first, but CMake it is different. It checks whether the library is there at runtime.
There is indeed a divergence between the way Make and Ninja handle imported targets. What works with Make, may sometimes not work with Ninja.
In particular, the following lines of code work with Make, but not with Ninja:
ExternalProject_Add(extProject
GIT_REPOSITORY <GIT_URL>
CMAKE_CACHE_ARGS "-
DCMAKE_INSTALL_PREFIX:STRING=${CMAKE_INSTALL_PREFIX}"
)
add_library(extLib SHARED IMPORTED)
add_dependencies(extLib extProject)
set_target_properties(extLib
PROPERTIES IMPORTED_LOCATION ${CMAKE_INSTALL_PREFIX}/lib/libext.so
)
target_link_libraries(project extLib)
The CMake configure step will work fine, but at build time Ninja will complain:
ninja: error: '/path/to/libext.so', needed by 'project', missing and no known rule to make it
But this will work fine with Make.
You need to specify the library as a byproduct of the ExternalProject_Add comment as mentioned by Tsyvarev, as ExternalProject runs at build time.
The following works fine for me:
ExternalProject_Add(extProject
GIT_REPOSITORY <GIT_URL>
CMAKE_CACHE_ARGS "-
DCMAKE_INSTALL_PREFIX:STRING=${CMAKE_INSTALL_PREFIX}"
BUILD_BYPRODUCTS ${CMAKE_INSTALL_PREFIX}/lib/libext.so
)
add_library(extLib SHARED IMPORTED)
add_dependencies(extLib extProject)
set_target_properties(extLib
PROPERTIES IMPORTED_LOCATION ${CMAKE_INSTALL_PREFIX}/lib/libext.so
)
target_link_libraries(project extLib)