本文以实战视角梳理 Yocto 的核心概念与高频用法,适合做日常速查。示例以 RK3588 + scarthgap 分支为背景,但概念通用。

名词辨析

刚入门最容易被这几个词绕晕,一句话区分:

  • BitBake:构建引擎(任务调度器),负责解析配方、解决依赖、执行编译。本身不含任何软件包。类比 make,但功能强大得多。
  • **OpenEmbedded (OE)**:一套构建框架和元数据集合(大量配方、类库)。BitBake 是它的执行器。
  • Poky:Yocto 官方提供的参考发行版 + 集成环境,把 BitBake、OE-Core、参考配置打包在一起,开箱即用。
  • Yocto Project:一个伞形项目/品牌,统筹上述组件、发布周期(如 kirkstonescarthgap)、合规与工具。

通俗理解:

名称 角色 类比
BitBake 构建引擎 make / 编译调度器
OpenEmbedded 元数据 + 类库框架 标准库 + 构建规则集
Poky 参考集成环境 脚手架 / 发行版模板
Yocto Project 统筹品牌与发布 基金会 / 总指挥

核心概念

Layer 与 meta-*

Layer 是一组配方和配置的集合容器,按功能或来源分组。meta- 开头的目录就是 layer 的实物形态,二者基本同义。

Layer 不是软件本身,而是"一批配方 + 配置"的归类。按职责通常分三类:

  • BSP 层:硬件相关(u-boot、kernel、设备树)。例如 meta-rockchip
  • 发行版层(Distro):定义"这个 OS 长什么样"(init 系统、包管理、特性开关)。
  • 软件层:提供应用与库的配方。例如 meta-openembeddedpoky/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 本地构建配置:MACHINEDISTRO、并行度、镜像类型、额外安装包等
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-cachedownloads 建议长期保留并跨工程复用,能极大缩短构建时间。


常用命令

环境初始化

# 进入构建环境(会 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.chello.service),然后:

bitbake hello
# 或加入镜像
echo 'IMAGE_INSTALL:append = " hello"' >> conf/local.conf
bitbake my-image

排错套路

  1. 变量值不对? bitbake -e <recipe> | grep ^VAR= 看最终展开值。
  2. 编译报错? 直接看 tmp/work/.../temp/log.do_compile,比终端输出全。
  3. 想手动复现编译? bitbake <recipe> -c devshell,进交叉环境手动敲命令。
  4. 改了没生效? 可能命中 sstate 缓存,bitbake -c cleansstate <recipe> 后重来。
  5. 依赖关系乱? bitbake -g <recipe> 生成 .dot 图,或用 oe-depends-dot 分析。
  6. 找文件归属? oe-pkgdata-util find-path /path/to/file

小结

  • meta-* = layer:按职责分组的容器(BSP / Distro / 软件)。
  • recipe(.bb:单个包怎么做出来的说明书;.bbappend 用来微调别人的包。
  • BitBake:解析配方、解决依赖、按任务流水线构建。
  • 三条铁律:改上游用 bbappend善用 bitbake -e 看变量保留 sstate/downloads 提速

掌握这些,就能看懂任意 Yocto 工程结构,并自己动手加层、写包、定制镜像。