Yocto 常用概念与使用说明
本文以实战视角梳理 Yocto 的核心概念与高频用法,适合做日常速查。示例以 RK3588 + scarthgap 分支为背景,但概念通用。
名词辨析
刚入门最容易被这几个词绕晕,一句话区分:
- BitBake:构建引擎(任务调度器),负责解析配方、解决依赖、执行编译。本身不含任何软件包。类比
make,但功能强大得多。 - **OpenEmbedded (OE)**:一套构建框架和元数据集合(大量配方、类库)。BitBake 是它的执行器。
- Poky:Yocto 官方提供的参考发行版 + 集成环境,把 BitBake、OE-Core、参考配置打包在一起,开箱即用。
- Yocto Project:一个伞形项目/品牌,统筹上述组件、发布周期(如
kirkstone、scarthgap)、合规与工具。
通俗理解:
| 名称 | 角色 | 类比 |
|---|---|---|
| BitBake | 构建引擎 | make / 编译调度器 |
| OpenEmbedded | 元数据 + 类库框架 | 标准库 + 构建规则集 |
| Poky | 参考集成环境 | 脚手架 / 发行版模板 |
| Yocto Project | 统筹品牌与发布 | 基金会 / 总指挥 |
核心概念
Layer 与 meta-*
Layer 是一组配方和配置的集合容器,按功能或来源分组。meta- 开头的目录就是 layer 的实物形态,二者基本同义。
Layer 不是软件本身,而是"一批配方 + 配置"的归类。按职责通常分三类:
- BSP 层:硬件相关(u-boot、kernel、设备树)。例如
meta-rockchip。 - 发行版层(Distro):定义"这个 OS 长什么样"(init 系统、包管理、特性开关)。
- 软件层:提供应用与库的配方。例如
meta-openembedded、poky/meta(OE-Core,基础包如 glib、systemd、glibc)。
为什么分层? 解耦与复用:升级 BSP 不影响业务层;自己的东西单独放一层,不污染上游,便于维护和移植。
每个 layer 必须有 conf/layer.conf 来声明自己,关键字段:
# 收集本层配方的路径规则
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
${LAYERDIR}/recipes-*/*/*.bbappend"
# 本层的唯一标识
BBFILE_COLLECTIONS += "meta-stark"
BBFILE_PATTERN_meta-stark = "^${LAYERDIR}/"
# 层优先级:多个层修改同一个包时,数字大的优先
BBFILE_PRIORITY_meta-stark = "10"
# 本层依赖的其他层
LAYERDEPENDS_meta-stark = "core rockchip"
# 兼容的 Yocto 发布分支
LAYERSERIES_COMPAT_meta-stark = "scarthgap"
一个典型的 layer 目录结构:
meta-mylayer/
├── conf/
│ └── layer.conf # 层声明(必须)
├── recipes-core/ # 配方按主题分目录
│ └── images/
│ └── my-image.bb
├── recipes-myapp/
│ └── myapp/
│ ├── myapp_1.0.bb
│ └── files/ # 补丁、配置等本地文件
└── classes/ # 自定义 .bbclass(可选)
Recipe
一个 .bb 文件 = 一个软件包的完整构建说明书,回答四个问题:从哪拿源码、依赖谁、怎么编译、装到哪。
SUMMARY = "My example daemon"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxxxxxx"
# 源码来源(git / http / 本地 file://)
SRC_URI = "git://example.com/myapp.git;branch=master;protocol=https"
SRCREV = "a1b2c3d4..."
# 复用通用构建逻辑(这里套用 cmake 流程)
inherit cmake
# 编译期依赖(其他 recipe)
DEPENDS = "glib-2.0 openssl"
# 运行期依赖(装进 rootfs 时一起带上)
RDEPENDS:${PN} = "bash"
.bb / .bbappend / .inc 区别:
.bb:定义一个新包。.bbappend:不改原配方,向已有配方追加/覆盖配置(打补丁、加文件、改开关)。文件名要与目标配方同名,可用%通配版本,如bluez5_%.bbappend。- **
.inc**:可被require/include复用的公共片段,常用于把多版本配方的共性抽出来。
黄金法则:绝不直接修改上游 layer(poky、meta-openembedded)里的配方,需要改就在自己的 layer 里用
.bbappend。
配置文件
构建配置集中在 build/conf/ 下:
| 文件 | 作用 |
|---|---|
bblayers.conf |
启用哪些 layer(BBLAYERS 变量列出 layer 路径) |
local.conf |
本地构建配置:MACHINE、DISTRO、并行度、镜像类型、额外安装包等 |
conf/distro/xxx.conf |
发行版定义:init 系统、包格式、DISTRO_FEATURES |
conf/machine/xxx.conf |
机器定义:CPU 架构、内核设备树、u-boot 配置 |
几个最常调的变量:
MACHINE ?= "stark-rk3588" # 目标硬件
DISTRO ?= "stark-os" # 发行版
PACKAGE_CLASSES ?= "package_deb" # 包格式:deb / rpm / ipk
BB_NUMBER_THREADS ?= "8" # BitBake 并行任务数
PARALLEL_MAKE ?= "-j 8" # 每个 make 的并行度
IMAGE_INSTALL:append = " myapp" # 往镜像里追加包
EXTRA_IMAGE_FEATURES ?= "debug-tweaks" # 例如允许空密码 root 登录
构建流程
每个配方的构建被拆成一串任务(task),按顺序执行:
graph LR
A[do_fetch 拉源码] --> B[do_unpack 解压]
B --> C[do_patch 打补丁]
C --> D[do_configure 配置]
D --> E[do_compile 编译]
E --> F[do_install 安装到临时目录]
F --> G[do_package 拆分打包]
G --> H[do_package_write_deb 生成包]
镜像级别再多两步:do_rootfs(组装根文件系统)→ do_image(生成 .wic / .ext4 等)。
可以单独跑某个任务(调试常用):
bitbake -c compile myapp # 只执行到编译
bitbake -c cleanall myapp # 清掉源码、状态、产物
bitbake -c listtasks myapp # 列出所有可用任务
目录结构
构建产物都在 build/ 下:
| 路径 | 内容 |
|---|---|
downloads/ |
所有下载的源码包(DL_DIR),可跨工程共享 |
sstate-cache/ |
共享状态缓存(SSTATE_DIR),命中可跳过重新编译,加速利器 |
tmp/work/<arch>/<pn>/<ver>/ |
每个包的工作目录(源码、编译产物、日志) |
tmp/deploy/images/<machine>/ |
最终镜像、内核、设备树、.manifest |
| `tmp/deploy/deb | rpm |
tmp/work/.../temp/log.do_* |
各任务的详细日志(排错第一现场) |
sstate-cache和downloads建议长期保留并跨工程复用,能极大缩短构建时间。
常用命令
环境初始化
# 进入构建环境(会 cd 到 build/ 并导出环境变量)
source oe-init-build-env build
BitBake 构建
bitbake core-image-minimal # 构建镜像
bitbake myapp # 只构建某个包
bitbake -k myapp # keep going:出错也尽量继续
bitbake -c menuconfig virtual/kernel # 配置内核
bitbake -e myapp # 打印某配方最终展开的所有变量(排查变量值神器)
bitbake -g myapp # 生成依赖关系图(task-depends.dot)
bitbake-layers show-layers # 列出已启用的 layer
bitbake-layers show-recipes 'glib*' # 查某个配方在哪些层、什么版本
bitbake-layers add-layer ../meta-foo # 添加 layer 到 bblayers.conf
bitbake-layers create-layer ../meta-foo # 生成一个新 layer 骨架
devtool
devtool add myapp <源码路径或git地址> # 从源码自动生成配方并加入工作区
devtool modify myapp # 把已有配方的源码检出,方便改代码/打补丁
devtool build myapp # 构建工作区里的包
devtool deploy-target myapp root@board # 直接部署到运行中的板子(快速迭代)
devtool finish myapp ../meta-mylayer # 把改动(含补丁)固化回某个 layer
查询与排错
# 查某文件由哪个包提供
oe-pkgdata-util find-path /usr/bin/foo
# 查某包安装了哪些文件
oe-pkgdata-util list-pkg-files myapp
# 进入某配方的交叉编译 shell 环境
bitbake myapp -c devshell
# 看某任务日志
less tmp/work/<arch>/myapp/<ver>/temp/log.do_compile
高频变量
| 变量 | 含义 |
|---|---|
PN / PV / PR |
包名 / 版本 / 修订号 |
S |
源码解压后的工作目录 |
B |
编译目录(out-of-tree 构建时与 S 不同) |
D |
do_install 的安装目标根(伪根) |
WORKDIR |
该配方的工作根目录 |
SRC_URI |
源码与本地文件来源 |
SRCREV |
git 源码的提交哈希 |
DEPENDS |
编译期依赖(其他配方名) |
RDEPENDS:${PN} |
运行期依赖 |
PROVIDES / RPROVIDES |
该配方额外提供的(虚拟)名字 |
FILES:${PN} |
哪些文件归入主包 |
inherit |
继承的 .bbclass(如 cmake、autotools、systemd) |
IMAGE_INSTALL |
镜像要安装的包列表 |
DISTRO_FEATURES |
发行版级特性(wifi、bluetooth、systemd 等) |
MACHINE_FEATURES |
硬件级特性 |
变量覆盖语法
FOO = "a" # 直接赋值
FOO ?= "a" # 弱赋值(未定义才生效)
FOO:append = " b" # 追加(注意自己留空格)
FOO:prepend = "b " # 前插
FOO:remove = "b" # 移除
FOO:append:rk3588 = " c" # 仅当 OVERRIDES 含 rk3588 时追加(条件覆盖)
:append/:remove是 OE 的 override 语法(旧版本用_append)。:<override>可按 MACHINE、DISTRO、${PN}等条件生效,是 Yocto 灵活性的核心。
自定义包示例
目录:meta-mylayer/recipes-example/hello/hello_1.0.bb
SUMMARY = "Hello world daemon"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://hello.c \
file://hello.service"
S = "${WORKDIR}"
inherit systemd
SYSTEMD_SERVICE:${PN} = "hello.service"
do_compile() {
${CC} ${CFLAGS} ${LDFLAGS} hello.c -o hello
}
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}/hello
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/hello.service ${D}${systemd_system_unitdir}/
}
把本地文件放到 recipes-example/hello/files/(hello.c、hello.service),然后:
bitbake hello
# 或加入镜像
echo 'IMAGE_INSTALL:append = " hello"' >> conf/local.conf
bitbake my-image
排错套路
- 变量值不对?
bitbake -e <recipe> | grep ^VAR=看最终展开值。 - 编译报错? 直接看
tmp/work/.../temp/log.do_compile,比终端输出全。 - 想手动复现编译?
bitbake <recipe> -c devshell,进交叉环境手动敲命令。 - 改了没生效? 可能命中 sstate 缓存,
bitbake -c cleansstate <recipe>后重来。 - 依赖关系乱?
bitbake -g <recipe>生成.dot图,或用oe-depends-dot分析。 - 找文件归属?
oe-pkgdata-util find-path /path/to/file。
小结
- meta-* = layer:按职责分组的容器(BSP / Distro / 软件)。
- recipe(
.bb):单个包怎么做出来的说明书;.bbappend用来微调别人的包。 - BitBake:解析配方、解决依赖、按任务流水线构建。
- 三条铁律:改上游用 bbappend、善用
bitbake -e看变量、保留 sstate/downloads 提速。
掌握这些,就能看懂任意 Yocto 工程结构,并自己动手加层、写包、定制镜像。
