4. 配方(Recipe)基础:配方文件结构、关键变量与版本管理
Yocto 里的配方,说白了就是一份「菜谱」。它告诉 BitBake 从哪里下载源码、怎么打补丁、怎么编译、最后把哪些文件装进根文件系统。我刚开始接触 Yocto 时,觉得配方文件就是一堆变量赋值,后来踩了几个坑才明白——配方的结构设计和变量使用习惯,直接决定了项目的可维护性。
这一章我们就把配方的基础打牢。你理解了 PN、PV、PR、S、D 这些关键变量,后面写复杂配方才不会手忙脚乱。
4.1 配方文件结构:从外到内看透它
一个典型的配方文件长什么样?我们直接看一个实际例子:
# recipes-example/hello/hello_1.0.0.bb
SUMMARY = "A simple hello world program"
DESCRIPTION = "This recipe builds a hello world binary for embedded systems"
HOMEPAGE = "https://example.com/hello"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=..."
SRC_URI = "git://github.com/example/hello.git;branch=main"
SRCREV = "a1b2c3d4e5f6..."
S = "${WORKDIR}/git"
inherit autotools
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}
}
嗯,这里要注意:配方的核心结构分为三段——元数据区、源码获取区、构建安装区。我个人习惯把变量按功能分组,中间用空行隔开,这样别人看你的配方时一眼就能找到关键信息。
配方文件三段式结构:
- 元数据段:SUMMARY、DESCRIPTION、LICENSE、HOMEPAGE 等描述性信息
- 源码段:SRC_URI、SRCREV、S 等控制源码来源和位置的变量
- 构建段:inherit、do_configure、do_compile、do_install 等任务定义
你想想看,如果所有变量混在一起写,后期维护时找 LICENSE 都要翻半天。我在项目中见过一个 300 行的配方,变量散落各处,最后不得不重构——那滋味可不好受。
4.2 关键变量:PN、PV、PR、S、D
这五个变量是配方的「身份证」。理解它们,你就掌握了配方的命名和路径逻辑。
| 变量 | 全称 | 含义 | 典型值示例 |
|---|---|---|---|
| PN | Package Name | 配方名,从文件名自动解析 | hello |
| PV | Package Version | 软件版本号 | 1.0.0 |
| PR | Package Revision | 配方修订号,用于配方变更 | r0 |
| S | Source Directory | 解压后的源码目录 | ${WORKDIR}/git |
| D | Destination Directory | 安装阶段的临时根目录 | ${WORKDIR}/image |
4.2.1 PN:配方的名字
PN 是从配方文件名自动提取的。比如 hello_1.0.0.bb,PN 就是 hello。我曾经犯过一个低级错误:把配方文件名写成 hello-world_1.0.0.bb,结果 PN 变成了 hello-world,而我在其他配方里依赖的是 hello——编译直接报错。所以配方文件名就是 PN,改文件名等于改 PN,牵一发而动全身。
4.2.2 PV:版本号的艺术
PV 同样从文件名解析。但这里有个坑:版本号比较不是字符串比较,而是按段比较的。1.10.0 大于 1.9.0,因为 BitBake 会把版本号拆成数字段逐段比较。我建议你统一使用 主版本.次版本.补丁 的三段式,避免出现 1.0.0-beta 这种让版本比较器困惑的写法。
小技巧:如果上游版本号包含字母(如 1.0.0rc1),可以在配方里用 PV = "1.0.0+rc1" 来保证版本比较正确。加号后面的部分会被视为「预发布」标记。
4.2.3 PR:你的配方版本控制
PR 默认是 r0。什么时候需要改?当配方逻辑变了,但上游软件版本没变时。比如你修改了 do_install 里的安装路径,或者加了一个补丁,就应该把 PR 递增为 r1、r2……
我记得有一次,团队里有人改了配方的编译选项但忘了改 PR,结果其他同事的构建缓存没失效,导致旧二进制被复用——排查了半天才发现是 PR 没更新。从那以后,我要求团队每次修改配方逻辑必须递增 PR,并在提交信息里注明。
4.2.4 S 和 D:两个关键目录
S 是源码目录。默认值是 ${WORKDIR}/${PN}-${PV},但很多项目解压后目录名不匹配,需要手动设置。比如 Git 仓库克隆到 ${WORKDIR}/git,你就得写 S = "${WORKDIR}/git"。
D 是安装目标目录。所有 do_install 里 install 命令的目标路径都要以 ${D} 开头。注意:${D} 不是最终根文件系统,它只是一个临时 staging 目录。我曾经见过新手直接写 install -d /usr/bin——这会把文件装到构建主机的 /usr/bin 里,后果可想而知。
警告:永远不要在 do_install 里使用绝对路径(不以 ${D} 开头)。所有安装操作都必须通过 ${D} 间接完成。否则你的构建主机可能会被污染,而且构建出来的包也不可移植。
4.3 配方命名规范:别让名字坑了你
Yocto 的配方命名有一套约定俗成的规则。虽然不遵守也能编译,但遵守了能让你的项目更清晰。
- 格式:
{PN}_{PV}.bb,例如hello_1.0.0.bb - 小写字母:配方名统一小写,避免大小写混淆
- 连字符 vs 下划线:推荐用连字符
-分隔单词,如hello-world。下划线在 Yocto 里有特殊含义(用于 override 语法),容易引发歧义 - 版本号不要带前缀:不要写成
hello_v1.0.0.bb,直接hello_1.0.0.bb即可
我个人习惯在配方文件名里只保留 PN 和 PV,所有额外信息(如分支名、补丁版本)都放在配方内部用变量管理。这样文件名干净,也方便自动化工具解析。
4.4 版本管理:多版本共存与升级策略
实际项目中,你经常需要同时维护多个版本的配方。Yocto 提供了几种机制:
4.4.1 多版本配方文件
你可以在同一目录下放多个版本:
recipes-example/hello/
├── hello_1.0.0.bb
├── hello_1.1.0.bb
└── hello_2.0.0.bb
默认情况下,BitBake 会选择最高版本。如果你想固定使用某个版本,可以在 local.conf 或 bbappend 里设置:
PREFERRED_VERSION_hello = "1.1.0"
4.4.2 PREFERRED_VERSION 的优先级
我曾经在一个项目里同时维护了三个版本的 OpenSSL,每个版本对应不同的客户需求。通过 PREFERRED_VERSION 可以精确控制每个镜像使用哪个版本。但要注意:PREFERRED_VERSION 只在配方选择阶段生效,如果两个配方有相同的 PN 但不同的 PV,BitBake 会先按 PREFERRED_VERSION 筛选,再按版本号排序。
版本选择优先级(从高到低):
PREFERRED_VERSION_pn-xxx显式指定- 版本号最高的配方
- 如果版本号相同,按 PR 比较
4.4.3 版本升级的避坑指南
升级配方版本时,我建议你遵循以下步骤:
- 先复制旧配方,改文件名和 PV,不要直接在原文件上改——方便回退
- 检查 SRC_URI:上游项目的下载地址或标签可能变了
- 验证 LIC_FILES_CHKSUM:新版本的 LICENSE 文件 MD5 值可能不同
- 重新生成补丁:旧补丁可能在新版本上不适用
- 测试构建:用
bitbake -c cleanall xxx && bitbake xxx完整构建一次
我曾经跳过「检查 LIC_FILES_CHKSUM」这一步,结果构建通过了,但最终产品里包含了 GPLv3 的代码——而我们的项目要求必须是 MIT 许可证。那次事故之后,我写了一个脚本,每次升级版本时自动校验所有 LICENSE 文件的 MD5 值。
4.5 知识体系总览
下面这张图帮你梳理本章的核心知识点:
这张图把配方基础拆成了四个维度:文件结构、关键变量、命名规范、版本管理。你写配方时,可以对照这张图检查自己是否覆盖了所有要点。我个人习惯把这张图打印出来贴在工位上,写新配方时扫一眼,基本不会漏东西。
好了,配方基础就讲到这里。记住:PN、PV、PR 是配方的「三要素」,S 和 D 是构建的「两个锚点」。把这些变量理解透,后面学继承、补丁、打包就会轻松很多。