Project structure and add_subdirectory

CMake · Practical · 10 min

A clear directory layout keeps CMake files small and lets components be reused or extracted later.

As a project grows, split it: the top-level CMakeLists.txt sets up the project and includes subdirectories, and each component directory defines its own targets.

project/
  CMakeLists.txt          # cmake_minimum_required, project, add_subdirectory(...)
  libs/core/CMakeLists.txt
  apps/cli/CMakeLists.txt
  tests/CMakeLists.txt

add_subdirectory(libs/core) processes that directory's CMakeLists.txt right away, with its own variable scope (a copy of the parent's variables) and its own binary directory under build/. Targets, unlike variables, are global: a target defined in libs/core can be linked from apps/cli as long as it's defined first.

Paths worth knowing:

  • CMAKE_CURRENT_SOURCE_DIR / CMAKE_CURRENT_BINARY_DIR the directory being processed and its build counterpart. Relative paths in target_sources and target_include_directories resolve against the current source directory.
  • PROJECT_SOURCE_DIR the directory of the latest project() call; prefer it to CMAKE_SOURCE_DIR, which is the top of the whole build and wrong when your project is itself included by another.

Guard optional parts with options: option(MYPROJ_BUILD_TESTS "Build tests" ON) and if(MYPROJ_BUILD_TESTS) add_subdirectory(tests) endif(). Prefix options with the project name to avoid clashes when the project is used as a subproject.

Example

# ---- CMakeLists.txt (top level) ----
cmake_minimum_required(VERSION 3.25)
project(todo VERSION 0.3 LANGUAGES CXX)

option(TODO_BUILD_TESTS "Build the todo tests" ${PROJECT_IS_TOP_LEVEL})

add_subdirectory(libs/core)     # defines target todo_core
add_subdirectory(apps/cli)      # links todo_core
if(TODO_BUILD_TESTS)
  enable_testing()
  add_subdirectory(tests)
endif()

# ---- libs/core/CMakeLists.txt ----
# add_library(todo_core STATIC store.cpp item.cpp)
# target_include_directories(todo_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

# ---- apps/cli/CMakeLists.txt ----
# add_executable(todo main.cpp)
# target_link_libraries(todo PRIVATE todo_core)

# $ cmake -S . -B build -DTODO_BUILD_TESTS=OFF

Remember

  • add_subdirectory gives each directory its own variable scope; targets are global
  • Use CMAKE_CURRENT_SOURCE_DIR and PROJECT_SOURCE_DIR, not CMAKE_SOURCE_DIR
  • Prefix options with the project name, e.g. TODO_BUILD_TESTS

Watch out for

  • Setting a variable in a subdirectory and expecting the parent to see it; use PARENT_SCOPE or, better, target properties.

Check yourself

  1. A variable is set inside libs/core/CMakeLists.txt. Is it visible in the top-level file after add_subdirectory(libs/core)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/command/add_subdirectory.html