3. Yocto 工作流与构建系统:Source、Configure、Build、Package、Image 生成、SDK 生成
Yocto 的构建流程,说白了就是一条从源码到最终产品的流水线。很多刚接触 Yocto 的朋友,一上来就被各种术语搞晕了——Source、Configure、Build、Package、Image、SDK…… 其实没那么复杂。我习惯把这六个阶段看作一个「加工厂」的六个车间,每个车间干好自己的活,东西就出来了。
下面这张图,是我每次给团队新人讲 Yocto 工作流时必画的。它把整个流程串起来了,你一看就明白。
3.1 Source(源码获取)—— 巧妇难为无米之炊
Yocto 构建的第一步,就是把所有需要的源码拿到本地。这里的「源码」不只是你写的应用程序,还包括 Linux 内核、U-Boot、各种库、工具链的源码。BitBake 会根据配方文件(.bb)里的 SRC_URI 变量,自动去下载。
关键点:Source 阶段对应 BitBake 的 do_fetch 任务。它支持 HTTP、HTTPS、Git、SVN、本地文件等多种协议。下载后的文件默认存放在 ${DL_DIR}(通常是 downloads/ 目录)。
我个人习惯在第一次构建前,先把所有源码手动下载好。为什么?因为 Yocto 构建过程中如果网络断了,重头再来很痛苦。我曾经在一个网络不稳定的客户现场做 BSP 移植,每次构建到一半就卡在下载上……后来我学乖了,先跑一遍 bitbake -c fetchall <target>,把源码全部拉下来,再正式构建。
小技巧:如果你在公司内网或离线环境工作,可以搭建一个本地 source mirror。在 local.conf 中设置 SOURCE_MIRROR_URL,Yocto 会优先从本地镜像拉取源码,速度飞快。
3.2 Configure(配置)—— 量体裁衣
源码下载完后,下一步就是配置。这个阶段对应 do_configure 任务。不同的软件包有不同的配置方式:
- Autotools 项目:执行
./configure --host=... --target=...,生成 Makefile。 - CMake 项目:执行
cmake -DCMAKE_TOOLCHAIN_FILE=...,生成构建系统。 - Linux 内核:根据
defconfig或.config文件配置内核选项。 - U-Boot:类似内核,通过
make <board>_defconfig配置。
Yocto 的厉害之处在于,它通过 PACKAGECONFIG 机制,让你可以在配方层面灵活开关功能。比如你想让某个库支持 SSL,只需要在配方里加上 PACKAGECONFIG:append = " ssl" 就行。
注意:配置阶段最容易出问题的是交叉编译工具链的路径。Yocto 会自动设置 CC、CXX、LD 等环境变量,但如果你在配方里手动覆盖了这些变量,很可能导致配置失败。我建议尽量用 Yocto 提供的 autotools、cmake 类,它们已经帮你处理好了交叉编译的细节。
3.3 Build(编译)—— 真刀真枪干
配置完成后,进入编译阶段,对应 do_compile 任务。这个阶段就是真正调用编译器、链接器,把源码变成目标文件(.o)、静态库(.a)、动态库(.so)和可执行文件。
Yocto 默认使用 make 作为构建工具,但你也完全可以用 Ninja、Meson 等。编译过程是高度并行的——BB_NUMBER_THREADS 和 PARALLEL_MAKE 这两个变量控制并发数。我一般把 PARALLEL_MAKE 设成 CPU 核心数的 1.5 倍,编译速度能快不少。
避坑指南:我曾经在一个 32 核的服务器上把 PARALLEL_MAKE 设成了 64,结果编译内核时 OOM(内存溢出)了。后来我学到一个经验:PARALLEL_MAKE 不要超过物理内存(GB)的 2 倍。比如 32GB 内存,设到 64 以内比较安全。
3.4 Package(打包)—— 分门别类
编译完成后,Yocto 不会直接把所有文件塞进镜像。它会先做打包,对应 do_package 和 do_packagedata 任务。这一步把编译产物拆分成多个安装包:
| 包类型 | 说明 | 示例 |
|---|---|---|
| 主包 | 包含可执行文件和运行时必需的库 | openssh |
| -dev 包 | 头文件、静态库、符号链接 | openssh-dev |
| -dbg 包 | 调试符号(用于 GDB 调试) | openssh-dbg |
| -doc 包 | 文档、man 手册 | openssh-doc |
| -locale 包 | 本地化翻译文件 | openssh-locale-zh-cn |
Yocto 支持三种包格式:IPK(默认)、DEB、RPM。我个人偏好 IPK,因为它体积小、安装快,适合嵌入式设备。你可以在 PACKAGE_CLASSES 变量中指定:
# local.conf
PACKAGE_CLASSES = "package_ipk"
小技巧:如果你发现镜像里某个包太大,可以用 bitbake -s | grep <package> 查看包的大小。然后通过 IMAGE_INSTALL:remove 把它从镜像中移除,或者用 PACKAGE_EXCLUDE 彻底排除。
3.5 Image 生成(镜像组装)—— 拼出完整系统
打包完成后,Yocto 开始组装最终的镜像文件。这个阶段对应 do_image 任务。它会根据 IMAGE_INSTALL 变量中列出的包列表,把所有需要的包安装到一个根文件系统目录中,然后生成可启动的镜像文件。
常见的镜像格式包括:
- .wic:Yocto 推荐的磁盘镜像格式,可以直接 dd 到 SD 卡或 eMMC。
- .tar.bz2 / .tar.xz:根文件系统的压缩归档,适合 NFS 挂载或容器使用。
- .ext4 / .squashfs:特定文件系统格式的镜像。
- .hddimg:传统的硬盘镜像(较老格式)。
你可以在 IMAGE_FSTYPES 变量中指定要生成的格式:
# local.conf
IMAGE_FSTYPES = "wic ext4 tar.bz2"
经验之谈:我建议至少生成 wic 和 tar.bz2 两种格式。wic 用于直接烧录到目标板,tar.bz2 用于调试时快速解压到 NFS 目录。另外,记得在 IMAGE_FEATURES 中开启 debug-tweaks,这样第一次启动时就可以用空密码登录 root 用户,方便调试。
3.6 SDK 生成(工具链输出)—— 给应用开发者用的
最后一步,生成 SDK(软件开发工具包)。这个阶段对应 populate_sdk 任务。SDK 包含完整的交叉编译工具链、头文件、库文件,以及一个环境设置脚本。应用开发者拿到 SDK 后,不需要安装 Yocto,就能在自己的机器上编译出能在目标板上运行的程序。
生成 SDK 的命令很简单:
bitbake -c populate_sdk <image-name>
生成的 SDK 默认放在 ${TMPDIR}/deploy/sdk/ 目录下,是一个 .sh 自解压安装包。安装后,运行 source environment-setup-<arch>-poky-linux-<abi>,就能在终端里使用交叉编译器了。
注意:SDK 有两种类型:标准 SDK 和 可扩展 SDK(eSDK)。标准 SDK 只包含工具链和库,适合给应用开发者用。eSDK 还包含了 BitBake 和配方文件,适合需要修改构建配置的开发者。我一般给客户提供标准 SDK,给内部团队提供 eSDK。
3.7 工作流总结:一条命令背后的故事
当你执行 bitbake core-image-minimal 时,Yocto 会自动执行上面提到的所有阶段。你不需要手动调用 do_fetch、do_configure…… BitBake 会解析依赖关系,按顺序执行每个任务。
但理解每个阶段做了什么,对调试和优化构建过程至关重要。比如:
- 如果构建失败在
do_fetch,检查网络或源码镜像。 - 如果失败在
do_configure,检查交叉编译配置或PACKAGECONFIG。 - 如果失败在
do_compile,检查源码兼容性或编译选项。 - 如果失败在
do_package,检查文件冲突或打包规则。 - 如果失败在
do_image,检查磁盘空间或包依赖。 - 如果失败在
populate_sdk,检查 SDK 配置或库文件缺失。
调试利器:使用 bitbake -S printdiff <recipe> 可以查看任务签名变化,快速定位哪些配置或源码变更导致了重新构建。这个命令在我排查「为什么这个包又重编了」时帮了大忙。
嗯,以上就是 Yocto 工作流的六个核心阶段。说白了,它就是一套高度自动化的「源码 → 镜像」流水线。你只要写好配方和配置,剩下的交给 BitBake 就行。但理解每个阶段在干什么,能让你在遇到问题时,一眼就看出问题出在哪一步。