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 筛选,再按版本号排序。

版本选择优先级(从高到低):

  1. PREFERRED_VERSION_pn-xxx 显式指定
  2. 版本号最高的配方
  3. 如果版本号相同,按 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 知识体系总览

下面这张图帮你梳理本章的核心知识点:

配方(Recipe)基础 · 知识体系 配方文件 .bb 文件结构 元数据段 源码获取段 构建安装段 三段式布局 关键变量 PN - 配方名 PV - 版本号 PR - 修订号 S - 源码目录 D - 安装目录 命名规范 {PN}_{PV}.bb 统一小写 连字符分隔单词 版本号无前缀 版本管理 多版本配方共存 PREFERRED_VERSION 版本升级流程

这张图把配方基础拆成了四个维度:文件结构、关键变量、命名规范、版本管理。你写配方时,可以对照这张图检查自己是否覆盖了所有要点。我个人习惯把这张图打印出来贴在工位上,写新配方时扫一眼,基本不会漏东西。

好了,配方基础就讲到这里。记住:PN、PV、PR 是配方的「三要素」,S 和 D 是构建的「两个锚点」。把这些变量理解透,后面学继承、补丁、打包就会轻松很多。