I am trying to add external dependencies to my new cmake project in the cleanest and modern cmake way possible. I want to download these dependencies during configuration time. My problem is that all the targets from external dependencies are always imported. This spams my configurations in e.g. Clion which i want to be much cleaner by default but as you can see i have several useless targets from gtest, googlebenchmark, eigen and spdlog in the figure below. The project should compile on Windows and Linux.
So my question is how to prevent this?
I watched several modern cmake videos, e.g. https://www.youtube.com/watch?v=eC9-iRN2b04 and the sources from https://cliutils.gitlab.io/modern-cmake/.
My project folder structure looks as follows
.
├── CMakeLists.txt
├── Ikarus
| └── CMakeLists.txt
| └── addexternalLibs.cmake
│ └── src
│ └── Ikarus
│ └── [...]
├── problems
| └── CMakeLists.txt
| └── [.cpp files]
├── tests
| └── CMakeLists.txt
| └── [.cpp files]
├── benchmarks
| └── CMakeLists.txt
| └── [.cpp files]
I have a top level CMakeLists.txt
as follows
cmake_minimum_required(VERSION 3.16)
project(Ikarus)
add_subdirectory(Ikarus)
add_subdirectory(problems)
add_subdirectory(tests)
add_subdirectory(benchmarks)
In the folder Ikarus
i want to build my Ikarus_lib
which should be used by the targets defined in problems
, tests
and benchmarks
.
The file Ikarus/CMakeLists.txt
looks as follows
file(GLOB_RECURSE IKARUS_CPP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS *.cpp )
add_library(${PROJECT_NAME}_lib STATIC)
target_sources(${PROJECT_NAME}_lib PRIVATE ${IKARUS_CPP_SOURCES})
include(addexternalLibs.cmake)
target_include_directories(${PROJECT_NAME}_lib PUBLIC src)
target_link_libraries(${PROJECT_NAME}_lib PUBLIC Eigen3::Eigen
PUBLIC spdlog::spdlog)
Now i want to talk about the contents of the file Ikarus/addexternalLibs.cmake
where the external depencies are imported. The difficulties similar arise in benchmark and tests where i import googletest and googlebenchmark but i think i will restrict myself to the Ikarus_lib case since the difficulties are the same.
Since i want to download my dependencies during configuration time i found the nice solution using https://cmake.org/cmake/help/latest/module/FetchContent.html
The result is Ikarus/addexternalLibs.cmake
:
include(FetchContent)
message("Configure Eigen: ")
FetchContent_Declare(
eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
option(EIGEN_BUILD_DOC OFF)
option(BUILD_TESTING OFF)
option(EIGEN_LEAVE_TEST_IN_ALL_TARGET OFF)
option(EIGEN_BUILD_PKGCONFIG OFF)
FetchContent_MakeAvailable(eigen)
message("====================================")
message("Configure spdlog: ")
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.8.5
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
option(SPDLOG_BUILD_SHARED OFF)
option(SPDLOG_BUILD_ALL OFF)
FetchContent_MakeAvailable(spdlog)
This solution works and i managed to deactivate some targets using the options EIGEN_BUILD_DOC OFF...
. But still i have lots of different useless targets as seen in the image above which cannot be deactivated using options of the package.
I tried several other things, e.g. using EXCLUDE_FROM_ALL
https://cmake.org/cmake/help/latest/prop_tgt/EXCLUDE_FROM_ALL.html as follows
FetchContent_Declare(
eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
option(EIGEN_BUILD_DOC OFF)
option(BUILD_TESTING OFF)
option(EIGEN_LEAVE_TEST_IN_ALL_TARGET OFF)
option(EIGEN_BUILD_PKGCONFIG OFF)
FetchContent_GetProperties(eigen)
if(NOT eigen_POPULATED)
FetchContent_Populate(Eigen)
add_subdirectory(${eigen_SOURCE_DIR} ${eigen_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
which also works but does also adds all that targets which i don't want. Therefore, EXCLUDE_FROM_ALL
does not work the way i think it works.
Another way of using EXCLUDE_FROM_ALL
could be
include(FetchContent)
message("Configure Eigen: ")
FetchContent_Declare(
eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
option(EIGEN_BUILD_DOC OFF)
option(BUILD_TESTING OFF)
option(EIGEN_LEAVE_TEST_IN_ALL_TARGET OFF)
option(EIGEN_BUILD_PKGCONFIG OFF)
FetchContent_MakeAvailable(eigen)
set_target_properties(eigen PROPERTIES EXCLUDE_FROM_ALL TRUE)
which also does not change the behaviour.
I also tried CPMAddPackage
from https://github.com/cpm-cmake/CPM.cmake
CPMAddPackage(
GITLAB_REPOSITORY libeigen/eigen
GIT_TAG 3.4
OPTIONS "EIGEN_BUILD_DOC NO"
"BUILD_TESTING NO"
"EIGEN_LEAVE_TEST_IN_ALL_TARGET YES"
"EIGEN_BUILD_PKGCONFIG NO"
)
which also works but also adds all that targets.
So now i don't know what else to try and i think i will just keep the targets and delete them from my Clion configuration interface and hope they don't reappear. But still i want to understand what am i missing. I think this is a common problem and the solution should be very easy?
Furthermore, i know that i can just download e.g. eigen and use include_directories(...)
to automatically add the headers without the targets. But on one hand this is not modern cmake as advertised in https://www.youtube.com/watch?v=eC9-iRN2b04 where using include_directories
is called an anti-pattern and one should "think in modules". On the other hand this does not help for packages which are not header-only.
So how can this problem be solved elegantly? Additionally, i would appreciate every other comment to my (bad) usage of Cmake. Thanks!
I used to have similar issues.
Change this directory structure
├── Ikarus
| ├── CMakeLists.txt
| ├── addexternalLibs.cmake
│ └── src
│ └── Ikarus
│ └── [...]
by adding an extern
folder
├── Ikarus
| ├── CMakeLists.txt
| ├── addexternalLibs.cmake
| ├── extern/
│ └── src
│ └── Ikarus
│ └── [...]
You can now right click the extern
folder in CLion and "Mark Directory as" -> "Excluded". I assume your build
folder(s) is(are) already excluded too.
Now in Ikarus/addexternalLibs.cmake
you change the first to lines to
include(FetchContent)
set(FETCHCONTENT_BASE_DIR ${PROJECT_SOURCE_DIR}/extern)
Then within each FetchContent_Declare(packagename...
you add the option BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/extern/packagename
. So for spdlog
, for example:
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.8.5
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/extern/spdlog
)
This works easily with STATIC and INTERFACE libraries. For SHARED libraries in Windows, you might need to change the BINARY_DIR to a project-wide binary directory.
Even with the above, I was still having some issues with Eigen so I did something different: only download the Eigen subfolder using the option SOURCE_SUBDIR Eigen
, i.e.:
FetchContent_Declare(
Eigensource
GIT_REPOSITORY https://gitlab.com/libeigen/eigen
GIT_TAG 3.4.0
GIT_SHALLOW ON
SOURCE_SUBDIR Eigen
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/extern/Eigen
GIT_PROGRESS ON
FIND_PACKAGE_ARGS
)
And then manually create the library interface with CMake:
add_library(Eigen INTERFACE)
add_library(Eigen::Eigen ALIAS Eigen)
target_include_directories(Eigen INTERFACE ${eigensource_SOURCE_DIR})
target_compile_definitions(Eigen INTERFACE EIGEN_FAST_MATH EIGEN_DEFAULT_TO_ROW_MAJOR
$<$<CONFIG:Release>:EIGEN_NO_DEBUG>)