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.