Mock 编译 RPM 包时基于 Spec 宏定义定制组件产物范围指南

在构建大型 RPM 包(尤其是内核 Kernel 包)时,官方 spec 文件通常通过宏定义提供了丰富的组件开关。利用 Mock 结合这些宏定义,可以灵活控制最终生成的 RPM 产物集合(如是否生成 headers、debuginfo、perf 工具等),从而显著缩短编译时间并减小存储占用。


一、基本概念介绍

1. 什么是 RPM 宏(Macros)

RPM 宏是 spec 文件中预定义的变量或条件判断语句,用于控制构建流程。在 kernel.spec 中,常见的组件开关定义如下:

spec 复制代码
# 默认开启构建 headers
%define with_headers %{?_without_headers:0} %{?!_without_headers:1}
# 默认开启构建 debuginfo
%define with_debuginfo %{?_without_debuginfo:0} %{?!_without_debuginfo:1}

2. 什么是 --with / --without 开关

这是 rpmbuild 命令原生支持的参数,用于在构建时动态修改宏值:

  • --without <feature> :将对应宏的 with_<feature> 值设为 0(关闭)。
  • --with <feature> :将对应宏的 with_<feature> 值设为 1(开启,通常用于默认关闭的特性)。

3. 什么是 Mock

Mock 是一个在隔离的 chroot 环境中构建 RPM 包的工具。它本身不直接解析 --with,但提供了 --define 选项,可以将任意宏定义直接传递给内部的 rpmbuild 进程。


二、工作原理(核心机制)

1. Spec 文件中的条件判断逻辑

以您提供的宏定义为例:

spec 复制代码
%define with_headers %{?_without_headers:0} %{?!_without_headers:1}
  • 如果用户没有 指定 --without headers,则宏 _without_headers 未定义,此时 %{?!_without_headers:1} 生效,with_headers=1(开启)。
  • 如果用户指定--without headers,则 _without_headers 被定义为任意非空值,此时 %{?_without_headers:0} 生效,with_headers=0(关闭)。

后续 spec 中通过 %if %{with_headers} 条件包裹具体的 %package%build 子段,从而决定该组件是否被构建。

2. Mock 传递参数的本质

Mock 执行命令时,会将 --define "MACRO VALUE" 原样传递给 chroot 内的 rpmbuild

  • 关键映射rpmbuild --without headers 等效于 rpmbuild --define "_without_headers 1"
  • 因此,在 Mock 中禁用组件,就是利用 --define 来模拟 rpmbuild--without 行为

三、常用操作指南

基于您提供的 kernel.spec 宏定义(默认全开),以下是常见的构建定制操作。

1. 禁用单个组件包

场景 :不需要构建 kernel-headers

bash 复制代码
mock -r <chroot配置文件> \
     --define "_without_headers 1" \
     <源码包>.src.rpm

2. 禁用多个组件包

场景:调试阶段仅需核心内核镜像,无需 tools、perf 和 debuginfo。

bash 复制代码
mock -r <chroot配置文件> \
     --define "_without_tools 1" \
     --define "_without_perf 1" \
     --define "_without_debuginfo 1" \
     <源码包>.src.rpm

3. 开启默认关闭的组件(逆向场景)

如果 spec 中使用的是 %bcond_with foo(默认关闭),则开启方法如下:

bash 复制代码
# 对于 %bcond_with foo,默认关闭,需开启
mock -r <chroot配置文件> \
     --define "_with_foo 1" \
     <源码包>.src.rpm

4. 精简构建(仅构建标准内核)

场景 :仅保留标准内核(with_up),剔除 Kata、Debug、Doc 等。

bash 复制代码
mock -r <chroot配置文件> \
     --define "_without_kata 1" \
     --define "_without_debug 1" \
     --define "_without_doc 1" \
     --define "_without_bpftool 1" \
     <源码包>.src.rpm

5. 使用 Mock 配置文件永久定制

如果频繁使用同一套开关,可修改 ~/.mock/<配置>.cfg,在 config_opts 中添加:

python 复制代码
config_opts['rpmbuild_opts'] = [
    '--define "_without_headers 1"',
    '--define "_without_perf 1"'
]

之后直接运行 mock -r <配置> 即可自动应用。


四、注意事项与最佳实践

1. 明确宏的默认状态(Default State)

在操作前,务必检查 spec 文件头部:

  • %define with_xxx %{?_without_xxx:0} ... :默认开启 ,需要用 _without_xxx 关闭。
  • %bcond_without xxx :默认开启 ,需要用 --without 关闭(对应 _without_xxx)。
  • %bcond_with xxx :默认关闭 ,需要用 --with 开启(对应 _with_xxx)。
    错误判断默认状态是导致组件意外缺失或冗余的最常见原因。

2. 依赖关系连带影响

禁用某些组件可能导致其他组件因依赖缺失而编译失败。例如:

  • 禁用 kernel-tools 可能不影响核心镜像,但如果 perf 依赖特定的工具链子包,强行禁用可能会触发 %check 阶段的报错。
  • 建议在禁用前,查看 spec 中 %packageRequires 部分的依赖关系。

3. 验证构建产物

构建完成后,查看 Mock 输出日志中 Wrote: 的行,确认生成的 RPM 列表是否符合预期:

log 复制代码
Wrote: /builddir/build/RPMS/x86_64/kernel-5.14.0-1.x86_64.rpm
Wrote: /builddir/build/RPMS/x86_64/kernel-core-5.14.0-1.x86_64.rpm

若发现依然生成了禁用的包,检查宏名称是否拼写错误(注意下划线 _without_)。

4. 清理缓存与增量构建

Mock 默认会缓存之前的构建根目录。如果修改了宏定义,建议加上 --no-clean(复用缓存加速)或先执行 mock --scrub=all 彻底清理,避免旧的编译对象残留干扰新配置。

5. 区分 SRPM 构建与 RPM 构建

  • --define 可以放在 mock --buildsrpm 阶段(影响 spec 预处理),也可以放在 mock --rebuild 阶段。
  • 若需要生成修改了组件定义的 SRPM,请在构建 SRPM 时就将宏定义传入。

五、总结速查表

目标行为 Mock 命令参数格式
禁用默认开启的组件(如 headers) --define "_without_headers 1"
开启默认关闭的组件(如 experimental) --define "_with_experimental 1"
同时禁用多个组件 串联多个 --define "_without_xxx 1"
查看最终生效的宏值(调试) 在 spec 中临时添加 %echo %{with_headers},或使用 rpm --eval 测试

通过灵活运用 Mock 的 --define 传递机制,您可以像操作原生 rpmbuild 一样精细化管理内核等大型软件的产物范围,实现高效、定制化的持续集成构建。