第4章:定制自己的Yocto发行版——创建自定义Layer、编写第一个Recipe、添加软件包到Image

好,咱们进入实战环节。前面几章聊了Yocto的架构和概念,说实话,光看理论容易犯困。我自己当年学Yocto的时候,就是被那一堆术语搞得晕头转向——什么Layer、Recipe、BitBake,听着都熟,一动手就懵。

这一章咱们直接上手。目标很明确:创建一个属于你自己的Layer,写一个能编译“Hello World”程序的Recipe,最后把这个程序打包进固件镜像里。做完这一步,你就真正掌握了Yocto定制化的核心流程。

本章核心目标

  • 理解Layer的结构和用途
  • 掌握Recipe的基本语法
  • 学会将自定义软件包添加到Image中
自定义Layer与Recipe构建流程 1. 创建自定义Layer meta-mylayer 2. 编写Recipe helloworld.bb 3. 添加到Image IMAGE_INSTALL 详细步骤说明 1. 创建Layer:使用 bitbake-layers create-layer 命令生成骨架 配置 conf/layer.conf,设置优先级和依赖 2. 编写Recipe:在 recipes-example/helloworld/ 下创建 .bb 文件 定义 SRC_URI、S、do_compile、do_install 等关键变量和任务 3. 添加到Image:在 local.conf 或 bbappend 中添加 IMAGE_INSTALL 执行 bitbake core-image-minimal 重新构建固件

4.1 为什么需要自定义Layer?

Yocto项目默认提供了很多Layer,比如 meta、meta-poky、meta-yocto-bsp。但实际产品开发中,你肯定需要加入自己的代码、配置、补丁。直接修改官方Layer?千万别!我见过有人直接在 meta 层里改东西,结果一升级Yocto版本,改的东西全丢了,那叫一个惨。

正确的做法是:创建自己的Layer。它就像个独立的容器,把你的定制内容跟官方代码隔离开。升级时只动官方层,你的东西纹丝不动。

我的习惯:每个产品系列建一个独立的Layer。比如产品A用 meta-product-a,产品B用 meta-product-b。这样复用和迁移都方便。

4.2 创建自定义Layer

Yocto提供了一个工具叫 bitbake-layers,专门用来管理Layer。创建新Layer只需要一条命令:

cd <你的Yocto项目根目录>
bitbake-layers create-layer meta-mylayer

执行完后,你会看到 meta-mylayer 目录被创建出来。它的结构是这样的:

meta-mylayer/
├── conf/
│   └── layer.conf
├── recipes-example/
│   └── example/
│       └── example.bb
├── COPYING.MIT
├── README
└── ...

嗯,这里要注意:recipes-example 只是个示例目录,实际开发中你可以按功能命名,比如 recipes-app、recipes-core、recipes-bsp。

接下来,把Layer添加到构建环境中:

bitbake-layers add-layer meta-mylayer

验证是否添加成功:

bitbake-layers show-layers

你会看到类似这样的输出:

layer                 path                                      priority
==========================================================================
meta                  /path/to/poky/meta                        5
meta-poky             /path/to/poky/meta-poky                   5
meta-yocto-bsp        /path/to/poky/meta-yocto-bsp              5
meta-mylayer          /path/to/poky/meta-mylayer                6

注意看优先级(priority)那一列。默认新建的Layer优先级是6,比官方层高。这意味着如果多个Layer里定义了同名的Recipe,Yocto会优先用优先级高的那个。这个机制很有用,后面讲bbappend时会用到。

我曾经踩过的坑:创建Layer后忘了执行 bitbake-layers add-layer,结果折腾了半天,BitBake一直说找不到我的Recipe。所以,添加完Layer后,一定记得确认 show-layers 里有它。

4.3 理解Layer的配置文件

打开 meta-mylayer/conf/layer.conf,看看里面有什么:

# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"

# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "mylayer"
BBFILE_PATTERN_mylayer = "^${LAYERDIR}/"
BBFILE_PRIORITY_mylayer = "6"

# This should only be incremented on significant changes that
# will cause compatibility issues with other layers
LAYERVERSION_mylayer = "1"

LAYERDEPENDS_mylayer = "core"
LAYERSERIES_COMPAT_mylayer = "kirkstone"

关键变量解释:

变量 作用
BBPATH 告诉BitBake去哪里找配置文件(.conf)和类文件(.bbclass)
BBFILES 指定Recipe文件(.bb)和附加文件(.bbappend)的搜索路径
BBFILE_COLLECTIONS Layer的唯一标识名,用于区分不同Layer
BBFILE_PATTERN 匹配该Layer的文件路径模式
BBFILE_PRIORITY 优先级,数值越大优先级越高
LAYERSERIES_COMPAT 声明该Layer兼容的Yocto版本系列

我个人习惯把 LAYERSERIES_COMPAT 改成当前使用的版本,比如 kirkstone 或 dunfell。不匹配的话,BitBake会报警告。

4.4 编写第一个Recipe

好,重头戏来了。咱们写一个最简单的Recipe——编译一个C语言的“Hello World”程序。

首先,在Layer里创建Recipe目录:

mkdir -p meta-mylayer/recipes-example/helloworld

然后,准备源代码文件。Yocto支持从网络下载源码,也支持本地源码。咱们用本地源码,更直观。在Recipe目录下创建一个 files 子目录:

mkdir meta-mylayer/recipes-example/helloworld/files

在 files 里创建 helloworld.c:

#include <stdio.h>

int main() {
    printf("Hello, Yocto World!\n");
    return 0;
}

再创建一个 Makefile:

all:
    $(CC) helloworld.c -o helloworld

install:
    install -d $(DESTDIR)/usr/bin
    install -m 0755 helloworld $(DESTDIR)/usr/bin

clean:
    rm -f helloworld

现在,创建Recipe文件 helloworld.bb:

SUMMARY = "Simple Hello World application"
DESCRIPTION = "A simple Hello World program to demonstrate Yocto Recipe creation"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://helloworld.c \
           file://Makefile"

S = "${WORKDIR}"

do_compile() {
    oe_runmake
}

do_install() {
    oe_runmake install DESTDIR=${D}
}

FILES:${PN} = "${bindir}/helloworld"

咱们一行行拆解一下:

  • SUMMARY / DESCRIPTION:软件包的简短描述和详细描述,会在 bitbake -s 时显示。
  • LICENSE:许可证类型,必须填写。Yocto对许可证检查很严格,不写会报错。
  • LIC_FILES_CHKSUM:许可证文件的校验和。这里用了Yocto自带的MIT许可证模板。
  • SRC_URI:指定源码来源。 file:// 表示本地文件,Yocto会自动在 files/ 目录下找。
  • S:源码解压后的工作目录。因为咱们没有压缩包,直接设成 ${WORKDIR}。
  • do_compile:编译任务。 oe_runmake 是Yocto封装的make命令,会自动传递交叉编译工具链。
  • do_install:安装任务。把编译好的二进制文件安装到目标目录。
  • FILES:${PN}:声明这个软件包包含哪些文件。 ${bindir} 对应目标系统的 /usr/bin。

关键点:do_install 里的 ${D} 是临时安装目录,不是目标系统的根目录。Yocto会在打包时自动把 ${D} 下的内容映射到目标系统。

4.5 编译和测试Recipe

Recipe写好了,先单独编译测试一下:

bitbake helloworld

如果一切顺利,你会看到类似这样的输出:

Loading cache: 100% |###########################################| Time: 0:00:00
Loaded 1234 entries from dependency cache.
Parsing recipes: 100% |#########################################| Time: 0:00:01
Parsing of 1234 .bb files complete. 0 cached.
NOTE: Resolving any missing task queue fragments
NOTE: Executing Tasks
NOTE: Tasks Summary: Attempted 4 tasks of which 0 didn't need to be rerun and all succeeded.

编译成功后,可以用以下命令查看生成的软件包:

bitbake -e helloworld | grep ^PN=
bitbake -e helloworld | grep ^PV=
bitbake -e helloworld | grep ^PR=

输出类似:

PN="helloworld"
PV="0.1"
PR="r0"

生成的IPK包在 tmp/deploy/ipk/ 目录下,你可以找到 helloworld_0.1-r0_cortexa7t2hf-neon-vfpv4.ipk 这样的文件。

小技巧:如果编译出错,用 bitbake helloworld -c devshell 可以进入一个包含交叉编译环境的Shell,方便手动调试。

4.6 将软件包添加到Image

Recipe能单独编译了,下一步就是把它加进固件镜像里。有两种常用方法:

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

打开 build/conf/local.conf,找到 IMAGE_INSTALL 变量,追加你的软件包:

IMAGE_INSTALL:append = " helloworld"

注意前面的空格,Yocto的变量拼接需要空格分隔。

方法二:通过bbappend文件添加

在自定义Layer中创建 recipes-core/images/core-image-minimal.bbappend:

IMAGE_INSTALL:append = " helloworld"

这种方法更规范,因为改动都在你的Layer里,不会污染 local.conf。

我个人推荐方法二。为什么呢?因为 local.conf 是本地配置文件,不同开发者之间同步起来很麻烦。而Layer里的bbappend文件可以纳入版本控制,团队协作时一目了然。

添加完成后,重新构建镜像:

bitbake core-image-minimal

构建完成后,启动模拟器或烧录到开发板,登录后执行:

helloworld

如果看到 Hello, Yocto World! 输出,恭喜你——你的第一个自定义Yocto发行版诞生了!

我曾经遇到的坑:有次我忘了在Recipe里写 FILES:${PN},结果软件包虽然编译成功了,但镜像里就是找不到 helloworld 这个程序。后来查文档才发现,如果不声明 FILES,BitBake默认不会把任何文件打包进去。嗯,这个坑我记了好几年。

4.7 本章小结

咱们这一章做了三件事:创建了自定义Layer、编写了第一个Recipe、把软件包加进了Image。说白了,这就是Yocto定制化的基本功。你想想看,以后不管是要加一个自己的应用程序、一个库、还是一个内核模块,流程都是一样的。