第4章:Recipe编写入门

好,咱们今天聊聊Recipe怎么写。说实话,很多刚接触Yocto的朋友,第一反应就是——这玩意儿不就是个菜谱吗?嗯,你还真说对了。Recipe就是告诉BitBake怎么把源代码变成你想要的软件包。我最早写Recipe的时候,也踩过不少坑,今天就把这些经验分享给你。

4.1 Recipe文件结构

一个典型的Recipe文件,其实结构很清晰。我习惯把它分成几个部分:

  • 头部信息:描述、许可证、版本号等
  • 源码定义:从哪里下载代码
  • 依赖关系:编译和运行时需要什么
  • 构建过程:怎么编译、怎么安装
  • 打包信息:生成哪些包

来看个最简单的例子:

DESCRIPTION = "一个简单的Hello World程序"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxx"

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}
}

你看,是不是很直观?每个部分各司其职。我在项目中见过有人把几百行的Recipe写得乱七八糟,其实完全没必要。保持结构清晰,后面维护起来才省心。

4.2 LICENSE与LIC_FILES_CHKSUM

许可证这块,很多人容易忽略。但我要说,这恰恰是最容易出问题的地方。

LICENSE:声明你的软件使用什么许可证。常见的比如MIT、GPLv2、Apache-2.0等。如果你用了多个许可证,可以用竖线(|)或与号(&)组合:

LICENSE = "GPLv2 & MIT"

LIC_FILES_CHKSUM:这个更关键。它告诉BitBake去检查许可证文件的校验和。为什么要这么做?说白了,就是防止有人偷偷改了许可证内容,你还没发现。

注意:我曾经遇到过这样一个坑——从GitHub上拉了一个开源项目,LICENSE文件写的是MIT,但实际代码里混了GPL的代码。如果没有LIC_FILES_CHKSUM检查,你根本发现不了。所以,这个校验和一定要写对。

格式是这样的:

LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

如果你有多个许可证文件,可以这样写:

LIC_FILES_CHKSUM = "file://LICENSE;md5=xxx \
                    file://COPYING;md5=yyy"

4.3 SRC_URI详解

SRC_URI,说白了就是告诉BitBake:你的源代码在哪儿。这个变量非常灵活,支持多种协议。

协议 示例 说明
本地文件 file://hello.c 从本地meta层获取
HTTP/HTTPS https://example.com/source.tar.gz 从网络下载
Git git://github.com/user/repo.git 从Git仓库克隆
SVN svn://example.com/repo 从SVN仓库获取

我个人最常用的是Git协议。但要注意,Git仓库需要指定分支和提交ID:

SRC_URI = "git://github.com/user/hello.git;branch=main;protocol=https"
SRCREV = "a1b2c3d4e5f6..."
小技巧:我建议你永远指定SRCREV为具体的commit hash,而不是用"master"或"main"。为什么?因为一旦上游更新了代码,你的构建就可能莫名其妙地失败。用固定hash,构建结果才是可重复的。

SRC_URI还有一些常用参数:

  • branch:指定分支
  • protocol:指定协议(https、ssh等)
  • subdir:解压到子目录
  • name:给这个源起个名字,方便引用

4.4 do_compile与do_install

这两个任务是Recipe的核心。你想想看,编译和安装,不就是构建软件包的两个关键步骤吗?

do_compile:负责编译源代码。默认情况下,BitBake会执行make。但如果你用的是其他构建系统,就需要自己写:

do_compile() {
    # 对于CMake项目
    cmake ${S}
    make
    
    # 或者对于简单的C程序
    ${CC} ${CFLAGS} ${LDFLAGS} main.c -o myapp
}

do_install:负责把编译好的文件安装到目标目录。注意,这里的安装不是装到你的开发机上,而是装到一个临时目录(${D}),后面打包时会用到:

do_install() {
    # 创建必要的目录
    install -d ${D}${bindir}
    install -d ${D}${sysconfdir}
    
    # 安装二进制文件
    install -m 0755 myapp ${D}${bindir}
    
    # 安装配置文件
    install -m 0644 myapp.conf ${D}${sysconfdir}
}

避坑指南:我曾经犯过一个错误——在do_install里直接写死了路径,比如/usr/bin。结果换了个架构,路径就变了。记住,永远使用${D}、${bindir}这些变量,不要写死路径。

4.5 常用变量:PN、PV、PR

这三个变量,你几乎在每个Recipe里都会见到。它们构成了软件包的基本标识。

变量 含义 示例
PN 包名(Package Name) hello-world
PV 包版本(Package Version) 1.0.0
PR 包修订号(Package Revision) r0

PN:通常由Recipe文件名决定。比如hello-world_1.0.0.bb,PN就是hello-world。当然,你也可以在Recipe里显式设置:

PN = "my-hello"

PV:版本号。同样可以从文件名推断,也可以手动指定:

PV = "2.0.1"

PR:这个变量很多人容易忽略。它用来表示Recipe本身的修订次数。比如你改了Recipe的编译选项,但软件版本没变,这时候就应该增加PR:

PR = "r1"

我的习惯:每次修改Recipe的逻辑(不是软件版本升级),我都会把PR加1。这样BitBake就知道这个包需要重新构建了。否则,你改了Recipe但PR没变,BitBake可能认为包已经是最新的,不会重新编译。

这三个变量组合起来,就构成了一个唯一的包标识:${PN}-${PV}-${PR}。比如hello-world-1.0.0-r0。在部署和版本管理时,这个标识非常有用。

小结

嗯,Recipe编写入门就讲到这里。说白了,Recipe就是告诉Yocto怎么把你的代码变成可运行的软件包。LICENSE和LIC_FILES_CHKSUM是法律保障,SRC_URI是源码来源,do_compile和do_install是核心构建逻辑,PN、PV、PR是版本标识。把这几个点搞清楚了,写Recipe就不是什么难事。

下一章,我们会深入聊聊依赖管理。到时候你会发现,Recipe之间的依赖关系,其实比你想的要复杂得多。