CMake Build System

Zephyr uses CMake as its build system generator. Understanding CMake is essential for customizing builds.

Build Flow Overview

flowchart TD
    A[west build] --> B[CMake Configure]
    B --> C[Process CMakeLists.txt]
    C --> D[Find Zephyr Package]
    D --> E[Load Board Config]
    E --> F[Process Kconfig]
    F --> G[Process Device Tree]
    G --> H[Generate build.ninja]
    H --> I[Ninja Build]
    I --> J[Compile Sources]
    J --> K[Link Application]
    K --> L[Generate Outputs]

    L --> M[zephyr.elf]
    L --> N[zephyr.hex]
    L --> O[zephyr.bin]

Minimal CMakeLists.txt

Every Zephyr application needs a CMakeLists.txt:

# Minimum required CMake version
cmake_minimum_required(VERSION 3.20.0)

# Find and load Zephyr build system
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

# Define project name
project(my_app)

# Add source files
target_sources(app PRIVATE src/main.c)

Key Elements

Element Purpose
cmake_minimum_required Ensures CMake version compatibility
find_package(Zephyr) Loads Zephyr’s build system
project() Names your project
target_sources() Adds source files to build

The app Target

After find_package(Zephyr), a target named app is created. This is your application:

# Add source files
target_sources(app PRIVATE
    src/main.c
    src/utils.c
    src/drivers/my_driver.c
)

# Add include directories
target_include_directories(app PRIVATE
    src/include
    ${CMAKE_CURRENT_SOURCE_DIR}/lib
)

# Add compile definitions
target_compile_definitions(app PRIVATE
    MY_FEATURE_ENABLED=1
    VERSION_STRING="1.0.0"
)

# Link libraries
target_link_libraries(app PRIVATE my_library)

Adding Source Files

Single Directory

target_sources(app PRIVATE src/main.c)

Multiple Files

target_sources(app PRIVATE
    src/main.c
    src/config.c
    src/handlers.c
)
# Works but not recommended - CMake won't detect new files
file(GLOB SOURCES "src/*.c")
target_sources(app PRIVATE ${SOURCES})

Conditional Sources

target_sources(app PRIVATE src/main.c)

if(CONFIG_MY_FEATURE)
    target_sources(app PRIVATE src/my_feature.c)
endif()

Include Directories

# Application includes
target_include_directories(app PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# System includes (suppress warnings)
target_include_directories(app SYSTEM PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/third_party/include
)

Compile Definitions

# Add preprocessor defines
target_compile_definitions(app PRIVATE
    DEBUG_ENABLED
    MAX_CONNECTIONS=10
    DEVICE_NAME="MyDevice"
)

# Conditional defines
if(CONFIG_DEBUG_OPTIMIZATIONS)
    target_compile_definitions(app PRIVATE DEBUG_BUILD)
endif()

Compile Options

# Add compiler flags
target_compile_options(app PRIVATE
    -Wall
    -Wextra
    -Werror
)

# Architecture-specific
if(CONFIG_ARM)
    target_compile_options(app PRIVATE -mthumb)
endif()

Linking Libraries

Zephyr Libraries

# Link against Zephyr subsystems
target_link_libraries(app PRIVATE
    subsys__net__ip
    subsys__bluetooth
)

External Libraries

# Add external static library
add_library(mylib STATIC IMPORTED)
set_target_properties(mylib PROPERTIES
    IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/lib/libmylib.a
)
target_link_libraries(app PRIVATE mylib)

Using Kconfig Values in CMake

Kconfig options are available as CMake variables with CONFIG_ prefix:

if(CONFIG_MY_FEATURE)
    target_sources(app PRIVATE src/my_feature.c)
endif()

if(CONFIG_LOG)
    target_compile_definitions(app PRIVATE LOGGING_ENABLED)
endif()

# Use Kconfig string values
message(STATUS "Board: ${CONFIG_BOARD}")

Project Structure

Typical Layout

my-app/
├── CMakeLists.txt
├── prj.conf
├── Kconfig                 # Optional: app-specific options
├── src/
│   ├── main.c
│   └── modules/
│       ├── CMakeLists.txt  # Optional: subdirectory
│       └── module.c
├── include/
│   └── my_app.h
└── boards/
    └── nrf52840dk_nrf52840.overlay

Subdirectory CMakeLists.txt

# src/modules/CMakeLists.txt
target_sources(app PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/module.c
)

Main CMakeLists.txt:

add_subdirectory(src/modules)

Build Directory Structure

After building:

build/
├── CMakeCache.txt          # CMake cache
├── build.ninja             # Ninja build file
├── compile_commands.json   # For IDE integration
├── zephyr/
│   ├── zephyr.elf          # Main output
│   ├── zephyr.hex
│   ├── zephyr.bin
│   ├── .config             # Final Kconfig
│   └── include/generated/  # Generated headers
└── Kconfig/                # Kconfig processing

CMake Variables

Useful Zephyr Variables

Variable Description
ZEPHYR_BASE Path to Zephyr
BOARD Target board name
BOARD_DIR Board directory path
APPLICATION_SOURCE_DIR Your app’s source dir
PROJECT_BINARY_DIR Build directory

Using Variables

message(STATUS "Building for: ${BOARD}")
message(STATUS "Zephyr base: ${ZEPHYR_BASE}")
message(STATUS "App source: ${APPLICATION_SOURCE_DIR}")

Advanced Topics

Custom CMake Modules

# Add module path
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)

# Include custom module
include(MyModule)

Post-Build Commands

# Run command after build
add_custom_command(TARGET app POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E echo "Build complete!"
    COMMAND ${CMAKE_COMMAND} -E copy
        ${PROJECT_BINARY_DIR}/zephyr/zephyr.hex
        ${CMAKE_CURRENT_SOURCE_DIR}/output/
)

Custom Targets

# Add custom target
add_custom_target(docs
    COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    COMMENT "Generating documentation"
)

Troubleshooting

“Zephyr package not found”

export ZEPHYR_BASE=~/zephyrproject/zephyr
# Or
source ~/zephyrproject/zephyr/zephyr-env.sh

CMake cache issues

# Clean rebuild
west build -p

View CMake configuration

# See all variables
cmake -LA build/

Next Steps

Learn about Kconfig to configure your application’s features.


Back to top

Zephyr RTOS Programming Guide is not affiliated with the Zephyr Project or Linux Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.