Federico Fuga

Engineering, Tech, Informatics & science

The importance of Workspaces in Zephyr projects

25 Oct 2023 15:50 CEST

Introduction

I’ve been struggling days on trying to make zephyr compile my cc1101 driver and the test app.

I followed many tutorials and examples, even official examples, some of which even suddenly disappeared from the official documentation of Zephyr 3.5.99. But none was able to bringh light to this mystery.

This is the problem I had: though the compilation of the test application in app/ started and completed correctly, when linking west was unable to find the symbols exported by the driver. The problem was that the driver wasn’t compiled at all.

What every tutorial doesn’t say is that it is really important to set up correctly your workspaces, otherwise your code will not be treated as a Zephyr’s module, and all the driver code will be ignored.

I found the answer to my problem by casually spotting this page on the Zephyr Documentation. It refers to the West Basics page, but fails to explain that if you don’t mention your driver in your workspace as a module, it will never be referred by west and it will be ignored.

The Manifest

Every workspace has a manifest, that is a special file called west.yml. This file describes what is in the workspace codebase, especially it lists the content of the zephyr distribution, i.e. external modules, tools, etc.

Topology and locations

It is very important to decide how you want to manage your sources in relations with Zephyr. You may have as many workspaces as you prefer, you can have as many projects as you want, but it will be very important to know that once you put a project in a workspace, your code will be tied to that version of zephyr until you update it.

The Workspace page of the Documentation has a section named “Topologies supported” and this is what you need to know to decide the organization of your sources.

There are 3 supported topologies: Two different “star” topologies and a “forest” topology.

The “manifest-centric” star topology

This is the default topology you are going to use when you don’t have special requirements and you just want to develop or compile a few applications without finer control over the modules you want to add. This is also the topology you are already using if you followed the quick start guides.

In this case, the manifest of the workspace is the Zephyr manifest.

The “Project-centric” star topology

If you have one specific application that either have special requirements and dependencies, then you can use the project manifest instead of the zephyr manifest.

The structure of the workspace will be this:

west-workspace/
│
├── application/                   # .git/     │
│             ├── CMakeLists.txt               │
│             ├── prj.conf                     │  never modified by west
│             ├── src/                         │
│             │             └── main.c         │
│             └── west.yml         # main manifest with optional import(s) and override(s)
│                                              │
├── modules/
│             └── lib/
│                └── zcbor/       # .git/ project from either the main manifest or some import.
│
└── zephyr/              # .git/ project
    └── west.yml         # This can be partially imported with lower precedence or ignored.
                         # Only the 'manifest-rev' version can be imported.

(this scheme has been taken from the Zephyr documentation)

The application’s manifest is the main manifest and will overwrite or superseded part of the Zephyr manifest.

Note a few things:

manifest:
  remotes:
    - name: zephyrproject-rtos
      url-base: https://github.com/zephyrproject-rtos
  projects:
    - name: zephyr
      remote: zephyrproject-rtos
      revision: v2.5.0
      import: true
  self:
    path: application
  • the self: path: lines defines where the module is located relative to the workspace. So the workspace will be the parent of the application/directory.

  • the import: true line instruct west to take the list of the modules from the Zephyr manifest, the alternative is to list the dependencies manually, it is very useful to limit the size of the download and the number of dependent projects.

For an example of the import lines, you can see the zds-2022 sample drivers app, that was developed during the mastering Zephyr driver development talk. We see this:

manifest:
  self:
    path: app

  remotes:
    - name: zephyrproject-rtos
      url-base: https://github.com/zephyrproject-rtos

  projects:
    - name: zephyr
      remote: zephyrproject-rtos
      revision: main
      import:
        name-allowlist:
          - cmsis
          - hal_nordic
          - segger
          - tinycrypt

In that example, west init and update will only take a few projects instead of everything.

The Forest topology

The forest topology is what you use when you are developing multiple modules and applications and you want to manage them all with the freedom of change them independently each other.

In this case you’ll put the manifest in a special folder called manifest-repo and there you’ll list all your dependencies:

west-workspace/
├── app1/               # .git/ project
│             ├── CMakeLists.txt
│             ├── prj.conf
│             └── src/
│                 └── main.c
├── app2/               # .git/ project
│             ├── CMakeLists.txt
│             ├── prj.conf
│             └── src/
│                 └── main.c
├── manifest-repo/      # .git/ never modified by west
│             └── west.yml        # main manifest with optional import(s) and override(s)
├── modules/
│             └── lib/
│                 └── zcbor/      # .git/ project from either the main manifest or
│                       #       from some import
│
└── zephyr/             # .git/ project
    └── west.yml        # This can be partially imported with lower precedence or ignored.
                        # Only the 'manifest-rev' version can be imported.

app1 and app2 are two drivers/modules that are independent each other. You then list them into the west.yml under the manifest-repo:

manifest:
  remotes:
    - name: zephyrproject-rtos
      url-base: https://github.com/zephyrproject-rtos
    - name: your-git-server
      url-base: https://git.example.com/your-company
  defaults:
    remote: your-git-server
  projects:
    - name: zephyr
      remote: zephyrproject-rtos
      revision: v2.5.0
      import: true
    - name: app1
      revision: SOME_SHA_OR_BRANCH_OR_TAG
    - name: app2
      revision: ANOTHER_SHA_OR_BRANCH_OR_TAG
  self:
    path: manifest-repo

How to choose

The first topology is what you are going to use for one or more application independent each other with nothing that must be compiled as a module. In this case, you don’t want to control the manifest, so you rely on the default one.

The second topology is what you want to use when you have one main application that controls the manifest as well. For example, if your application has dependent modules, you certainly want to keep this dependency inside the application source code as well. So you are going to use this model.

The third topology is for very complex projects that have multiple modules under your control, but none is more important than the other, so the manifest is managed independently. For example, if you are going to keep multiple sources for drivers, you want to use this model.

Initializing and updating

When you have put the manifest in the proper folder, either in the app folder (second topology) or in the manifest folder (third topology), you initialize the workspace:

$ west init -l

This will initialize from the local folder, create a .west folder in the root of the workspace. You can also initialize from the manifest repository (or the project repository if it contains a manifest), just use the -m and -mr options in west.

The make an update:

$ west update