4. Recipe 编写入门:从零开始构建你的第一个配方
Recipe,说白了就是 Yocto 的“菜谱”。它告诉 BitBake 从哪里下载源码、怎么打补丁、怎么编译、最后装到哪。我刚开始接触 Yocto 时,觉得这东西很神秘。后来写了几十个 recipe 才发现,核心套路就那么几个。
今天咱们就把 recipe 的骨架拆开,一个一个看明白。
4.1 Recipe 文件结构:一个 recipe 长什么样
一个典型的 recipe 文件,后缀是 .bb。它本质上是一个 元数据文件,用 BitBake 的语法写成。我习惯把它分成三个部分:
- 头部:描述信息,比如名字、版本、许可证
- 源码区:告诉系统去哪拿代码
- 任务区:定义构建和安装的步骤
来看一个最简单的例子:
# 头部
SUMMARY = "Hello World 示例程序"
DESCRIPTION = "一个简单的 C 程序,用于演示 recipe 结构"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxx"
# 源码区
SRC_URI = "file://hello.c"
# 任务区
do_compile() {
${CC} ${CFLAGS} ${LDFLAGS} hello.c -o hello
}
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}
}
嗯,这里要注意:${CC}、${CFLAGS} 这些变量是 Yocto 自动帮你设好的,你不用自己写死编译器路径。
4.2 LICENSE 与 LIC_FILES_CHKSUM:许可证不是小事
Yocto 对许可证检查非常严格。如果你不写 LICENSE,BitBake 会直接报错。我见过不少新手在这栽跟头。
LICENSE 变量声明软件使用什么许可证。常见的有:
| 许可证 | 说明 | 典型场景 |
|---|---|---|
| MIT | 宽松许可证 | 自己写的小工具 |
| GPL-2.0-only | GNU 通用公共许可证 v2 | Linux 内核、BusyBox |
| BSD-2-Clause | BSD 2 条款许可证 | 一些网络库 |
| Proprietary | 专有许可证 | 商业软件、闭源驱动 |
LIC_FILES_CHKSUM 是校验文件。Yocto 会去检查你指定的许可证文件,计算它的 MD5 值。如果文件内容变了,构建就会失败。为什么要这样?
我曾经在一个项目里,上游悄悄改了许可证条款,从 MIT 改成了 GPL。如果没有这个校验,我们可能就把 GPL 代码当成 MIT 代码发布出去了——那可就惹上法律麻烦了。
file:// 指向本地文件,路径是相对于 recipe 所在目录的。别写成绝对路径。
4.3 SRC_URI 详解:源码从哪里来
SRC_URI 是 recipe 里最灵活的变量之一。它告诉 BitBake 去哪里拿源码。支持的协议非常多:
file://— 本地文件,放在 recipe 同目录或files/子目录http:///https://— 从网络下载git://— 从 Git 仓库拉取svn://— 从 SVN 仓库拉取ftp://— 从 FTP 服务器下载
来看几个实际例子:
# 从 GitHub 下载特定版本的 tar 包
SRC_URI = "https://github.com/example/libfoo/archive/v1.2.3.tar.gz"
# 从 Git 仓库拉取,指定分支和提交
SRC_URI = "git://github.com/example/libfoo.git;branch=master;protocol=https"
# 混合使用:从网络下载主程序,本地补丁
SRC_URI = "https://example.com/app-1.0.tar.gz \
file://fix-build.patch \
file://config.cfg"
这里有个坑:用 git:// 协议时,记得加上 protocol=https。很多公司防火墙会拦截 git 协议,但放行 HTTPS。我踩过这个坑,排查了半天才发现是网络策略问题。
4.4 do_fetch / do_unpack / do_patch:源码准备三步走
这三个任务是 Yocto 构建流程的前三步。它们按顺序执行:
- do_fetch — 下载源码
- do_unpack — 解压源码
- do_patch — 打补丁
咱们一个一个看。
do_fetch:把源码拿回来
BitBake 会根据 SRC_URI 里的协议,自动选择对应的 fetcher 模块。下载的文件会放在 ${DL_DIR} 目录下(通常是 downloads/)。
我个人习惯在 local.conf 里设置 DL_DIR 指向一个共享目录。这样多个项目可以共用下载好的源码,省带宽也省时间。
do_unpack:解压到工作目录
解压后的文件会放到 ${WORKDIR} 下。对于 tar 包,BitBake 会自动识别格式并解压。对于 Git 仓库,它会执行 git checkout。
如果你需要自定义解压方式,可以重写 do_unpack:
do_unpack_append() {
# 解压后执行一些额外操作
chmod +x ${S}/scripts/*.sh
}
do_patch:给源码打补丁
Yocto 会自动应用 SRC_URI 里所有 .patch 或 .diff 文件。补丁文件通常放在 recipe 目录的 files/ 子目录下。
补丁的命名我建议遵循这个格式:
数字-描述.patch
比如:
0001-fix-cross-compile.patch
0002-add-missing-header.patch
数字越小,越先应用。这样可以避免补丁之间的依赖问题。
devtool modify 可以自动帮你生成补丁。你修改源码后,devtool finish 会把改动导出为 patch 文件。省去手动 diff 的麻烦。
4.5 do_configure / do_compile / do_install:构建三剑客
这三个任务是 recipe 的核心。它们定义了软件怎么配置、怎么编译、怎么安装。
do_configure:配置构建环境
对于 autotools 项目,默认会执行 ./configure。对于 CMake 项目,会执行 cmake。如果你用的是 Makefile 项目,默认什么都不做。
我经常需要给 configure 传一些自定义参数:
EXTRA_OECONF = "--enable-feature-x --disable-debug"
对于 CMake 项目,用 EXTRA_OECMAKE:
EXTRA_OECMAKE = "-DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release"
do_compile:真正开始编译
默认行为是执行 make。如果你的项目需要特殊参数:
EXTRA_OEMAKE = "V=1"
如果你需要完全自定义编译过程,直接重写 do_compile:
do_compile() {
oe_runmake -C ${S}/src
}
oe_runmake 是 Yocto 封装的 make 命令,它会自动带上交叉编译的工具链参数。
do_install:把文件装到目标目录
这是最容易出错的地方。记住:do_install 是把文件安装到 ${D} 目录(即 ${WORKDIR}/image),而不是你的开发机根目录。
do_install() {
# 创建目标目录
install -d ${D}${bindir}
install -d ${D}${sysconfdir}
# 安装二进制文件
install -m 0755 ${S}/src/myapp ${D}${bindir}/
# 安装配置文件
install -m 0644 ${S}/config/myapp.conf ${D}${sysconfdir}/
}
make install DESTDIR=/usr,结果把文件装到了开发机的 /usr 下。一定要用 ${D} 前缀。
4.6 知识体系总览
下面这张图把 recipe 的完整流程串起来了。我建议你保存下来,写 recipe 时对照着看:
4.7 实战建议:从零开始写一个 recipe
如果你现在要写第一个 recipe,我建议按这个步骤来:
- 先抄一个现成的 — 在
meta/recipes-*目录里找一个类似的 recipe,复制过来改 - 只改必要字段 —
SUMMARY、LICENSE、SRC_URI这三个必须改 - 用
bitbake -c fetch测试下载 — 先确认源码能拿到 - 用
bitbake -c compile测试编译 — 确认能编译通过 - 用
bitbake -c install测试安装 — 确认文件装到了正确位置
我刚开始写 recipe 时,经常一次性跑完整构建,结果报错了也不知道是哪一步的问题。后来学乖了,一步一测,效率反而更高。
- Recipe 是 .bb 文件,包含头部、源码区、任务区
- LICENSE 和 LIC_FILES_CHKSUM 必须配对使用
- SRC_URI 支持多种协议,Git 仓库记得加 protocol=https
- do_fetch / do_unpack / do_patch 是源码准备三步
- do_configure / do_compile / do_install 是构建三剑客
- 安装时一定要用 ${D} 前缀,别装到开发机根目录