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.