| # User Guide |
| |
| ## Introduction |
| |
| 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](https://docs.zephyrproject.org/) 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. |
| |
| ## Zephyr Apps |
| |
| In Zephyr-Bazel, applications are defined as Bazel targets, but they |
| maintain the standard Zephyr structure and configuration files you are |
| familiar with. |
| |
| ### Registering Applications in `MODULE.bazel` |
| |
| To 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: |
| |
| ```python |
| 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: |
| |
| ```text |
| 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) |
| ``` |
| |
| ### Defining a Zephyr Application |
| |
| 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: |
| - **Header Injection**: It ensures that the generated `autoconf.h` is |
| automatically included in your source files, so Kconfig symbols are |
| available. |
| - **Devicetree Integration**: It handles mapping the generated Devicetree |
| headers to your build targets. |
| - **Configuration Resolution**: It sets up the necessary "transitions" |
| (a Bazel concept) to allow building the same app for different boards. |
| |
| ### Example `BUILD.bazel` |
| |
| Here is an example of how to define a Zephyr application in a |
| `BUILD.bazel` file: |
| |
| ```python |
| 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"], |
| ) |
| ``` |
| |
| ### Multi-Board Support and Configuration |
| |
| 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: |
| - It reads the base `prj.conf` file in the application root. |
| - It looks for board-specific configurations in a `boards/` |
| subdirectory (e.g., `boards/<board_name>.conf`). |
| - It applies Devicetree overlays found in the `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. |
| |
| ## Zephyr Boards |
| |
| 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. |
| |
| ### Mapping Platforms to Boards |
| |
| When you build an application, you specify the target board using the |
| `--platforms` flag: |
| |
| ```bash |
| 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. |
| |
| ### Registering Boards in `MODULE.bazel` |
| |
| By 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: |
| |
| ```python |
| zephyr_setup.env( |
| apps_dirs = ["//apps"], |
| boards_dirs = ["//boards"], # Register OOT boards directory |
| ) |
| ``` |
| |
| ### Board-App Combinations (Discovery) |
| |
| 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: |
| |
| 1. **Out-of-Tree Exemption**: If both the application and the board |
| are Out-of-Tree, the combination is automatically enabled. |
| 2. **Heuristics for In-Tree Assets**: For combinations involving |
| in-tree boards or apps, the system looks for specific configuration |
| files in the application's `boards/` directory (e.g., |
| `boards/<board_name>.conf` or `boards/<board_name>.overlay`). |
| - **Adding support for a specific board**: If you need to build an |
| application for a specific in-tree board, you can simply add a |
| corresponding `.conf` file for that board in the app's `boards/` |
| directory. |
| 3. **Manual Fallback**: If you want a board to be available for all |
| applications regardless of whether specific configuration files |
| exist, you can add it to the `manual_boards` list in `MODULE.bazel`: |
| |
| ```python |
| zephyr_setup.env( |
| apps_dirs = ["//apps"], |
| manual_boards = ["nrf52840dk/nrf52840"], # Available for all apps |
| ) |
| ``` |
| |
| ### Boards, SoCs, and Modifiers |
| |
| 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: |
| |
| 1. **Explicit Match**: The target name matches the Zephyr board ID. |
| 2. **SoC/Variant Match**: The target name matches a specific SoC |
| variant of a board. For example, a target `:nrf52840` in the |
| `//boards/nordic/nrf52840dk` package matches the board/SoC |
| combination `nrf52840dk/nrf52840`. |
| 3. **Default Shortcut**: If the target name is `:default` and the |
| directory contains exactly one board, that board is selected. |
| |
| ### Examples |
| |
| #### Defining a Simple Board |
| |
| 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`): |
| |
| ```python |
| 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` |
| |
| #### Handling SoCs and Variants |
| |
| 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: |
| |
| ```python |
| # 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 |
| |
| Zephyr modules provide additional source code, drivers, and libraries |
| that can be used by your applications. |
| |
| ### Including Zephyr Modules |
| |
| Zephyr modules are discovered by the build system similarly to apps and |
| boards. You can include modules in two ways: |
| |
| 1. **Bzlmod Dependencies**: If a module is available as a Bazel module |
| (via `bazel_dep`), the `zephyr_setup` extension will automatically |
| attempt to discover it if it contains a `zephyr/module.yml` file. |
| 2. **Manual Modules**: You can explicitly list modules in the |
| `modules` attribute of `zephyr_setup.env` in `MODULE.bazel`: |
| |
| ```python |
| zephyr_setup.env( |
| apps_dirs = ["//apps"], |
| modules = [ |
| "@my_module//:zephyr/module.yml", |
| ], |
| ) |
| ``` |
| |
| ### Bazel Support Requirement |
| |
| 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). |
| |
| ### Important: Exporting `zephyr/module.yml` |
| |
| For 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: |
| |
| ```python |
| exports_files(["zephyr/module.yml"]) |
| ``` |
| |
| ## Sysbuild |
| |
| `zephyr-bazel` supports Zephyr's Sysbuild feature for multi-image, multi-core, |
| and multi-SoC targets. |
| |
| ### Defining a Sysbuild Target |
| |
| You define a sysbuild target using the `zephyr_sysbuild` macro in a |
| `BUILD.bazel` file: |
| |
| ```python |
| load("@zephyr-bazel//:bazel/sysbuild.bzl", "zephyr_sysbuild") |
| |
| zephyr_sysbuild( |
| name = "my_firmware", |
| main = "//apps/my_app", |
| ) |
| ``` |
| |
| ### Configuration |
| |
| - **Hardware Graph**: Described in `sysbuild.yml` in the app directory and |
| `boards/<board>.sysbuild.yml` for board-specific helpers. |
| - **Kconfig**: Handled via `sysbuild.conf` in the app directory. Settings are |
| translated to target images. |
| |
| |
| ## Unsupported Zephyr Features |
| |
| 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: |
| |
| 1. **Full Twister Compatibility**: Native Twister can evaluate complex |
| dynamic constraints in `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`). |
| 2. **Interactive Kconfig (`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. |
| 3. **Standard `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. |
| 4. **Implicit Default Combinations**: You cannot build an app for an |
| in-tree board that relies purely on defaults without adding at least |
| an empty `<board>.conf` file or listing it in `manual_boards`, due |
| to the discovery pruning logic. |