Recipe深度解剖:Recipe文件结构、SRC_URI、DEPENDS、do_compile、do_install等核心任务

各位同学,今天我们来聊聊Recipe。说白了,Recipe就是Yocto项目的“灵魂”。你想想看,一个嵌入式系统里那么多软件包,怎么编译?怎么安装?怎么打包?全靠这个小小的.bb文件来指挥。

我记得刚接触Yocto那会儿,打开一个Recipe文件,看到一堆变量和函数,说实话有点懵。但后来做多了就发现,它的结构其实非常清晰。今天我就带大家把Recipe的骨架拆开,看看里面到底装了些什么。

Recipe文件的基本结构

一个标准的Recipe文件,通常长这样:

DESCRIPTION = "这是一个示例驱动"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://COPYING;md5=xxx"

SRC_URI = "file://mydriver.c \
           file://Makefile \
           file://0001-fix-bug.patch"

DEPENDS = "virtual/kernel"

S = "${WORKDIR}"

inherit module

do_compile() {
    oe_runmake
}

do_install() {
    install -d ${D}${base_libdir}/modules/${KERNEL_VERSION}/extra
    install -m 0644 ${S}/mydriver.ko ${D}${base_libdir}/modules/${KERNEL_VERSION}/extra/
}

嗯,看着是不是有点眼熟?其实Recipe就是一段BitBake能理解的脚本。它告诉系统:你要从哪里拿源码、依赖谁、怎么编译、装到哪里去。

SRC_URI:源码从哪里来

SRC_URI这个变量,我习惯叫它“资源定位器”。它告诉BitBake去哪里拿源码。可以是本地文件、Git仓库、HTTP下载,甚至是补丁文件。

协议类型 示例 说明
file:// file://mydriver.c 本地文件,放在Files目录下
git:// git://github.com/xxx/driver.git Git仓库
http:// http://example.com/source.tar.gz HTTP下载
ftp:// ftp://server.com/package.tar.bz2 FTP下载
我的经验:用file://的时候,记得把文件放在和Recipe同名的Files目录下,或者用FILESEXTRAPATHS变量指定路径。我曾经因为放错位置,折腾了整整一个下午。

DEPENDS:依赖关系管理

DEPENDS这个变量,说白了就是告诉系统:“我编译之前,你得先把这些东西准备好”。对于驱动开发来说,最常见的依赖就是virtual/kernel。

为什么会这样?因为驱动要调用内核的头文件和API。没有内核源码,驱动根本编译不过去。

DEPENDS = "virtual/kernel"
# 或者更具体一点
DEPENDS = "linux-yocto"
注意:DEPENDS只影响编译时的依赖。如果你需要运行时依赖,要用RDEPENDS。我曾经犯过这个错,编译通过了,但装到板子上跑不起来,查了半天才发现是运行时缺库。

do_compile:编译的核心战场

do_compile这个任务,是Recipe里最核心的部分。默认情况下,BitBake会执行oe_runmake,但很多时候我们需要自定义。

我个人习惯这样写:

do_compile() {
    # 先清理一下
    make clean
    
    # 传入内核构建目录
    oe_runmake KERNEL_SRC=${STAGING_KERNEL_DIR} \
               KERNEL_PATH=${STAGING_KERNEL_DIR} \
               CC="${KERNEL_CC}" \
               LD="${KERNEL_LD}"
}

这里有几个关键变量:

  • STAGING_KERNEL_DIR:内核源码的暂存目录
  • KERNEL_CC:内核用的交叉编译器
  • KERNEL_LD:内核用的链接器

我记得有一次,一个同事直接用了系统的gcc来编译驱动,结果在目标板上加载时报错“invalid module format”。嗯,这就是典型的编译器和内核不匹配的问题。

do_install:安装的艺术

编译完了,东西放哪?这就是do_install的任务。对于驱动来说,通常要安装到内核模块目录下。

do_install() {
    # 创建目标目录
    install -d ${D}${base_libdir}/modules/${KERNEL_VERSION}/extra
    
    # 安装驱动模块
    install -m 0644 ${B}/mydriver.ko \
        ${D}${base_libdir}/modules/${KERNEL_VERSION}/extra/
    
    # 如果有配置文件,也一并安装
    if [ -f ${S}/mydriver.conf ]; then
        install -d ${D}${sysconfdir}/modprobe.d
        install -m 0644 ${S}/mydriver.conf \
            ${D}${sysconfdir}/modprobe.d/
    fi
}
关键点:${D}是临时安装目录,最终打包时会把${D}下的内容打包成IPK/RPM/DEB。千万不要直接安装到根目录!

知识体系总览

下面这张图,是我自己总结的Recipe核心逻辑。你看一眼就能明白整个流程:

Recipe核心流程 SRC_URI 源码获取 DEPENDS 依赖解析 do_compile 编译构建 do_install 安装部署 do_package 打包输出 关键变量 • WORKDIR:工作目录 • S:源码目录 • B:构建目录 • D:安装目录 • STAGING_KERNEL_DIR

避坑指南

最后,我分享几个实战中踩过的坑:

  • SRC_URI路径问题:我曾经把file://写成http://,结果BitBake一直尝试联网下载,等了半天超时报错。
  • DEPENDS循环依赖:A依赖B,B依赖A,BitBake直接罢工。解决办法是拆包或者用PACKAGE_CONFIG。
  • do_install权限问题:驱动模块一定要用0644权限,可执行权限会导致内核拒绝加载。
  • KERNEL_VERSION不匹配:编译时用的内核版本和运行时不一致,模块加载会报错。建议在Recipe里加个检查。
我的建议:刚开始写Recipe的时候,多看看OE-Core里现成的例子。比如meta/recipes-kernel/目录下有很多内核模块的Recipe,直接拿来改改就能用。

好了,Recipe的核心内容就这些。你想想看,其实Recipe没那么神秘,就是告诉系统:源码在哪、依赖谁、怎么编译、装到哪。把这四件事搞清楚了,写Recipe就跟喝水一样简单。

可可精品资料库