Docs

CMake

CMake Basics

Overview

CMake is a cross-platform build system generator. It doesn't build your code directly—it generates native build files (Makefiles, Visual Studio projects, etc.) that then compile your code.

Learning Objectives

By the end of this section, you will understand:

  • CMake basics and CMakeLists.txt structure
  • Creating executables and libraries
  • Managing dependencies
  • Modern CMake practices (target-based)
  • Using CMake with external libraries

Why CMake?

┌─────────────────────────────────────────────────────────────────────────┐
│                      CMake WORKFLOW                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│    ┌─────────────────┐                                                  │
│    │ CMakeLists.txt  │                                                  │
│    │ (Platform-      │                                                  │
│    │  Independent)   │                                                  │
│    └────────┬────────┘                                                  │
│             │                                                           │
│             ▼                                                           │
│    ┌─────────────────┐                                                  │
│    │     CMake       │                                                  │
│    │   Generator     │                                                  │
│    └────────┬────────┘                                                  │
│             │                                                           │
│     ┌───────┼───────┐                                                   │
│     ▼       ▼       ▼                                                   │
│  ┌──────┐ ┌──────┐ ┌──────┐                                            │
│  │Make- │ │Visual│ │Ninja │  ... other build systems                   │
│  │file  │ │Studio│ │      │                                            │
│  └──────┘ └──────┘ └──────┘                                            │
│                                                                         │
│  Write once, build anywhere!                                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Minimal CMakeLists.txt

# Minimum required CMake version
cmake_minimum_required(VERSION 3.16)

# Project name and version
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

# Create an executable
add_executable(my_app main.cpp)

Project Structure

┌─────────────────────────────────────────────────────────────────────────┐
│                    TYPICAL CMAKE PROJECT STRUCTURE                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  my_project/                                                            │
│  ├── CMakeLists.txt           ◄── Root CMake file                       │
│  ├── src/                                                               │
│  │   ├── CMakeLists.txt       ◄── Source directory CMake               │
│  │   ├── main.cpp                                                       │
│  │   ├── utils.cpp                                                      │
│  │   └── utils.hpp                                                      │
│  ├── include/                                                           │
│  │   └── my_project/                                                    │
│  │       └── public_header.hpp                                          │
│  ├── tests/                                                             │
│  │   ├── CMakeLists.txt       ◄── Test directory CMake                 │
│  │   └── test_utils.cpp                                                 │
│  ├── libs/                    ◄── Third-party libraries                │
│  └── build/                   ◄── Build output (generated)             │
│      ├── Makefile                                                       │
│      └── ...                                                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Building with CMake

┌─────────────────────────────────────────────────────────────────────────┐
│                    BUILD COMMANDS                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  # Step 1: Create build directory                                       │
│  mkdir build && cd build                                                │
│                                                                         │
│  # Step 2: Generate build files                                         │
│  cmake ..                                                               │
│                                                                         │
│  # Step 3: Build the project                                            │
│  cmake --build .                                                        │
│                                                                         │
│  # Or use make directly (if Makefile generator)                         │
│  make                                                                   │
│                                                                         │
│  # Build with parallel jobs                                             │
│  cmake --build . -j4                                                    │
│                                                                         │
│  # Build specific target                                                │
│  cmake --build . --target my_app                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

CMake Commands

Creating Targets

# Executable
add_executable(app_name source1.cpp source2.cpp)

# Static library (.a / .lib)
add_library(mylib STATIC source1.cpp source2.cpp)

# Shared library (.so / .dll)
add_library(mylib SHARED source1.cpp source2.cpp)

# Header-only library (INTERFACE)
add_library(mylib INTERFACE)

Setting Properties

# C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Or per-target (modern way)
target_compile_features(my_app PRIVATE cxx_std_17)

# Compiler flags
target_compile_options(my_app PRIVATE -Wall -Wextra -Wpedantic)

# Definitions
target_compile_definitions(my_app PRIVATE DEBUG_MODE=1)

Include Directories

# Include paths
target_include_directories(my_app
    PUBLIC  ${PROJECT_SOURCE_DIR}/include  # Propagates to dependents
    PRIVATE ${PROJECT_SOURCE_DIR}/src      # Only for this target
)

Linking

# Link libraries
target_link_libraries(my_app
    PRIVATE mylib       # Internal dependency
    PUBLIC  otherlib    # Propagates to dependents
)

Visibility Keywords

┌─────────────────────────────────────────────────────────────────────────┐
│               TARGET PROPERTY VISIBILITY                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                         PRIVATE                                  │   │
│  │  Only affects the target itself                                  │   │
│  │  Example: Implementation details, internal includes              │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                         PUBLIC                                   │   │
│  │  Affects target AND anything that links to it                   │   │
│  │  Example: Public headers, API definitions                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                        INTERFACE                                 │   │
│  │  Only affects things that link to the target                    │   │
│  │  Example: Header-only libraries                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Example:                                                               │
│  ┌─────────┐ PUBLIC ┌─────────┐ links ┌─────────┐                      │
│  │  LibA   │───────►│  LibB   │──────►│   App   │                      │
│  └─────────┘        └─────────┘       └─────────┘                      │
│       │                  │                  │                           │
│       └──────────────────┴──────────────────┘                          │
│                App gets LibA's public properties!                       │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Finding Packages

# Find system-installed package
find_package(Threads REQUIRED)

# Find with components
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)

# Use the found package
target_link_libraries(my_app
    PRIVATE
        Threads::Threads
        Boost::filesystem
        Boost::system
)

Common Package Patterns

# OpenSSL
find_package(OpenSSL REQUIRED)
target_link_libraries(app PRIVATE OpenSSL::SSL OpenSSL::Crypto)

# CURL
find_package(CURL REQUIRED)
target_link_libraries(app PRIVATE CURL::libcurl)

# SQLite
find_package(SQLite3 REQUIRED)
target_link_libraries(app PRIVATE SQLite::SQLite3)

FetchContent (Download Dependencies)

include(FetchContent)

# Declare dependency
FetchContent_Declare(
    json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.2
)

# Make it available
FetchContent_MakeAvailable(json)

# Use it
target_link_libraries(my_app PRIVATE nlohmann_json::nlohmann_json)

Variables and Cache

# Regular variable
set(MY_VAR "value")

# Cache variable (user-configurable)
set(MY_OPTION "default" CACHE STRING "Description of option")

# Boolean option
option(ENABLE_TESTS "Build tests" ON)

# Use variables
message(STATUS "MY_VAR = ${MY_VAR}")

# Conditional
if(ENABLE_TESTS)
    add_subdirectory(tests)
endif()

Subdirectories

# Root CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyProject)

# Add subdirectories
add_subdirectory(src)
add_subdirectory(tests)

# src/CMakeLists.txt
add_library(mylib source.cpp)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# tests/CMakeLists.txt
add_executable(tests test_main.cpp)
target_link_libraries(tests PRIVATE mylib)

Complete Example

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Calculator VERSION 1.0.0 LANGUAGES CXX)

# Settings
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Options
option(BUILD_TESTS "Build unit tests" ON)

# Create library
add_library(calc_lib
    src/calculator.cpp
    src/operations.cpp
)

target_include_directories(calc_lib
    PUBLIC  ${PROJECT_SOURCE_DIR}/include
    PRIVATE ${PROJECT_SOURCE_DIR}/src
)

target_compile_options(calc_lib PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
    $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

# Create executable
add_executable(calculator src/main.cpp)
target_link_libraries(calculator PRIVATE calc_lib)

# Tests
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

# Installation
install(TARGETS calculator DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)

Generator Expressions

# Compiler-specific flags
target_compile_options(app PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-Wall>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

# Configuration-specific
target_compile_definitions(app PRIVATE
    $<$<CONFIG:Debug>:DEBUG_MODE>
    $<$<CONFIG:Release>:NDEBUG>
)

# Build interface vs install interface
target_include_directories(lib
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
)

Best Practices

┌─────────────────────────────────────────────────────────────────────────┐
│                    MODERN CMAKE BEST PRACTICES                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ✓ DO                              ✗ DON'T                              │
│  ─────────────────────────────     ─────────────────────────────        │
│  • Use target_* commands           • Use include_directories()          │
│  • Use PRIVATE/PUBLIC/INTERFACE    • Use link_libraries()               │
│  • Use generator expressions       • Use CMAKE_CXX_FLAGS                │
│  • Use FetchContent for deps       • Use file(GLOB ...) for sources     │
│  • Create IMPORTED targets         • Hardcode paths                     │
│  • Use cmake --build              • Mix old and modern CMake            │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  OLD (avoid):                                                    │   │
│  │  include_directories(include)                                    │   │
│  │  link_libraries(mylib)                                           │   │
│  │  add_definitions(-DDEBUG)                                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  MODERN (prefer):                                                │   │
│  │  target_include_directories(app PRIVATE include)                 │   │
│  │  target_link_libraries(app PRIVATE mylib)                        │   │
│  │  target_compile_definitions(app PRIVATE DEBUG)                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Useful Commands

CommandDescription
cmake -S . -B buildConfigure with source and build dirs
cmake --build buildBuild the project
cmake --build build -jBuild with all CPU cores
cmake --install buildInstall the project
cmake --build build --target cleanClean build
cmake -DCMAKE_BUILD_TYPE=Release ..Release build
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..For IDE/tools

Summary

ConceptPurpose
cmake_minimum_requiredSet minimum CMake version
project()Define project name and languages
add_executable()Create executable target
add_library()Create library target
target_* commandsModern, target-based configuration
find_package()Locate installed dependencies
FetchContentDownload dependencies at configure time

Next Steps

  • Practice with examples.cpp (a complete CMake project)
  • Complete the exercises.cpp challenges
  • Try building real projects with CMake
  • Explore CPack for packaging
CMake - C++ Tutorial | DeepML