Using west can be a lot of fun, until you hit an edge.

Working with the Zephyr RTOS, the use of west isn’t essential, but it does make a lot of sense to have the mechanisms of building abstracted away nicely by some pretty tooling.

Besides, west is pretty useful as a replacement for the repo tool on projects beyond Zephyr. Both work rather similar, with one being using YAML parsing and the other using an XML parser. I prefer to look at YAML.

The biggest downfall to both of these tools I find is that users do not often update the versions. At least repo has a message to warn that you are using an old version, but even then it is unlikely they will update. Especially if installed via their Linux package manager and not downloaded directly from the source.

Note, all behaviors in here are found in west version 0.9.0 so later versions may behave differently.

Create A Basic Manifest

Starting with a basic project, we create a manifest file that looks like:

#
# module-a/west.yml:
#
projects:
  - name: group_module_b
    remote: github
    repo-path: module_b
  - name: group_module_c
    remote: github
    repo-path: module_c
  - name: single_module_d
    remote: github
    repo-path: module_d
  - name: single_module_e
    remote: github
    repo-path: module_e

self:
  path: module-a

This project defines itself as module-a and pulls a few additional projects in to create it’s world. Pretty standard stuff.

Creating A Super West

Now let’s assume you have a project where multiples of these modules are being developed independent of each other but all need to work together. At some point you will arrive to the standard computer science solution of creating another layer of abstraction. In the case of west, that layer of abstraction is creating a parent repo with it’s own west.yml to pull together the multiple modules and create a world.

That project’s manifest would looks remarkably like:

#
# project-a/west.yml:
#
projects:
  - name: group_module_a
    remote: github
    repo-path: module_a
    revision: aaaaaaa
    import: true
  - name: group_module_b
    remote: github
    repo-path: module_b
    revision: bbbbbbb
    import: true
  - name: group_module_c
    remote: github
    repo-path: module_c
    revision: ccccccc
    import: true

self:
  path: project-a

When we re-constitute the world using project-a/west.yml everything works fine with the name resolution of project-a. Summarized poorly as self > projects > project imports. Note that in project-a we do not concern ourselves with details of what the modules themselves need to build their world. Thanks to the name resolution rules and the use of the import flag, project-a allows those module projects to define what their borders look like without worrying about name collision.

Yes, I realize the revisions don’t exist but they are not the point of focus here other than to show that they are all some different value.

Having a parent repo like this opens up additional details that can be applied to project-a. For example, project-a can be used to track module packages that all work together. We can call it something that went through some kind of functional testing and reached a verified state. You may even add a tag to each so you can track when the decision that is made.

Introducing First Dependency Chain

At this point you may turn around and say something like “hey modules testing requires knowing the specific command set used by the project and we want to always use the same command set.” Seems reasonable enough. Now you add your first circular dependency to the manifests, by having the modules point to a specific tag of project-a. For example, the manifest for module-a will now look like:

#
# module-a/west.yml:
#
projects:
  - name: group_module_b
    remote: github
    repo-path: module_b
  - name: group_module_c
    remote: github
    repo-path: module_c
  - name: single_module_d
    remote: github
    repo-path: module_d
  - name: single_module_e
    remote: github
    repo-path: module_e
+  - name: project_a
+    remote: github
+    repo-path: project_a
+    revision: SOME_TAG

self:
  path: module-a

Now all developers of modules can run the tests exactly like the project-a does and it will be updated as a process when project-a does. Pretty neat!

And thanks to the west name resolution, we do not break the ability to re-create the world in any of the modules or in the project manifests.

Introducing Second Dependency

As the days go by, you may find yourself saying “hey module-a uses module-b and module-c, but the developers of module-a only want to use those modules that have been validated”. Again, seems reasonable enough. The west tooling even has the ability to do this through the name-allowlist (formerly name-whitelist). So let us adapt our module-a/west.yml to pull in our test product combinations. Now we can modify the module manifests to do this:

#
# module-a/west.yml:
#
projects:
-  - name: group_module_b
-    remote: github
-    repo-path: module_b
-  - name: group_module_c
-    remote: github
-    repo-path: module_c
-  - name: project-a
-    remote: github
+    repo-path: project-a
+    import:
+      name-allowlist:
+        - group_module_b
+        - group_module_c
+    revision: SOME_TAG
  - name: single_module_d
    remote: github
    repo-path: module_d
  - name: single_module_e
    remote: github
    repo-path: module_e

self:
  path: module-a

Now the modules can all pull down a validated combination of each other and work independently. There are some challenges with this approach, such as what happens with API breaking changes, but those are beyond the scope of this discussion.

Broken World

Now comes the interesting part.

While the modules are all happy, the project-a is no longer. Clone and setup an instance of project-a, once you hit the step of west udpate we see something interesting in the process. Specifically we see that the initial clone of project-a gets switched out to being that of the first listed modules defined version.

This is not what was expected and violates the poorly summarized rules of west resolution mentioned earlier as the project clearly identifies itself as project-a and the modules consume it as project-a, so it is not in the name problem. The problem comes from the name-allowlist processing, which throws the entire parsing off.

Work-Around

This can be worked around by modifying the project-a manifest to ignore the entries that would take over ownership of the project-a manifest. The west tooling even provides the mechanism with name-blocklist (formerly name-blacklist). Modifying the project-a manifest to look like:

#
# project-a/west.yml:
#
projects:
  - name: group_module_a
    remote: github
    repo-path: module_a
    revision: aaaaaaa
-    import: true
+    import:
+      name-blocklist:
+        - project-a
  - name: group_module_b
    remote: github
    repo-path: module_b
    revision: aaaaaaa
-    import: true
+    import:
+      name-blocklist:
+        - project-a
  - name: group_module_c
    remote: github
    repo-path: module_c
    revision: aaaaaaa
-    import: true
+    import:
+      name-blocklist:
+        - project-a

self:
  path: project-a

This works really well to enable project-a to re-build the world. It does add additional effort on the upstream consumers of the modules manifest files to be aware of this change in the name resolution process.

Written on March 10, 2021