虚拟配方与提供者:PROVIDES与RPROVIDES机制

包依赖冲突,说白了就是两个配方都想提供同一个东西。比如 libjpeg 和 libjpeg-turbo,都声称自己能提供 JPEG 解码功能。这时候系统就懵了——到底该用哪个?

Yocto 的解决方案很巧妙:虚拟配方。它不直接解决冲突,而是让冲突变得「不冲突」。我刚开始接触这个机制时也觉得绕,但用顺手了就会发现,这其实是嵌入式构建系统里最优雅的设计之一。

PROVIDES:给配方起个「艺名」

每个配方都有一个默认的「本名」,也就是 PN(Package Name)。但有时候,一个配方可以干好几件事。比如 libjpeg-turbo 这个包,它不仅能提供 libjpeg-turbo,还能提供标准的 libjpeg 接口。

这时候就用 PROVIDES 给它加个「艺名」:

# libjpeg-turbo_2.1.5.bb
PROVIDES += "libjpeg"

这样一来,任何依赖 libjpeg 的配方,都能自动匹配到 libjpeg-turbo。系统会认为「libjpeg 这个功能,libjpeg-turbo 也能干」。

我的习惯:PROVIDES 的命名尽量用通用名称。比如不要写 "jpeg-decoder-xyz",直接写 "libjpeg" 就好。这样其他配方引用时更自然。

RPROVIDES:运行时也认这个「艺名」

PROVIDES 解决的是构建时的依赖。但包装到目标系统上后,运行时依赖怎么处理?比如你的应用在运行时需要 libjpeg.so,而 libjpeg-turbo 提供的是 libturbojpeg.so,名字对不上。

这时候需要 RPROVIDES:

# libjpeg-turbo_2.1.5.bb
RPROVIDES:${PN} += "libjpeg"

嗯,这里要注意:RPROVIDES 通常配合 RREPLACES 和 RCONFLICTS 一起用。我遇到过只写了 RPROVIDES 没写 RREPLACES,结果安装时两个包打架的情况。

变量 作用域 典型用途
PROVIDES 构建时 让其他配方能依赖这个虚拟名
RPROVIDES 运行时 让包管理器识别这个虚拟名
RREPLACES 运行时 替换掉旧包
RCONFLICTS 运行时 声明不能和哪些包共存

使用虚拟配方解决命名冲突

虚拟配方本身不产生任何文件。它就是一个「占位符」,告诉系统:某个功能由谁提供。

举个例子。我有一个项目,需要 cryptodev 驱动。但内核里有两个实现:一个是内核主线自带的,一个是第三方模块。两个都叫 cryptodev,直接冲突。

我的做法是创建一个虚拟配方:

# virtual/cryptodev.bb
SUMMARY = "Virtual package for cryptodev"
LICENSE = "MIT"

PROVIDES = "virtual/cryptodev"

然后让两个实现配方都 PROVIDES += "virtual/cryptodev"。其他配方依赖时写 DEPENDS = "virtual/cryptodev"。这样系统就知道:只要有人提供 virtual/cryptodev 就行,具体是谁不重要。

关键点:虚拟配方的命名通常以 virtual/ 开头。这不是强制要求,但已经是社区约定俗成的规范。我个人建议你也这么用,方便别人一眼识别。

PREFERRED_PROVIDER:指定谁才是「正主」

当多个配方都 PROVIDES 同一个虚拟名时,系统会随机选一个。这显然不行。你需要用 PREFERRED_PROVIDER 来指定优先级。

在 local.conf 或 distro.conf 中配置:

PREFERRED_PROVIDER_virtual/cryptodev = "cryptodev-module"
PREFERRED_PROVIDER_virtual/jpeg = "libjpeg-turbo"

语法是 PREFERRED_PROVIDER_虚拟名 = "实际配方名"。注意这里的虚拟名要写全,包括 virtual/ 前缀。

我曾经踩过的坑:PREFERRED_PROVIDER 的赋值必须放在全局配置文件中,不能放在配方里。因为配方解析时,PREFERRED_PROVIDER 还没生效。我试过在配方里写这个变量,结果完全没起作用,排查了半天。

实战:解决 libjpeg 与 libjpeg-turbo 的冲突

这是嵌入式 Linux 里最经典的冲突场景。很多老代码依赖 libjpeg,但新项目都用 libjpeg-turbo(性能更好)。

完整配置如下:

# libjpeg-turbo_2.1.5.bb
PROVIDES = "libjpeg"
RPROVIDES:${PN} = "libjpeg"
RREPLACES:${PN} = "libjpeg"
RCONFLICTS:${PN} = "libjpeg"

然后在 local.conf 中:

PREFERRED_PROVIDER_libjpeg = "libjpeg-turbo"

这样所有依赖 libjpeg 的配方,都会自动链接到 libjpeg-turbo。而且安装时,libjpeg-turbo 会直接替换掉 libjpeg 的包,不会出现两个包共存的情况。

虚拟配方的核心逻辑

我画了一张图,帮你理清整个流程:

虚拟配方与 PROVIDES 核心逻辑 应用配方 DEPENDS = "libjpeg" 虚拟配方 PROVIDES = "libjpeg" libjpeg-turbo PROVIDES += "libjpeg" libjpeg-original PROVIDES += "libjpeg" PREFERRED_PROVIDER libjpeg = "libjpeg-turbo" 应用配方依赖虚拟名 → 虚拟配方声明提供 → 实际配方通过 PROVIDES 注册 PREFERRED_PROVIDER 从多个提供者中选出最终使用的那个 核心思想:解耦「需要什么」和「谁提供」

你想想看,这个机制本质上就是「面向接口编程」在包管理上的体现。应用配方只关心「我需要 libjpeg」,不关心具体实现。具体用哪个,由 PREFERRED_PROVIDER 在构建时决定。

这样做的好处很明显:

  • 解耦:应用配方不需要修改,就能切换底层实现
  • 灵活:不同产品线可以选不同的提供者
  • 可维护:新增一个实现,不需要改任何依赖方
我的建议:如果你在维护一个 BSP 层,尽量把公共接口抽象成虚拟配方。比如虚拟的 GPU 驱动、虚拟的音频后端、虚拟的网络协议栈。这样上层应用写起来干净,底层换硬件时也方便。

嗯,虚拟配方这块内容其实不难,但容易忽略细节。尤其是 RPROVIDES 和 PREFERRED_PROVIDER 的配置位置,搞错了就会出各种奇怪的问题。记住一句话:PROVIDES 在配方里写,PREFERRED_PROVIDER 在配置里写,基本就不会出大错。