4. Recipe 编写入门:从零到一构建你的第一个软件包

Recipe,说白了就是 Yocto 世界的「菜谱」。你告诉它去哪找源码、怎么编译、装到哪个目录,它就能帮你把原材料变成嵌入式系统里能跑的二进制。这一章,我带你手写一个最简单的 Recipe,把整个流程跑通。

本章核心目标:理解 Recipe 的骨架结构,掌握 LICENSE、SRC_URI、do_compile、do_install 四个关键要素,并能将 Recipe 成功加入镜像。

4.1 Recipe 文件结构:一个 .bb 文件就够了

一个标准的 Recipe 就是一个 .bb 文件,放在 meta-<layer>/recipes-<category>/<recipe-name>/ 目录下。文件名格式是 <包名>_<版本号>.bb。

举个例子:

meta-myapp/
└── recipes-example/
    └── hello/
        ├── hello_1.0.bb
        └── files/
            └── hello.c

我个人习惯把源码文件放在 files/ 子目录里,这样结构清晰。Recipe 文件本身由多个变量和任务函数组成,最基本的骨架长这样:

SUMMARY = "A simple hello world program"
DESCRIPTION = "This recipe builds a hello world binary for embedded Linux"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://hello.c"

S = "${WORKDIR}"

do_compile() {
    ${CC} ${CFLAGS} ${LDFLAGS} hello.c -o hello
}

do_install() {
    install -d ${D}${bindir}
    install -m 0755 hello ${D}${bindir}
}

嗯,这里要注意:S 默认指向 ${WORKDIR}/${BP},如果你的源码直接放在 files/ 下,需要显式设成 ${WORKDIR},否则 BitBake 找不到源码文件。

4.2 LICENSE 和 LIC_FILES_CHKSUM:合规的「身份证」

Yocto 对许可证管理非常严格。每个 Recipe 必须声明 LICENSE 和 LIC_FILES_CHKSUM,缺一不可。

变量 作用 示例
LICENSE 声明软件包的许可证类型 LICENSE = "MIT"
LIC_FILES_CHKSUM 指定许可证文件的路径和 MD5 校验值 file://LICENSE;md5=xxx

我曾经踩过一个坑:有一次我改了源码里的版权声明,但忘了更新 LIC_FILES_CHKSUM 的 MD5 值。BitBake 直接报错退出,提示「License checksum mismatch」。后来我养成了习惯——每次修改许可证相关文件后,先跑 bitbake -c fetch <recipe> 看报错信息里的正确 MD5,再填进去。

对于本地源码(file:// 协议),LIC_FILES_CHKSUM 的路径是相对于 Recipe 所在目录的。Yocto 内置了通用许可证文件,路径是 ${COMMON_LICENSE_DIR},比如 MIT、GPL-2.0 这些标准许可证可以直接引用。

4.3 SRC_URI:告诉 BitBake 去哪找源码

SRC_URI 是 Recipe 的「眼睛」,它决定了 BitBake 从哪里获取源码。支持多种协议:

  • 本地文件:file://hello.c —— 源码放在 Recipe 同级的 files/ 目录
  • Git 仓库:git://github.com/user/repo.git;branch=main
  • HTTP 下载:https://example.com/source.tar.gz
  • SVN:svn://example.com/repo

你想想看,嵌入式项目里最常见的场景是什么?就是拿一个开源库,打个补丁,再编译。这时候 SRC_URI 可以写多个源:

SRC_URI = "git://github.com/libfoo/libfoo.git;branch=master \
           file://0001-fix-cross-compile.patch \
           file://config.patch"

BitBake 会按顺序依次获取:先 clone Git 仓库,再打补丁。补丁文件同样放在 files/ 目录下。

小技巧:如果源码在 Git 仓库里,记得设置 SRCREV 指定 commit ID。我习惯用 ${AUTOREV} 在开发阶段自动取最新提交,但发布版本一定要锁定具体 commit,否则构建不可重复。

4.4 do_compile 和 do_install:自定义编译与安装

Yocto 的构建流程是分阶段的,do_compile 和 do_install 是其中两个关键任务。默认情况下,如果源码是 autotools 或 cmake 项目,BitBake 会自动调用 ./configure && make && make install。但如果你写的是裸机程序或简单 C 项目,就得自己动手了。

do_compile:编译阶段

在这个任务里,你可以调用交叉编译器。Yocto 已经帮你设好了环境变量:

  • ${CC} —— 交叉编译器(如 arm-poky-linux-gnueabi-gcc)
  • ${CFLAGS} —— 编译选项
  • ${LDFLAGS} —— 链接选项
  • ${WORKDIR} —— 工作目录,源码解压到这里

举个例子,编译一个多文件项目:

do_compile() {
    ${CC} ${CFLAGS} main.c utils.c -o myapp ${LDFLAGS}
}

do_install:安装阶段

这个任务负责把编译好的文件复制到目标目录。注意,这里的「安装」不是装到你的开发机上,而是装到 ${D} 目录(即 ${WORKDIR}/image),最终会被打包进 rootfs。

常用的安装命令:

do_install() {
    # 创建目标目录
    install -d ${D}${bindir}      # /usr/bin
    install -d ${D}${sbindir}     # /usr/sbin
    install -d ${D}${sysconfdir}  # /etc

    # 安装二进制
    install -m 0755 myapp ${D}${bindir}

    # 安装配置文件
    install -m 0644 myapp.conf ${D}${sysconfdir}
}

注意:千万不要用 cp 命令直接复制文件!install 命令会自动处理权限、目录创建和文件属性。我见过有人用 cp 导致文件权限不对,最终 rootfs 里程序无法执行。

4.5 将 Recipe 添加到镜像

Recipe 写好了,怎么让它出现在最终的文件系统里?有两种方式:

方式一:在 local.conf 中直接添加

最直接的方法,在 build/conf/local.conf 里加上:

IMAGE_INSTALL:append = " hello"

注意 hello 前面有个空格,这是 Yocto 的语法要求。然后重新构建镜像:

bitbake core-image-minimal

方式二:通过自定义镜像 Recipe

如果你维护多个镜像,更推荐创建一个自定义镜像 Recipe。在 meta-myapp/recipes-core/images/my-image.bb 里:

require recipes-core/images/core-image-minimal.bb

IMAGE_INSTALL:append = " hello libfoo"

我个人更倾向第二种方式。为什么呢?因为 local.conf 是本地配置文件,换台机器就得重新配。而镜像 Recipe 可以随 layer 一起分发,团队里所有人都能用。

4.6 本章知识体系总览

下面这张图总结了 Recipe 从编写到生效的完整流程:

Recipe 编写与集成流程 Recipe .bb 文件 LICENSE / LIC_FILES_CHKSUM SRC_URI 指定源码来源 do_compile() 编译 do_install() 安装到 ${D} IMAGE_INSTALL 加入镜像 方式一:local.conf IMAGE_INSTALL:append = " hello" 方式二:自定义镜像 Recipe require core-image-minimal.bb ✅ 最终 rootfs 包含 hello

4.7 实战:完整跑通一个 Recipe

光说不练假把式。我们从头到尾跑一遍:

  1. 创建 layer 和目录结构
    bitbake-layers create-layer meta-myapp
    cd meta-myapp
    mkdir -p recipes-example/hello/files
  2. 编写源码文件 recipes-example/hello/files/hello.c
    #include <stdio.h>
    int main() {
        printf("Hello from Yocto!\n");
        return 0;
    }
  3. 编写 Recipe recipes-example/hello/hello_1.0.bb(内容见 4.1 节)
  4. 添加 layer 到构建环境
    bitbake-layers add-layer meta-myapp
  5. 加入镜像并构建
    echo 'IMAGE_INSTALL:append = " hello"' >> build/conf/local.conf
    bitbake core-image-minimal
  6. 验证 —— 启动 QEMU 或刷到板子上,运行 hello 命令

调试小技巧:如果构建失败,先单独编译 Recipe 看日志:bitbake -c compile hello -v。我经常用 -v 参数查看详细输出,比翻 log 文件快多了。

Recipe 编写其实没那么玄乎。你只要记住:LICENSE 是合规底线,SRC_URI 是源码入口,do_compile 和 do_install 是核心逻辑,IMAGE_INSTALL 是最终归宿。把这四个点吃透,大部分 Recipe 你都能手到擒来。