I have several projects (all building with CMake from the same source tree structure) all using their own mix out of dozens of supporting libraries.
So I came about the question how to set up this correctly in CMake. So far I have only found CMake how to correctly create dependencies between targets, but I'm still struggling between setting up everything with global dependencies (the project level does know it all) or with local dependencies (each sub-level target only handles its own dependencies).
Here is a reduced example of my directory structure and what I currently came up with using CMake and local dependencies (the example shows only one executable project, App1
, but there are actually more, App2
, App3
, etc.):
Lib
+-- LibA
+-- Inc
+-- a.h
+-- Src
+-- a.cc
+-- CMakeLists.txt
+-- LibB
+-- Inc
+-- b.h
+-- Src
+-- b.cc
+-- CMakeLists.txt
+-- LibC
+-- Inc
+-- c.h
+-- Src
+-- c.cc
+-- CMakeLists.txt
App1
+-- Src
+-- main.cc
+-- CMakeLists.txt
Lib/LibA/CMakeLists.txt
include_directories(Inc ../LibC/Inc)
add_subdirectory(../LibC LibC)
add_library(LibA Src/a.cc Inc/a.h)
target_link_libraries(LibA LibC)
Lib/LibB/CMakeLists.txt
include_directories(Inc)
add_library(LibB Src/b.cc Inc/b.h)
Lib/LibC/CMakeLists.txt
include_directories(Inc ../LibB/Inc)
add_subdirectory(../LibB LibB)
add_library(LibC Src/c.cc Inc/c.h)
target_link_libraries(LibC LibB)
App1/CMakeLists.txt (for the ease of reproducing it I generate the source/header files here)
cmake_minimum_required(VERSION 2.8)
project(App1 CXX)
file(WRITE "Src/main.cc" "#include \"a.h\"\n#include \"b.h\"\nint main()\n{\na();\nb();\nreturn 0;\n}")
file(WRITE "../Lib/LibA/Inc/a.h" "void a();")
file(WRITE "../Lib/LibA/Src/a.cc" "#include \"c.h\"\nvoid a()\n{\nc();\n}")
file(WRITE "../Lib/LibB/Inc/b.h" "void b();")
file(WRITE "../Lib/LibB/Src/b.cc" "void b() {}")
file(WRITE "../Lib/LibC/Inc/c.h" "void c();")
file(WRITE "../Lib/LibC/Src/c.cc" "#include \"b.h\"\nvoid c()\n{\nb();\n}")
include_directories(
../Lib/LibA/Inc
../Lib/LibB/Inc
)
add_subdirectory(../Lib/LibA LibA)
add_subdirectory(../Lib/LibB LibB)
add_executable(App1 Src/main.cc)
target_link_libraries(App1 LibA LibB)
The library dependencies in the above example do look like this:
App1 -> LibA -> LibC -> LibB
App1 -> LibB
At the moment I prefer the local dependencies variant, because it's easier to use. I just give the dependencies at the source level with include_directories()
, at the link level with target_link_libraries()
and at the CMake level with add_subdirectory()
.
With this you don't need to know the dependencies between the supporting libraries and - with the CMake level "includes" - you will only end-up with the targets you really use. Sure enough you could just make all include directories and targets be known globally and let the compiler/linker sort out the rest. But this seems like a kind of bloating to me.
I also tried to have a Lib/CMakeLists.txt
to handle all the dependencies in the Lib
directory tree, but I ended up having a lot of if ("${PROJECT_NAME}" STREQUAL ...)
checks and the problem that I can't create intermediate libraries grouping targets without giving at least one source file.
So the above example is "so far so good", but it throws the following error because you should/can not add a CMakeLists.txt
twice:
CMake Error at Lib/LibB/CMakeLists.txt:2 (add_library):
add_library cannot create target "LibB" because another target with the
same name already exists. The existing target is a static library created
in source directory "Lib/LibB".
See documentation for policy CMP0002 for more details.
At the moment I see two solutions for this, but I think I got this way too complicated.
1. Overwriting add_subdirectory()
to prevent duplicates
function(add_subdirectory _dir)
get_filename_component(_fullpath ${_dir} REALPATH)
if (EXISTS ${_fullpath} AND EXISTS ${_fullpath}/CMakeLists.txt)
get_property(_included_dirs GLOBAL PROPERTY GlobalAddSubdirectoryOnceIncluded)
list(FIND _included_dirs "${_fullpath}" _used_index)
if (${_used_index} EQUAL -1)
set_property(GLOBAL APPEND PROPERTY GlobalAddSubdirectoryOnceIncluded "${_fullpath}")
_add_subdirectory(${_dir} ${ARGN})
endif()
else()
message(WARNING "add_subdirectory: Can't find ${_fullpath}/CMakeLists.txt")
endif()
endfunction(add_subdirectory _dir)
2. Adding an "include guard" to all sub-level CMakeLists.txt
s, like:
if (NOT TARGET LibA)
...
endif()
I've been testing the concepts suggested by tamas.kenez and m.s. with some promising results. The summaries can be found in my following answers: