4. Recipe编写入门:从零开始构建你的第一个配方
Recipe,说白了就是Yocto的“菜谱”。你想想看,做菜需要食材清单、步骤说明,Recipe也是一样——它告诉BitBake去哪里下载源码、怎么解压、怎么打补丁、怎么编译、最后装到哪。今天我就带你手把手拆解一个完整的Recipe。
4.1 Recipe文件结构:骨架先搭好
一个典型的Recipe文件长什么样?我直接给你看个例子:
SUMMARY = "一个简单的Hello World程序"
DESCRIPTION = "这个程序会在终端打印Hello World,用于演示Recipe编写"
HOMEPAGE = "https://example.com"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxxx"
SRC_URI = "git://github.com/example/hello.git;branch=main"
S = "${WORKDIR}/git"
inherit autotools
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}
}
嗯,这里要注意几个关键变量:
- SUMMARY/DESCRIPTION:简短描述和详细说明,别偷懒,写清楚对后面维护有好处
- HOMEPAGE:项目主页,方便别人查资料
- SECTION:分类标签,比如console/utils、libs、devel等
- LICENSE:许可证声明,这个后面细说
- SRC_URI:源码来源,Recipe的灵魂所在
- S:源码解压后的目录,默认是${WORKDIR}/${BPN}-${PV}
4.2 LICENSE与LIC_FILES_CHKSUM:合规的底线
许可证这事,说实话很多人不重视。我在项目中遇到过客户审计,发现一个第三方库的LICENSE没写对,结果整个BSP都要重新发布。那叫一个折腾。
LICENSE变量声明许可证类型,比如:
LICENSE = "GPL-2.0-only"
LICENSE = "MIT & BSD-3-Clause"
LICENSE = "LGPL-2.1+"
多个许可证用空格分隔,&表示“且”,|表示“或”。
LIC_FILES_CHKSUM是校验文件,格式是:
LIC_FILES_CHKSUM = "file://COPYING;md5=xxxxx"
为什么要加这个?说白了就是防止有人偷偷改许可证。BitBake会计算文件的MD5值,跟你写的比对,不一致就报错。
bitbake -c fetch recipe-name,看校验值对不对。
获取MD5值的方法:
# 方法1:手动计算
md5sum path/to/LICENSE
# 方法2:让BitBake帮你算(推荐)
bitbake -c fetch recipe-name
# 然后看报错信息里的expected值
4.3 SRC_URI详解:源码从哪里来
SRC_URI是Recipe里最灵活的变量。它支持多种协议:
| 协议 | 示例 | 说明 |
|---|---|---|
| HTTP/HTTPS | https://example.com/foo.tar.gz | 最常用,支持wget下载 |
| Git | git://github.com/xxx/yyy.git;branch=master | 克隆仓库,支持分支、tag、commit |
| 本地文件 | file://mypatch.patch | 放在Files目录下的文件 |
| SVN | svn://example.com/repo | Subversion仓库 |
Git协议最常用,参数也最多:
SRC_URI = "git://github.com/example/libfoo.git;protocol=https;branch=main;tag=v1.0"
参数说明:
- protocol:协议类型,git、https、ssh等
- branch:分支名
- tag:标签名,指定后就不需要branch了
- rev:commit ID,最精确的方式
- destsuffix:解压后的目录名,默认是git
4.4 do_fetch / do_unpack / do_patch:源码准备三步走
这三个任务是BitBake自动执行的,但理解它们能帮你排查问题。
do_fetch:下载源码
- 根据SRC_URI下载源码包
- 下载到${DL_DIR}目录
- 支持断点续传,我经常用
bitbake -c fetchall recipe-name提前下载所有依赖
do_unpack:解压源码
- 解压到${WORKDIR}目录
- tar.gz自动解压,git仓库自动克隆
- 解压后的目录名由S变量指定
do_patch:打补丁
- 应用SRC_URI中列出的所有补丁文件
- 补丁文件放在Recipe同目录的Files文件夹下
- 按文件名顺序应用,建议用数字前缀控制顺序
# 补丁文件示例
SRC_URI += "file://0001-fix-build-error.patch"
SRC_URI += "file://0002-add-feature.patch"
bitbake -c listtasks recipe-name查看所有任务。我调试时经常用这个命令。
4.5 do_configure / do_compile / do_install:核心构建三件套
这三个任务才是真正干活的地方。默认情况下,BitBake会调用autotools或cmake的标准流程,但很多时候你需要自定义。
do_configure:配置阶段
- 默认执行./configure(autotools)或cmake(CMake)
- 可以添加额外参数:EXTRA_OECONF或EXTRA_OECMAKE
- 也可以完全重写:
do_configure() {
# 自定义配置
./configure --prefix=/usr --enable-feature
}
do_compile:编译阶段
- 默认执行make
- 可以指定并行度:PARALLEL_MAKE = "-j4"
- 也可以完全重写:
do_compile() {
oe_runmake
}
do_install:安装阶段
- 把编译产物安装到${D}目录(临时根文件系统)
- ${D}默认是${WORKDIR}/image
- 安装路径要对应目标系统的目录结构
do_install() {
install -d ${D}${bindir}
install -m 0755 myprogram ${D}${bindir}
install -d ${D}${sysconfdir}
install -m 0644 myconfig.conf ${D}${sysconfdir}
}
4.6 实战:写一个完整的Recipe
光说不练假把式。我们来写一个完整的Recipe,编译一个简单的C程序:
# hello_1.0.bb
SUMMARY = "Hello World示例程序"
DESCRIPTION = "一个简单的Hello World程序,用于演示Recipe编写流程"
HOMEPAGE = "https://example.com"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
SRC_URI = "file://hello.c \
file://LICENSE"
S = "${WORKDIR}"
do_compile() {
${CC} ${CFLAGS} ${LDFLAGS} hello.c -o hello
}
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}
}
对应的Files目录结构:
recipes-example/hello/
├── hello_1.0.bb
└── files/
├── hello.c
└── LICENSE
hello.c内容:
#include <stdio.h>
int main() {
printf("Hello from Yocto!\n");
return 0;
}
编译这个Recipe:
bitbake hello
如果一切顺利,你会在目标系统的/usr/bin目录下找到hello程序。运行它,就会打印出"Hello from Yocto!"。
嗯,写Recipe其实没那么难。核心就是搞清楚:源码从哪来、怎么编译、装到哪去。这三个问题搞定了,Recipe就写好了。剩下的就是多写多练,遇到问题多查日志。我在项目里写了上百个Recipe,现在闭着眼睛都能写出来。你也行的。