The zephyr-bazel project provides a Bazel overlay for the Zephyr RTOS. It allows you to build Zephyr applications using Bazel while maintaining the familiar concepts of applications, boards, Kconfig, and Devicetree. This guide assumes you are already familiar with these Zephyr concepts. If you are new to Zephyr, please refer to the official Zephyr documentation first.
In this setup, Zephyr applications are defined as Bazel targets, and boards are represented as Bazel platforms. Kconfig and Devicetree configurations are resolved contextually for each application and board combination, similar to how the standard Zephyr build system works, but fully integrated into the Bazel build graph.
In Zephyr-Bazel, applications are defined as Bazel targets, but they maintain the standard Zephyr structure and configuration files you are familiar with.
MODULE.bazelTo make the build system aware of your applications, you must register all directories containing application folders in your project's MODULE.bazel file. This includes custom application directories as well as standard Zephyr directories (like the Zephyr samples directory) if you wish to build them.
This is done using the zephyr_setup module extension. You provide a list of directory paths to the apps_dirs parameter:
zephyr_setup = use_extension("@zephyr-bazel//:setup.bzl", "zephyr_setup") zephyr_setup.env( apps_dirs = [ "//apps", "@zephyr//samples", # Required to build Zephyr samples ], )
This tells Bazel to scan these directories for valid Zephyr applications.
For a directory to be passed as a label to apps_dirs, it must be a valid Bazel package. This means the directory must contain a BUILD or BUILD.bazel file. Within that directory, the discovery system looks for standard Zephyr application indicators like a prj.conf file.
Here is an example of a simple project structure:
my_project/ ├── MODULE.bazel ├── apps/ │ ├── BUILD.bazel │ ├── my_app1/ │ │ ├── BUILD.bazel │ │ ├── prj.conf │ │ └── main.cc │ └── my_app2/ │ ├── BUILD.bazel │ ├── prj.conf │ └── main.cc └── src/ └── (optional shared source files)
Inside your application's directory, you define the build target using a zephyr_app macro in a BUILD.bazel file (similar to a CMakeLists.txt file in standard Zephyr).
Here is what creating a Zephyr app in Bazel does for you automatically:
autoconf.h is automatically included in your source files, so Kconfig symbols are available.BUILD.bazelHere is an example of how to define a Zephyr application in a BUILD.bazel file:
load("@zephyr//:cc.bzl", "zephyr_app", "zephyr_cc_library") zephyr_app( name = "hello_bazel", deps = [ ":app_main", ], ) zephyr_cc_library( name = "app_main", srcs = ["hello_bazel.cc"], )
A key feature of this setup is that a single Zephyr application target can be built against multiple different boards (platforms). You do not need to create separate build targets for each board.
When you build an application, Bazel will look for configuration files just like standard Zephyr does:
prj.conf file in the application root.boards/ subdirectory (e.g., boards/<board_name>.conf).boards/ directory (e.g., boards/<board_name>.overlay).The resolution of Kconfig symbols and Devicetree overlays works the same way as in the standard Zephyr build system, ensuring compatibility with existing Zephyr projects.
In Zephyr-Bazel, hardware boards are represented as Bazel platforms. A platform defines the constraints of the target hardware (like CPU architecture) and tells Bazel which Zephyr board to use for building.
When you build an application, you specify the target board using the --platforms flag:
bazelisk build //apps/my_app1 --platforms=//boards/my_custom_board
Bazel maps this platform label to a Zephyr board using a set of resolution rules based on the package path and target name.
MODULE.bazelBy default, the build system automatically includes all in-tree boards provided by the Zephyr repository. If you have Out-of-Tree (OOT) boards, you must register their directories in your MODULE.bazel file using the boards_dirs parameter:
zephyr_setup.env( apps_dirs = ["//apps"], boards_dirs = ["//boards"], # Register OOT boards directory )
To prevent generating a massive matrix of build targets for every possible combination of application and board, the system uses heuristics to determine which combinations are valid:
boards/ directory (e.g., boards/<board_name>.conf or boards/<board_name>.overlay)..conf file for that board in the app's boards/ directory.manual_boards list in MODULE.bazel:zephyr_setup.env( apps_dirs = ["//apps"], manual_boards = ["nrf52840dk/nrf52840"], # Available for all apps )
Zephyr's Hardware Model v2 (HWM v2) supports boards with multiple SoCs and qualifiers (modifiers). The Bazel build system handles these by matching the target name in the platform label to the Zephyr board ID or SoC name.
Here are the resolution rules used to map a Bazel platform to a Zephyr board:
:nrf52840 in the //boards/nordic/nrf52840dk package matches the board/SoC combination nrf52840dk/nrf52840.:default and the directory contains exactly one board, that board is selected.Here is how you might define a platform for a simple board in a BUILD.bazel file inside your board's directory (e.g., //boards/my_board):
platform( name = "my_board", constraint_values = [ "@platforms//cpu:armv7m", "@zephyr//constraints/arm:cortex-m4", ], )
You would build for this board using: --platforms=//boards/my_board
For a multi-core board like nrf5340dk, you can define platforms for both the application and network cores by matching the target name to the Zephyr SoC name:
# In //boards/nordic/nrf5340dk/BUILD.bazel # Application core platform platform( name = "nrf5340_cpuapp", constraint_values = [ "@platforms//cpu:armv8-m", "@zephyr//constraints/arm:cortex-m33", ], ) # Network core platform platform( name = "nrf5340_cpunet", constraint_values = [ "@platforms//cpu:armv8-m", "@zephyr//constraints/arm:cortex-m33", ], )
Referencing //boards/nordic/nrf5340dk:nrf5340_cpuapp will resolve to the nrf5340dk/nrf5340_cpuapp board in Zephyr, and similarly for the network core.
Zephyr modules provide additional source code, drivers, and libraries that can be used by your applications.
Zephyr modules are discovered by the build system similarly to apps and boards. You can include modules in two ways:
bazel_dep), the zephyr_setup extension will automatically attempt to discover it if it contains a zephyr/module.yml file.modules attribute of zephyr_setup.env in MODULE.bazel:zephyr_setup.env( apps_dirs = ["//apps"], modules = [ "@my_module//:zephyr/module.yml", ], )
For a Zephyr module to work in this system, it must have Bazel support. This means it needs to contain BUILD.bazel files defining its libraries (either provided natively by the module or applied via a Bazel overlay).
zephyr/module.ymlFor the automatic module discovery from Bzlmod dependencies to work, the module's repository must export the zephyr/module.yml file. If the file is not visible to external repositories, the discovery scan will not be able to read it, and the module will not be detected.
In the module's BUILD.bazel file (at the root or where module.yml resides), you should ensure it is exported:
exports_files(["zephyr/module.yml"])
zephyr-bazel supports Zephyr's Sysbuild feature for multi-image, multi-core, and multi-SoC targets.
You define a sysbuild target using the zephyr_sysbuild macro in a BUILD.bazel file:
load("@zephyr-bazel//:bazel/sysbuild.bzl", "zephyr_sysbuild") zephyr_sysbuild( name = "my_firmware", main = "//apps/my_app", )
sysbuild.yml in the app directory and boards/<board>.sysbuild.yml for board-specific helpers.sysbuild.conf in the app directory. Settings are translated to target images.While zephyr-bazel attempts to mirror the flexibility of the standard Zephyr build system, some features are unsupported or work differently due to Bazel's architecture:
testcase.yaml (like arch_allow: arm). In this system, complex filters are skipped during the loading phase to avoid lockfile explosions, falling back to filesystem heuristics (checking for <board>.conf).menuconfig): The system generates Kconfig configurations statically. Interactive tools like menuconfig or guiconfig are not directly supported for generating prj.conf updates within the Bazel workflow.west Workspace Structure: Since Bazel fetches dependencies into its own cache, there is no standard west workspace directory structure. Tools that expect a specific relative path layout between Zephyr and modules may need adaptation.<board>.conf file or listing it in manual_boards, due to the discovery pruning logic.