Understanding the Zephyr Build System: CMake, KConfig, and Ninja

August 06, 2024

Introduction

As I announced in the previous lesson, I've been taking the nRF Connect SDK Fundamentals course and I'm sharing my learning process in this blog series. In this post, we are going to talk about the Zephyr Build System and understand how CMake, KConfig, and Ninja work together in a Zephyr application.

The Role of CMake in Zephyr

CMake is a cross-platform build system generator. In the Zephyr build system, CMake is responsible for generating build scripts tailored to your development environment.
The CMake build process can be divided into two main stages: configuration and build, and we’ll discuss them later in this post.

CMakeLists.txt

Each Zephyr project includes a CMakeLists.txt file, which is the entry point for the CMake build process. This file defines the project's source files, includes directories, and sets up the necessary build configurations.

A typical CMakeLists.txt for a Zephyr application might look like this:

# Specify the minimum CMake version required
cmake_minimum_required(VERSION 3.20.0)

# Declare the name of the project
project(my_zephyr_app)

# Find the Zephyr package, which initializes the Zephyr build system
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

# Add the application source files
target_sources(app PRIVATE src/main.c)
  1. cmake_minimum_required: Specifies the minimum version of CMake required to build the project.
  2. project: Declares the project name.
  3. find_package: Finds and loads the Zephyr build system.
  4. target_sources: Adds the source files to the project.

Note: target_sources, CMake uses the concept of 'targets' to manage different components of the build. A target can be an executable, a library, or a generated file. For Zephyr, library targets are crucial. They encapsulate source files, headers, and build instructions.

KConfig

KConfig is a configuration system originally developed for the Linux kernel. In Zephyr, KConfig allows you to specify settings for the target architecture, SoC (System on Chip), board, and application.

Kconfig Files

KConfig files also known as configuration fragments, specify available configuration options and their dependencies. These files are scattered across the architecture, SoC, board, and application directories.

During the build process, specifically, in the configuration phase, the initial configuration is generated by merging configuration fragments, such as prj.conf from your application and default configurations from the board and SoC (e.g <board_name>_defconfig files).

Here’s an example of a prj.conf file:

# Enable logging
CONFIG_LOG=y

# Set the logging level to debug
CONFIG_LOG_DEFAULT_LEVEL=4

# Enable a specific driver
CONFIG_SENSOR=y
CONFIG_LIS3DH=y
  1. CONFIG_LOG: Enables the logging subsystem.
  2. CONFIG_LOG_DEFAULT_LEVEL: Sets the default logging level.
  3. CONFIG_SENSOR: Enables sensor support.
  4. CONFIG_LIS3DH: Enables support for the LIS3DH sensor.

KConfig Output Files

As a result, KConfing generates two files: autoconf.h and .config

  1. autoconf.h: KConfig generates an autoconf.h header file containing preprocessor macros that correspond to the selected configuration options. This header is automatically included in the build, so you don't need to include it manually.
  2. .config: The .config file serves as both a saved configuration and an input for CMake, ensuring the build process respects the chosen options.

KConfig Customization

You can also customize the configuration using menuconfig, a terminal-based configuration tool:

west build -t menuconfig

This command opens an interactive menu where you can browse and modify configuration options.

Building with Ninja

Ninja is a small, high-speed build system that focuses on improving the efficiency of incremental builds. In the Zephyr build system, Ninja is used to perform the actual compilation of the source code.

Build Process

During the build stage, Ninja is invoked and process’s output is a complete Zephyr application (zephyr.elf, zephyr.hex, etc.). Conceptually, the build phase can be divided into four stages: the pre-build, first-pass binary, final binary, and post-processing.

  1. Pre-Build Stage: Before actual compilation, the pre-build stage handles tasks such as preparing directories, copying files, and generating necessary headers from configuration and devicetree sources.
  2. First-Pass Binary: Ninja compiles the source files into object files and links them into an initial binary. This stage might involve multiple steps, depending on the complexity of the project.
  3. Final Binary: The final binary stage refines the initial binary, applying optimizations and final linking steps to produce the executable output (e.g., zephyr.elf, zephyr.hex).
  4. Post-Processing: After building the final binary, post-processing steps may include generating additional files like disassembly listings or preparing the binary for flashing onto the target device.

The Zephyr's Build Process Explained

So, what happens when you hit your ‘build’ button in VSCode or when you run the west build command from the CLI?

As we discussed before, the Zephy Config System utilizes CMake under the hood and that initiates the build process. This process can be divided into two main stages: configuration and build.

Configuration Phase

When you initiate a build with west build command, the following steps occur:

  1. CMake Configuration:

    • CMake processes CMakeLists.txt files starting from your application directory, then the Zephyr root directory, and finally throughout the entire build tree. These scripts define the structure and dependencies of the project.
    • Generates platform-specific build scripts, typically for Ninja or Make. These scripts include all necessary instructions to compile the application and the Zephyr kernel.
    • Finds and includes Zephyr CMake Package files.
  2. KConfig Configuration:

    • All configuration fragments are processed and merged, and as the result, the following files ( .config , autoconf.h ) are generated.

Build Phase

  1. Ninja Build:

    • After configuration, Ninja start with the build phase by using the generated build script files. These scripts manage the compilation process efficiently, only recompiling files that have changed.
    • Compiles the source code and links the application generating the zephyr.elf, zephyr.hex files.

Here's a visual representation of the relationships and processes involving CMakeLists.txt, KConfig, and Ninja in the Zephyr build system:

zephyr_build_system_overview_rev1.png

Customization

  • Use menuconfig to interactively modify configuration options in prj.conf.
  • Rebuild the project with west build to apply new configurations.

Here’s how you can build a Zephyr application using the west tool, which wraps around CMake and Ninja:

# Initialize the build directory and configure the build
west build -b nrf52840dk_nrf52840

# If you need to clean the build directory and rebuild
west build -c
  1. west build -b : Specifies the target board for the build.
  2. west build -c: Cleans the build directory and configures the build from scratch.

Conclusion

Understanding the Zephyr build system's components—CMake, KConfig, and Ninja—provides a solid foundation for developing applications with Zephyr. CMake manages the build configuration, KConfig customizes build options, and Ninja efficiently compiles the code. By mastering these tools and following the development life cycle, you can leverage the full power of the Zephyr RTOS for your embedded projects.

Happy coding!


Further Learning

For more details on this topic, refer to the Zephyr Build System Documentation, which provides comprehensive coverage of this topic and other essential concepts.

Upcoming Series

Stay tuned for more posts in this series, where we'll dive deeper into various aspects of nRF Connect SDK and Zephyr, providing practical insights and examples to help you master these powerful tools.


Profile picture

Written by Marco Ciau who is apassionate about providing solutions by using software. I thoroughly enjoy learning new things and am always eager to embrace new challenges. You can follow me on Twitter.