前言
上一篇把环境安装和点灯简单过了一遍,这一篇继续补 Zephyr 开发里一个绕不开的话题,也就是 west 和 manifest。
刚开始接触 Zephyr 的时候,很容易把它们看成“又一个构建命令”和“又一个配置文件”。但实际用下来会发现,它们更像是 Zephyr 工作流的骨架。
如果不理解这两个东西,后面在拉取源码、管理依赖、切换版本、构建用户工程的时候就很容易迷糊。
west 是什么
官方文档把 west 描述为 Zephyr 的一个 meta-tool,也就是“元工具”。
我的理解是:它不是单纯替代 cmake 或 ninja 的命令,而是站在更高一层,把 多仓库管理、工程构建、烧录、调试 这些事情统一到一个命令行入口中。
也就是说,在 Zephyr 的日常开发中,west 主要负责下面几类事情:
- 初始化工作空间
- 根据
manifest拉取和更新多个 Git 仓库 - 调用底层构建系统去编译工程
- 调用 runner 去烧录和调试目标板
- 提供一些查询、配置和批量操作能力
它的基本调用形式和 git 很像:
west [common-opts] <command> [opts] <args>
比如:
west init
west update
west build -b stm32_min_dev@blue samples/basic/blinky
west flash
west 常见指令与作用
west 的命令不少,不过日常最常碰到的其实就是下面这些。
west init
作用是 初始化 west 工作空间。
最常见的情况,就是先创建一个工作区,把 manifest 仓库放进去:
west init ~/zephyrproject
执行之后,west 会创建工作空间根目录,并生成 .west/ 配置目录。
如果使用官方默认仓库,通常会得到类似下面这样的结构:
zephyrproject/
├── .west/
│ └── config
└── zephyr/
└── west.yml
这里先只有工作空间和 manifest 仓库,真正的其它模块仓库还要靠 west update 去拉。
west update
作用是 根据 manifest 下载或更新项目依赖。
west update
这个命令会读取 west.yml 中的项目列表,然后逐个执行下面这些动作:
- 本地没有仓库就克隆
- 本地有仓库就抓取对应 revision
- 把每个项目切到 manifest 指定的版本
所以从直觉上看,west init 是“搭骨架”,west update 是“把肉补齐”。
west list
作用是 列出当前工作空间中由 west 管理的项目。
west list
这个命令很适合在你忘了工作空间里到底有哪些模块时快速确认。
west status
作用是 查看各个仓库的状态。
west status
它有点像对整个工作空间批量执行 git status。
west diff
作用是 查看各个仓库的本地修改。
west diff
这个命令在你改了多个模块,又忘了自己动过哪些地方时很好用。
west forall
作用是 在多个项目仓库里批量执行命令。
west forall -c "git status --short"
这类命令适合做统一检查,或者批量跑一些 Git 命令。
west manifest
作用是 管理和检查 manifest。
例如:
west manifest --resolve
west manifest --freeze
其中:
--resolve可以把 import 之后的最终 manifest 展开出来--freeze可以把各项目 revision 冻结成具体提交号,方便复现
west config
作用是 读取或设置 west 配置项。
west config -l
west config manifest.path zephyr
例如 manifest.path、manifest.file 这些配置都会影响 west 去哪里找 manifest。
west topdir
作用是 输出当前工作空间根目录。
west topdir
在脚本里这个命令很方便,因为它能让你快速确认自己是不是处于某个 west workspace 之中。
west build
作用是 构建 Zephyr 工程。
west build -b stm32_min_dev@blue samples/basic/blinky
它本质上是在帮你组织 CMake/Ninja 相关参数,所以用户一般不需要直接手写一长串底层构建命令。
west flash
作用是 把构建产物烧录到目标板。
west flash
前提当然是工程已经成功构建,并且对应板卡的 runner 配置可用。
west debug
作用是 启动调试流程。
west debug
这个命令会结合目标板支持的调试方式,把 GDB、OpenOCD、J-Link 之类的流程串起来。
其它说明
除了上面这些,west 还有 compare、grep、help 等命令。
另外,一些文章里还会提到 west attach、west boards 这类命令。它们通常属于 Zephyr 提供的扩展命令,是否可用与具体环境、版本和扩展命令集有关。实际使用时最稳妥的方法还是直接执行:
west --help
west <command> -h
manifest 是什么
manifest 可以理解成 west 工作空间的总清单。
它告诉 west:
- 需要管理哪些 Git 仓库
- 这些仓库从哪里拉
- 每个仓库要放到工作空间的哪个路径下
- 每个仓库应该切到哪个 revision
- 是否要继续导入其它 manifest
在 Zephyr 里,manifest 文件通常就叫 west.yml。
所以你也可以简单把它理解为:
west.yml 决定工作空间长什么样,west update 负责把这个样子真正落到本地。
manifest 的基本格式
manifest 本质上是一个 YAML 文件,顶层一般是 manifest:。
不过如果只看那种“教学版极简例子”,实际遇到真正的 west.yml 时还是容易发懵。
所以这里我直接结合一个真实的上游 Zephyr west.yml 来理解。它开头的核心结构大致可以概括成下面这样:
manifest:
defaults:
remote: upstream
remotes:
- name: upstream
url-base: https://github.com/zephyrproject-rtos
- name: babblesim
url-base: https://github.com/BabbleSim
group-filter: [-babblesim, -optional, -testing]
projects:
- name: acpica
revision: 8d24867bc9c9d81c81eeac59391cda59333affd4
path: modules/lib/acpica
- name: hal_espressif
revision: 15789ec576bdb3397c6fc39542b0d9b534d98d2d
path: modules/hal/espressif
west-commands: west/west-commands.yml
groups:
- hal
- name: babblesim_base
remote: babblesim
repo-path: base
path: tools/bsim/components
revision: 1cce925c72805cfdbb6341ca8c0755c7d42699d3
groups:
- babblesim
self:
path: zephyr
west-commands: scripts/west-commands.yml
import: submanifests
这个例子比“教学版”更接近真实情况,因为它已经把 Zephyr 实际维护多个仓库、多个分组、扩展 west 命令的方式体现出来了。
上面这些字段可以这样理解。
remotes
定义远程仓库地址前缀。
比如:
remotes:
- name: upstream
url-base: https://github.com/zephyrproject-rtos
- name: babblesim
url-base: https://github.com/BabbleSim
这表示当前 manifest 里至少定义了两个远程来源:
upstream:Zephyr 官方仓库组织babblesim:BabbleSim 相关仓库组织
后面 projects 中如果写了 remote: babblesim,那就表示这个项目默认从 https://github.com/BabbleSim 下面去找。
defaults
定义默认值。
在这个真实示例里写的是:
defaults:
remote: upstream
意思是:如果某个项目没有单独写 remote,那就默认从 upstream 这个远程来源拉。
所以像下面这种项目:
- name: acpica
revision: 8d24867bc9c9d81c81eeac59391cda59333affd4
path: modules/lib/acpica
虽然没有显式写 remote: upstream,但它依然会继承 defaults 的设置。
group-filter
这个字段在很多入门文章里容易被忽略,但在真实工程里很有用。
这个示例中是:
group-filter: [-babblesim, -optional, -testing]
它的意思可以粗略理解成:
默认不启用 babblesim、optional、testing 这些组里的项目。
这样做的好处是:
- 避免一上来就把所有可选仓库都拉下来
- 减少工作空间体积
- 降低普通开发场景下的依赖复杂度
所以 group-filter 本质上是在控制“哪些项目默认参与当前工作空间”。
projects
这是最核心的部分,用来列出需要管理的项目仓库。
常见字段有:
name:项目名remote或url:仓库来源repo-path:远程仓库的实际路径名revision:分支、tag 或具体提交path:项目在本地工作空间里的路径import:是否继续导入其它 manifestwest-commands:该项目是否提供额外 west 命令groups:该项目所属分组
例如这个示例里有一个很简单的项目:
- name: acpica
revision: 8d24867bc9c9d81c81eeac59391cda59333affd4
path: modules/lib/acpica
这表示:
- 项目名是
acpica - 它没有单独写
remote,所以继承defaults.remote: upstream - 它会被拉到工作空间中的
modules/lib/acpica - 它会被固定到指定提交
8d24867...
这里的 revision 不是 main,而是一个具体 commit id。
这说明 Zephyr 更关注 可复现性,也就是希望大家同步下来的是一组确定版本的依赖,而不是每次都跟着分支头乱跑。
再看一个更复杂一点的例子:
- name: babblesim_base
remote: babblesim
repo-path: base
path: tools/bsim/components
revision: 1cce925c72805cfdbb6341ca8c0755c7d42699d3
groups:
- babblesim
这段可以这样理解:
- 这个项目逻辑上的名字叫
babblesim_base - 它来自
babblesim这个 remote - 远程仓库真实路径名不是
babblesim_base,而是base - 本地放到
tools/bsim/components - 它属于
babblesim组
这里的 repo-path 很关键。
如果没有它,west 往往会默认用 name 去拼远程仓库地址;而有了它,就说明“显示名”和“远程仓库名”不一定相同。
再看一个和 west 扩展命令相关的例子:
- name: hal_espressif
revision: 15789ec576bdb3397c6fc39542b0d9b534d98d2d
path: modules/hal/espressif
west-commands: west/west-commands.yml
groups:
- hal
这说明 hal_espressif 这个项目除了提供代码,还额外提供了一份 west 命令描述文件。
也就是说,某些 west 命令并不一定全部内建在主程序里,也可能由某个项目通过 west-commands.yml 注册进来。
self
self 描述的是 manifest 仓库自己。
这个示例末尾这一段很典型:
self:
path: zephyr
west-commands: scripts/west-commands.yml
import: submanifests
其中:
path: zephyr表示 manifest 仓库自己就在工作空间里的zephyr/目录west-commands: scripts/west-commands.yml表示 Zephyr 自己也提供了一批扩展 west 命令import: submanifests表示还要继续导入submanifests里的其它 manifest 内容
这一点非常重要,因为它解释了为什么你在实际环境里能用到 west boards、west attach、west sdk 这类命令。
查看对应的 scripts/west-commands.yml 就会发现,Zephyr 在里面注册了很多扩展命令,比如:
boardsbuildflashdebugattachsdk
所以更准确地说:
west本体提供基础框架- Zephyr 项目再通过
west-commands.yml把自己的一批命令挂进去
这样一来,west 才会真正变成一个覆盖构建、烧录、调试、板卡查询等工作流的统一入口
version
可选字段,用于声明 manifest 文件格式所依赖的 west 版本特性。
例如:
version: "0.10"
它的作用更偏向兼容性检查,一般在简单入门阶段不一定马上用到。
如何理解一个真实的 Zephyr west.yml
如果只想抓住主线,我觉得可以按下面这个顺序去读:
先看 self
先确认 manifest 仓库自己是谁、放在哪里。
在这个示例里:
self:
path: zephyr
这说明当前这个 west.yml 就属于 zephyr 仓库本身。
也就是说,你现在用的是 Zephyr 官方默认工作流,而不是“我自己的 app 仓库作为 manifest 仓库”的那种玩法。
再看 defaults 和 remotes
这两块决定“默认从哪里拉仓库”。
在这个示例里,默认 remote 是 upstream,而 upstream 对应的是 https://github.com/zephyrproject-rtos。
所以大部分没特别声明来源的模块,本质上都是从 Zephyr 官方维护的仓库组织里同步下来的。
再看 group-filter
这决定“默认启用哪些依赖,默认跳过哪些依赖”。
你这里把 babblesim、optional、testing 默认排除了,所以这份工作空间更偏向普通开发使用,而不是一次性把测试和仿真生态全量拉满。
最后看 projects
这里决定“这个工作空间究竟包含哪些具体模块”。
你会发现里面有很多仓库路径:
modules/hal/...modules/lib/...modules/fs/...modules/crypto/...tools/...
这就说明 Zephyr 并不是“一个大仓库包打天下”,而是通过 manifest 把 HAL、库、工具、测试相关仓库组织成一个统一工作空间。
所以当我们执行 west update 时,本质上不是“更新 Zephyr 一个仓库”,而是:
- 读取
zephyr/west.yml - 按里面的
projects列表去同步很多子仓库 - 把它们放到
modules/、tools/、bootloader/等位置 - 并切到各自指定的 revision
这样理解以后,再去看工作空间目录结构就不会觉得乱了,因为这些目录并不是随便摆出来的,而是 manifest 明确规定好的。
工作空间、west、manifest、用户项目之间的关系
这一块其实是最容易绕晕的。
我自己的理解可以总结成一句话:
工作空间是容器,manifest 是清单,west 是管理者,用户项目是这个容器中的一个实际工程。
可以先看一个简化关系图:
west workspace/
├── .west/ <- west 的工作空间配置
├── manifest repo/ <- 保存 west.yml 的仓库
├── zephyr/ <- Zephyr 内核源码
├── modules/... <- 各类模块仓库
├── bootloader/... <- 例如 mcuboot
└── app/ <- 用户自己的应用工程
它们之间可以这样理解。
工作空间(workspace)
工作空间就是最外层的目录。
它把 .west/、manifest 仓库、Zephyr 源码、模块仓库、用户应用工程都收纳到一起。
只要一个目录里有 .west/,west 就能把它识别成自己的工作空间。
west
west 是操作这个工作空间的工具。
它不负责“发明”依赖关系,而是 读取 manifest,再去操作 workspace 里的各个仓库。
所以可以理解成:
manifest决定“应该有什么”west负责“把它变成现实”
manifest
manifest 是工作空间的结构说明书。
它描述了项目仓库集合以及它们的版本关系。
当你执行 west update 时,west 就按照这个说明书把每个仓库拉到指定位置、指定 revision。
用户项目
用户项目就是你自己写业务代码、应用代码的地方。
它通常也是工作空间中的一个目录,可以是:
- 工作空间里单独的
app/ - 某个独立仓库
- 甚至就是 manifest 仓库本身的一部分
你平时最常做的事情,一般就是在用户项目里写代码,然后让 west build 去结合工作空间中的 Zephyr 和各模块一起完成构建。
一个更贴近实际的理解
如果把它们类比成一次做饭:
- 工作空间像厨房
- manifest 像购物清单和配方
- west 像负责采购和摆放食材的人
- 用户项目像你真正要做的那道菜
没有工作空间,东西没地方放。
没有 manifest,west 不知道该拿哪些仓库。
没有 west,你就得自己手动 clone 一堆仓库、对齐版本、处理路径。
没有用户项目,那这些依赖拉下来也只是“环境准备好了”,还没有真正的应用要构建。
小结
学 Zephyr 的早期阶段,west 和 manifest 基本可以先抓住下面这几个点:
west是 Zephyr 的统一入口,不只是构建命令manifest是一个 YAML 清单,用来描述多仓库依赖关系west init负责初始化工作空间west update负责按 manifest 同步各项目仓库- 用户项目通常位于工作空间中,并依赖 workspace 里的 Zephyr 与模块一起构建
把这套关系想明白之后,后面再看 Zephyr 的模块组织、版本管理和自定义工程结构,就会顺很多。
参考资料
- Developing with Zephyr » West (Zephyr’s meta-tool)
- Developing with Zephyr » West » Basics
- Developing with Zephyr » West » Built-in commands
- Developing with Zephyr » West » Workspaces
- Developing with Zephyr » West » West Manifests
- Zephyr RTOS 学习系统 » west 工具核心用法