第4章:定制自己的Yocto发行版——创建自定义Layer、编写第一个Recipe、添加软件包到Image
好,咱们进入实战环节。前面几章聊了Yocto的架构和概念,说实话,光看理论容易犯困。我自己当年学Yocto的时候,就是被那一堆术语搞得晕头转向——什么Layer、Recipe、BitBake,听着都熟,一动手就懵。
这一章咱们直接上手。目标很明确:创建一个属于你自己的Layer,写一个能编译“Hello World”程序的Recipe,最后把这个程序打包进固件镜像里。做完这一步,你就真正掌握了Yocto定制化的核心流程。
本章核心目标
- 理解Layer的结构和用途
- 掌握Recipe的基本语法
- 学会将自定义软件包添加到Image中
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定制化的基本功。你想想看,以后不管是要加一个自己的应用程序、一个库、还是一个内核模块,流程都是一样的。