A complete Zephyr RTOS example application managed with dfetch instead of west submodules.
Zephyr's standard tooling relies on west, which uses git submodules under the hood. This approach has several drawbacks:
- Submodule state is implicit and easy to break
- Dependencies are not stored inside the repository — contributors must run
west updateafter every clone - Merging and rebasing across submodule boundaries is error-prone
dfetch solves this by copying dependency sources directly into the repository. All code lives in one place, making the repository fully self-contained and reproducible without any extra fetch step after cloning.
This repo demonstrates the Zephyr example-application set up with dfetch and a Dockerfile to keep the build environment reproducible.
- Prerequisites
- Install dfetch
- Create the basic manifest
- Fetch the core sources
- Register dfetch as a west command
- Add hardware abstraction modules
- Wire up the CMake module
- Take ownership of the application
- How west is configured
- Build
- Flash
Before starting, make sure the following tools are installed on your system:
- Python 3.8+ — required by dfetch and Zephyr's scripts
- git — required by dfetch to fetch sources
- CMake 3.13.1+ and Ninja — the Zephyr build system
- Zephyr SDK — the cross-compilation toolchain; follow the official Zephyr Getting Started Guide
west (Zephyr's meta-tool) is installed automatically in the next step via pip.
Tip
The easiest way to get a fully configured environment is to open this repository in the provided Dev Container. It includes the Zephyr SDK, CMake, and all other build tools pre-installed.
Create a Python virtual environment and install dfetch.
Unix/macOS
python -m venv venv
. venv/bin/activate
pip install dfetchWindows (Command Prompt)
python -m venv venv
venv\Scripts\activate.bat
pip install dfetchWindows (PowerShell)
If script execution is blocked, run
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUseronce first.
python -m venv venv
venv\Scripts\Activate.ps1
pip install dfetchdfetch is driven by a dfetch.yaml manifest that lists all external dependencies. Create the following file in the root of your workspace:
manifest:
version: 0.0
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
repo-path: zephyr.git
dst: zephyr
branch: main
ignore:
- .github
- samples
- tests
- name: application
dst: application
branch: main
repo-path: example-application.gitThis manifest defines two projects:
| Project | Source | Destination |
|---|---|---|
zephyr |
zephyrproject-rtos/zephyr |
zephyr/ |
application |
zephyrproject-rtos/example-application |
application/ |
Run dfetch to clone the declared projects into the repository:
dfetch updateThen install Zephyr's Python tooling into the same virtual environment (this includes west):
pip install -r zephyr/scripts/requirements-base.txtNote
All subsequent commands (dfetch, west) assume this virtual environment is active.
At this point west is installed but knows nothing about dfetch. The next steps
will add HAL modules via dfetch, and it is convenient to run them with
west update so the workflow feels natural to anyone familiar with west.
This repository ships a west extension command — dfetch-update — that
calls dfetch's own CLI. The command lives in dfetch/commands/:
dfetch/commands/dfetch_update.py— the extension implementationdfetch/commands/west-commands.yml— the declaration west reads
Copy both files from this repository into the same paths in your workspace.
Next, register the command with west by adding the dfetch directory as a
project in application/west.yml. West loads extension commands from the
project's directory, so pointing the project path at dfetch/ is enough —
no separate git repository is needed:
manifest:
self:
west-commands: scripts/west-commands.yml
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
- name: dfetch-org
url-base: https://github.com/dfetch-org
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: main
west-commands: scripts/west-commands.yml
- name: example-zephyr
remote: dfetch-org
revision: main
path: dfetch
west-commands: commands/west-commands.ymlNow initialise the west workspace and set the alias that maps west update
to dfetch-update:
west init -l application
west config alias.update dfetch-updateCommit the resulting .west/config so that everyone who clones the
repository gets a working workspace with the alias already set — no manual
setup required:
git add .west/config application/west.yml dfetch/commands
git commit -m "Register dfetch-update as a west command, alias west update"From this point on use west update wherever you would previously have run
dfetch update. West resolves the alias and calls dfetch-update, which
invokes dfetch internally.
The example application requires several hardware abstraction layer (HAL) modules. These are declared in application/west.yml under a name-allowlist:
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: main
import:
name-allowlist:
- cmsis_6 # required by the ARM port for Cortex-M
- hal_nordic # required by the custom_plank board (Nordic based)
- hal_stm32 # required by the nucleo_f302r8 board (STM32 based)The entries in name-allowlist refer to projects defined in zephyr/west.yml. Look each one up there to find its revision and path. For example:
# in zephyr/west.yml
- name: cmsis_6
repo-path: CMSIS_6
revision: 30a859f44ef8ab4dc8f84b03ed586fd16ccf9d74
path: modules/hal/cmsis_6
groups:
- hal
- name: hal_nordic
revision: 4dea410729da817a464f778533eb721473262e5b
path: modules/hal/nordic
groups:
- hal
- name: hal_stm32
revision: 57803da28e985e1cbc32a7ea993578f7267d0935
path: modules/hal/stm32
groups:
- halTranslate each entry into dfetch syntax and add them to your dfetch.yaml:
Note
The dfetch manifest syntax is similar to west.yml but has a few differences:
path:becomesdst:groups:is not used and should be removedrepo-path:must be set explicitly (append.gitto the name if the field is absent)
- name: cmsis_6 # required by the ARM port for Cortex-M
repo-path: CMSIS_6.git
revision: 30a859f44ef8ab4dc8f84b03ed586fd16ccf9d74
dst: modules/hal/cmsis_6
- name: hal_nordic # required by the custom_plank board (Nordic based)
repo-path: hal_nordic.git
revision: 4dea410729da817a464f778533eb721473262e5b
dst: modules/hal/nordic
- name: hal_stm32 # required by the nucleo_f302r8 board (STM32 based)
revision: 57803da28e985e1cbc32a7ea993578f7267d0935
repo-path: hal_stm32.git
dst: modules/hal/stm32Run west update to fetch the new modules (the alias calls dfetch-update
which in turn invokes dfetch):
west updateZephyr needs to know about the fetched modules at CMake configure time.
This repository provides a self-contained CMake module in dfetch/cmake/
that does this automatically by reading dfetch.yaml — no manual
-DZEPHYR_MODULES flags required.
Create the directory and copy the two files from this repository:
Then add the following lines to application/app/CMakeLists.txt before
find_package(Zephyr ...):
get_filename_component(WORKSPACE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
list(APPEND CMAKE_MODULE_PATH "${WORKSPACE_ROOT}/dfetch/cmake")
include(DfetchModules)
dfetch_discover_zephyr_modules("${WORKSPACE_ROOT}/dfetch.yaml" "${WORKSPACE_ROOT}")
dfetch_set_zephyr_build_version("${WORKSPACE_ROOT}/zephyr")dfetch_discover_zephyr_modules scans every dst: path in dfetch.yaml
and adds those that contain a zephyr/module.yml to ZEPHYR_MODULES.
dfetch_set_zephyr_build_version reads zephyr/VERSION and sets
BUILD_VERSION, silencing the CMake warning that appears when Zephyr is
present as a plain directory without its own .git.
The application entry in dfetch.yaml was only there to bootstrap a
starting point — a minimal Zephyr application structure to build from.
Now that you have a local copy, the directory belongs to your repository.
Keeping it under dfetch management would mean dfetch update can overwrite
your edits at any time, so remove it from the manifest.
The fetched application/west.yml contains an import: block that tells
west to pull HAL modules from Zephyr's own manifest. Because dfetch now
manages those modules directly, delete the import: block from the zephyr
project entry. The block looks like this — remove it entirely:
# delete these lines from application/west.yml:
import:
name-allowlist:
- cmsis_6
- hal_nordic
- hal_stm32Then remove the application entry from dfetch.yaml and delete
.dfetch_data.yaml (dfetch's tracking file) so dfetch no longer considers
the directory its own:
rm application/.dfetch_data.yamlYour final dfetch.yaml should look like this:
manifest:
version: 0.0
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
repo-path: zephyr.git
dst: zephyr
branch: main
- name: cmsis_6 # required by the ARM port for Cortex-M
repo-path: CMSIS_6.git
revision: 30a859f44ef8ab4dc8f84b03ed586fd16ccf9d74
dst: modules/hal/cmsis_6
- name: hal_nordic # required by the custom_plank board (Nordic based)
repo-path: hal_nordic.git
revision: 4dea410729da817a464f778533eb721473262e5b
dst: modules/hal/nordic
- name: hal_stm32 # required by STM32-based boards (e.g. nucleo_f302r8)
repo-path: hal_stm32.git
revision: 57803da28e985e1cbc32a7ea993578f7267d0935
dst: modules/hal/stm32The application/ directory is itself a Zephyr module — it contains a
zephyr/module.yml that registers its Kconfig symbols, DTS bindings, and
board definitions with the build system. Now that it is no longer in
dfetch.yaml, dfetch_discover_zephyr_modules will not add it to
ZEPHYR_MODULES. Add it explicitly in application/app/CMakeLists.txt before find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}):
list(APPEND ZEPHYR_MODULES "${WORKSPACE_ROOT}/application")Commit application/ along with the updated manifest so the directory is
fully tracked by your repository:
git add application dfetch.yaml
git commit -m "Take ownership of application, remove from dfetch manifest"After completing the steps above, application/west.yml looks like this:
manifest:
self:
west-commands: scripts/west-commands.yml
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
- name: dfetch-org
url-base: https://github.com/dfetch-org
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: main
west-commands: scripts/west-commands.yml
- name: example-zephyr
remote: dfetch-org
revision: main
path: dfetch
west-commands: commands/west-commands.ymlThere are three west-commands declarations:
| Declaration | Source | What it provides |
|---|---|---|
self: west-commands |
application/scripts/west-commands.yml |
application-specific extensions |
zephyr: west-commands |
zephyr/scripts/west-commands.yml |
west build, west flash, etc. |
example-zephyr: west-commands |
dfetch/commands/west-commands.yml |
dfetch-update |
West resolves path: dfetch to the workspace-root dfetch/ directory and
loads commands/west-commands.yml from there. Because the file lies inside
that directory, no path escaping occurs and no separate git repository is
required.
The committed .west/config records the manifest location, the Zephyr base,
and the dfetch-update alias set up in step 5:
[manifest]
path = application
file = west.yml
[zephyr]
base = zephyr
[alias]
update = dfetch-updateActivate Zephyr's environment and run west build. No extra flags are needed
— application/app/CMakeLists.txt reads dfetch.yaml at configure time,
finds every dst: directory that contains a zephyr/module.yml, and adds
them to ZEPHYR_MODULES automatically.
Unix/macOS
source zephyr/zephyr-env.sh
west build -b nucleo_f302r8 application/appWindows (Command Prompt)
call zephyr\zephyr-env.cmd
west build -b nucleo_f302r8 application\appWindows (PowerShell)
Zephyr does not ship a PowerShell environment script. The simplest option is to invoke the Command Prompt script within a cmd subshell:
cmd /c "zephyr\zephyr-env.cmd && west build -b nucleo_f302r8 application\app"[!TIP] Zephyr development on Windows is also well-supported via WSL 2, where the Unix instructions apply directly.
When CMake runs you will see a status line for each discovered module:
-- Found dfetch module: modules/hal/cmsis_6
-- Found dfetch module: modules/hal/nordic
-- Found dfetch module: modules/hal/stm32
Adding a new module to dfetch.yaml (and running dfetch update) is all
that is needed — the build picks it up on the next configure with no
CMakeLists.txt changes required.
See Building without west for more details on standalone builds.
Flash the compiled firmware to the connected board:
west flashThe default runner for nucleo_f302r8 is stm32cubeprogrammer. To use a different runner:
# Use OpenOCD
west flash --runner openocd
# Use J-Link
west flash --runner jlinkAvailable runners for nucleo_f302r8: stm32cubeprogrammer (default), openocd, jlink.