3
votes

I still don't completely understand what is the correct way to configure a CMake project with deep hierarchy of nested folders with the source code.Here,configuring for MSVC 2013

Here is example of such a project:

+RootFolder(no code)
    +NestedFolder1(no code)
        +NestedNestedFolder1(has code .h and .cpp)
        +NestedNestedFolder2(has code and nested folder with code)
             +NestedNestedNestedFolder1(has code .h and .cpp)

That's how I configure it in CMake:

RootFolder CMakeLists:

project(MyDemoCmakeProject)
add_subdirectory(NestedFolder1)

NestedFolder1 CMakeLists:

 add_subdirectory(NestedNestedFolder1)
 add_subdirectory(NestedNestedFolder2)     
 add_definitions(.....)
 include_directories(.....)

NestedNestedFolder1 CMakeLists:

 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })

NestedNestedFolder2 CMakeLists:

 subdirs(NestedNestedNestedFolder1)
 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })

NestedNestedNestedFolder1 CMakeLists:

 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })

With this config when running CMake I am getting :

add_library cannot create target "MyLib" because another target with the same name already exists.The existing target is a static library created in source directory NestedNestedFolder1

Now,if I remove add_subdirectory(NestedNestedFolder1) add_subdirectory(NestedNestedFolder2)

from NestedFolder1 CMakeLists.txt it works ok.I don't completely understand this moment.For example inside NestedNestedFolder2 I also do add_subdirectory for the nested sub-directory but it doesn't complain.Also I don't understand how CMake is able to 'reveal' the subdirs of NestedFolder1 if I don't add_subdirectory() those.From the different examples I understood that each directory that has a subdirectory with the sources must expose it with subdirs() or add_subdirectory().What do I miss here?

2

2 Answers

8
votes

subdirs is the old way of adding subdirectories and it has been deprecated. The CMake docs say to use add_subdirectory instead. The behaviour of subdirs is a bit different to add_subdirectory because the former also searches for CMakeLists.txt files in the subdirectories and adds them automatically.

There are a few approaches for managing deep hierarchies like the scenario you describe. In my personal order of preference:

APPROACH 1:

This requires CMake 3.1 or later, since it uses the target_sources command. You can define your executable target at the top level and then call target_sources in each subdirectory to add sources to it. You can also use target_include_directories and target_compile_definitions within each subdirectory as necessary instead of calling add_definitions and include_directories at the top level to make these dependencies attached to the target instead of applying to everything (only really matters here if you added more things to your top level CMakeLists.txt file(s) later). For your scenario, it would look something like the following (I've inserted dummy source file names and compile definitions for illustration purposes):

RootFolder CMakeLists:

project(MyDemoCmakeProject)

# add_library() requires a source file argument to be
# present, but it can be an empty string if there are
# no source files, headers, etc. that should be added
# from this directory.
add_library(MyLib STATIC "")
add_subdirectory(NestedFolder1)

NestedFolder1 CMakeLists:

add_subdirectory(NestedNestedFolder1)
add_subdirectory(NestedNestedFolder2)

NestedNestedFolder1 CMakeLists:

target_sources(MyLib PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1a.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1b.cpp"
)
target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

NestedNestedFolder2 CMakeLists:

add_subdirectory(NestedNestedNestedFolder1)
target_sources(MyLib PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2a.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2b.cpp"
)
target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_definitions(MyLib PUBLIC -DSOME_VAL=42)

NestedNestedNestedFolder1 CMakeLists:

target_sources(MyLib PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1a.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1b.cpp"
)
target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

This approach has the advantage that each subdirectory remains self contained. The upper levels don't have to know what the lower levels are doing. Furthermore, information is added to the target at the level where that information is most relevant/known. One disadvantage is that you have to add each source with its full path, but that is a minor inconvenience.

APPROACH 2:

Instead of using target_sources, use a variable to accumulate the source file names and add the contents of that variable at the top level after including all the subdirectories. We use include rather than add_subdirectory as well so that our variable is being manipulated always in the same scope. Since the target isn't defined until after pulling in the subdirectories, you also cannot use target_compile_definitions or target_include_directories, so these too have to be handled through variables.

RootFolder CMakeLists:

project(MyDemoCmakeProject)

set(MyLib_SOURCES)
set(MyLib_INCLUDE_DIRS)
set(MyLib_DEFINITIONS)
include(NestedFolder1/CMakeLists.txt)
add_library(MyLib STATIC ${MyLib_SOURCES})
target_include_directories(MyLib PUBLIC ${MyLib_INCLUDE_DIRS})
target_compile_definitions(MyLib PUBLIC ${MyLib_DEFINITIONS})

NestedFolder1 CMakeLists:

include(NestedNestedFolder1/CMakeLists.txt)
include(NestedNestedFolder2/CMakeLists.txt)

NestedNestedFolder1 CMakeLists:

list(APPEND MyLib_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1a.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1b.cpp"
)
list(APPEND MyLib_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")

NestedNestedFolder2 CMakeLists:

include(NestedNestedNestedFolder1/CMakeLists.txt)
list(APPEND MyLib_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2a.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2b.cpp"
)
list(APPEND MyLib_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")
list(APPEND MyLib_DEFINITIONS -DSOME_VAL=42)

NestedNestedNestedFolder1 CMakeLists:

list(APPEND MyLib_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1a.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1b.cpp"
)
list(APPEND MyLib_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")

The main reason this is inferior to approach 1 is that carrying all the sources, header paths and compile definitions through in variables makes it more fragile. All subdirectories have to play nice and not use the same variable names for anything else (minor problem) and handling paths with embedded spaces requires more care (trickier problem). Another disadvantage is that variable scoping has to be handled more carefully. Any add_subdirectory call would change the scope. An advantage of this approach over approach 1 is that it works for much older versions of CMake.

APPROACH 3:

Another alternative is to define a library at each subdirectory level and make the top level library link against them. These subdirectory-level libraries could be static or shared libraries, or object libraries (this requires a more recent CMake version). This approach has the advantage that it would work even for old versions of CMake. Disadvantages depend on your point of view. It would lead to a much higher number of targets being defined and it may reduce the effectiveness of parallel builds. For a small number of subdirectories, I'd suggest this approach might actually be the best, but once you grow beyond a couple of subdirectories, things can quickly start getting out of hand.

0
votes

The problem is that you have multiple targets with the same name: "MyLib". Just give each library a unique name and it should work.