blob: d7489528ad7e09b333b3fed00ac33b3da591b559 [file] [view] [edit]
# 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.