add_subdirectory(subdir) includes another CMakeLists.txt from a subdirectory, creating a new scope for variables. Targets declared in subdirectories are visible in the parent, but variables are not (unless explicitly propagated with PARENT_SCOPE).
Basic usage:
# Top-level CMakeLists.txt project(MyApp) add_subdirectory(src) add_subdirectory(tests) # src/CMakeLists.txt add_library(mylib lib.cpp) # tests/CMakeLists.txt add_executable(test_app test.cpp) target_link_libraries(test_app PRIVATE mylib)
Typical project structure:
CMakeLists.txt # main build configuration src/ CMakeLists.txt # declares mylib target lib.cpp tests/ CMakeLists.txt # declares test_app target test.cpp include/ mylib.h
Variable scope:
# Top-level set(VERSION 1.0) add_subdirectory(src) message("In parent: ${LOCAL_VAR}") # undefined (declared in src/CMakeLists.txt) # src/CMakeLists.txt set(LOCAL_VAR "value") # local: doesn't escape # To propagate a variable up: set(RESULT 42 PARENT_SCOPE)
Targets are always visible to the parent (unlike variables):
# src/CMakeLists.txt add_library(mylib lib.cpp) # tests/CMakeLists.txt target_link_libraries(test_app PRIVATE mylib) # works!
Nested subdirectories:
# CMakeLists.txt add_subdirectory(lib) # lib/CMakeLists.txt add_subdirectory(core) add_subdirectory(utils) # lib/core/CMakeLists.txt add_library(core_lib core.cpp)
Conditional subdirectories:
if(BUILD_TESTS) add_subdirectory(tests) endif()
Build with:
cmake -DBUILD_TESTS=ON ..
Organizing large projects:
Each subdirectory is self-contained: it declares its own targets, sets its own compiler options, manages its own dependencies. The parent orchestrates which subdirectories to build.
Subdirectories prevent variable pollution and make projects modular. Use them to organize code logically and separate concerns (library, tests, examples). Each CMakeLists.txt should be understandable independently.