这几天想在Rocky Linux 9上配置一个PostgreSQL的开发环境,需要Bear(一款专为clang工具链生成编译数据库的工具。在clang项目中,JSON编译数据库用于记录单个编译单元的处理信息。借助该数据库,开发者能够轻松使用替代程序重新执行编译流程)这个工具,然而Rocky并没有提供Bear的rpm包,这也难不倒我,自己下载源码编译呗!一动手发现Bear依赖google的gprc,grpc又依赖protobuff,可恨的是Rocky下居然也没包含二者的开发包!考虑到后续很多地方都会用到这三个,所以干脆给它们打个rpm包算了,顺便我也学学rpm打包这项技能。经过一番折腾,在AI的帮助下总算搞定了,于是总结了下面这些内容,后续还会写几个具体的案例。
RPM 打包的核心是 .spec 文件,它是包含打包规则、软件信息、构建流程、安装配置的文本配置文件,所有生成 RPM 包的逻辑均定义于此。除了基础的配置选项,spec 文件还支持条件判断语法,可实现多架构适配、多发行版兼容等灵活配置。本文将系统拆解 spec 文件的核心选项,并详细说明条件判断(if/else)的用法,为 RPM 打包提供完整指南。
一、 基础信息类选项(必选,定义软件核心标识)
这类选项用于描述软件的基本信息,是 RPM 包的“身份标识”,大部分为必选项,确保 RPM 包的可识别性和规范性。
1.1 核心必选选项
| 选项 | 详细说明 | 示例 |
|---|---|---|
| Name | RPM 包的名称(最终 RPM 包名格式:Name-Version-Release.arch.rpm),只能包含字母、数字、下划线、连字符,不能有特殊字符 |
Name: nginx |
| Version | 软件的原始版本号(对应上游软件版本,如 1.24.0),不能包含连字符(-),可使用点(.)和数字 |
Version: 1.24.0 |
| Release | 发行版号(用于标识同一软件版本的不同 RPM 打包版本,如补丁更新、配置调整),格式通常为 数字%{?dist}(%{?dist} 自动适配系统发行版,如 el7、el8) |
Release: 1%{?dist} |
| Summary | 软件简短描述(一句话概括软件功能),支持多语言(通过 %{_default_lang} 或指定语言标识) |
Summary: High performance web server and reverse proxy server |
| License | 软件许可证类型(需明确合法许可证,如 GPLv2、MIT、BSD、Commercial 等),对应上游软件授权协议 | License: BSD-2-Clause |
| Group | 软件分类(可选,部分旧系统仍需指定,用于软件仓库分类管理),常见分类:System Environment/Daemons、Applications/Internet 等 |
Group: System Environment/Daemons |
| Source0 | 上游软件源码包路径(本地路径或 URL),多个源码/补丁可依次定义为 Source1、Source2...,通常使用宏简化路径 |
Source0: https://nginx.org/download/nginx-%{version}.tar.gz |
| BuildRoot | 构建根目录(可选,现代 RPM 打包可省略,用于指定编译构建时的临时根目录,避免污染系统环境),通常使用宏定义 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) |
1.2 补充描述选项
| 选项 | 详细说明 | 示例 |
|---|---|---|
| URL | 软件官方网站或源码仓库地址,便于用户获取上游信息 | URL: https://nginx.org/ |
| Vendor | 打包者/厂商名称(可选,标识 RPM 包的提供方) | Vendor: Example Tech Co., Ltd. |
| Packager | 打包者信息(姓名+邮箱,可选) | Packager: Zhang San zhangsan@example.com |
| Description | 软件详细描述(多行文本,第一行可省略,后续行需以空格或制表符开头,支持换行和格式说明) | Description: Nginx is a high-performance HTTP and reverse proxy server, \n as well as a mail proxy server. It is known for its stability, \n rich feature set, simple configuration and low resource consumption. |
二、 构建依赖与运行依赖类选项
这类选项用于声明 RPM 包编译构建时所需的依赖和安装后运行时所需的依赖,确保打包过程顺利且软件安装后可正常运行。
2.1 构建依赖选项
| 选项 | 详细说明 | 示例 |
|---|---|---|
| BuildRequires | 编译构建时必需的软件包(如编译器、开发库、配置工具等),多个依赖用空格分隔,支持版本约束(>=、<=、= 等),对应 devel包 |
BuildRequires: gcc make pcre-devel zlib-devel openssl-devel |
| BuildConflicts | 编译构建时冲突的软件包(存在该包则无法构建),可选,支持版本约束 | BuildConflicts: nginx-legacy-devel < 1.20.0 |
2.2 运行依赖选项
| 选项 | 详细说明 | 示例 |
|---|---|---|
| Requires | 软件运行时必需的软件包(安装当前 RPM 包时会自动检查并安装依赖包),多个依赖用空格分隔,支持版本约束 | Requires: pcre >= 8.32 zlib >= 1.2.8 openssl >= 1.1.1 |
| Requires(pre) | 预安装脚本(%pre)执行前必需的依赖包(优先级高于 Requires) |
Requires(pre): shadow-utils >= 4.1.5 |
| Requires(post) | 安装后脚本(%post)执行时必需的依赖包 |
Requires(post): systemd >= 219 |
| Requires(preun) | 预卸载脚本(%preun)执行前必需的依赖包 |
Requires(preun): systemd >= 219 |
| Requires(postun) | 卸载后脚本(%postun)执行时必需的依赖包 |
Requires(postun): systemd >= 219 |
| Conflicts | 软件运行时冲突的软件包(无法与当前包同时安装),支持版本约束 | Conflicts: apache httpd >= 2.4.0 |
| Obsoletes | 声明当前包替代的旧版本软件包(安装当前包时会自动卸载被替代的包),用于软件版本迭代替换 | Obsoletes: nginx-old < %{version}-%{release} |
| Provides | 声明当前包提供的“虚拟包”或功能标识(其他包可通过该标识依赖当前包),可选 | Provides: web-server = 1.0 nginx = %{version}-%{release} |
三、 构建与安装路径类选项(宏定义)
这类选项多为 RPM 内置宏(也可自定义宏),用于指定编译、安装、文件存放的标准路径,遵循 FHS(文件系统层次结构标准),提高打包的规范性和可移植性。
| 选项/宏 | 详细说明 | 默认值(CentOS/RHEL) | 示例 |
|---|---|---|---|
| %{_prefix} | 软件安装根目录 | /usr | --prefix=%{_prefix}(编译配置参数) |
| %{_bindir} | 可执行程序存放目录(普通用户程序) | %{_prefix}/bin | %{_bindir}/nginx |
| %{_sbindir} | 系统管理员可执行程序存放目录 | %{_prefix}/sbin | %{_sbindir}/nginx |
| %{_datadir} | 共享数据文件存放目录 | %{_prefix}/share | %{_datadir}/nginx/html |
| %{_sysconfdir} | 配置文件存放目录 | /etc | %{_sysconfdir}/nginx/nginx.conf |
| %{_localstatedir} | 本地状态数据存放目录(日志、缓存等) | /var | %{_localstatedir}/log/nginx、%{_localstatedir}/run/nginx |
| %{_libdir} | 共享库文件存放目录 | %{_prefix}/lib64(64位系统)、%{_prefix}/lib(32位系统) | %{_libdir}/nginx/modules |
| %{_mandir} | 手册页(man page)存放目录 | %{_datadir}/man | %{_mandir}/man8/nginx.8.gz |
| %{_builddir} | 编译构建临时目录 | %{_topdir}/BUILD | 自动使用,无需手动配置 |
| %{_rpmdir} | 生成 RPM 包存放目录 | %{_topdir}/RPMS | 自动使用,无需手动配置 |
四、 构建流程脚本段(定义编译、安装逻辑)
这类脚本段以% 开头,按固定顺序执行,用于定义软件的编译、安装、清理等构建步骤,是 spec 文件的核心执行逻辑。
| 脚本段 | 执行时机 | 详细说明 | 示例 |
|---|---|---|---|
| %prep | 构建前准备阶段 | 用于解压源码包、应用补丁、创建目录等预处理操作,常用内置宏:1. %setup -q:自动解压 Source0 源码包并进入解压目录(-q 静默模式)2. %patch0 -p1:应用 Patch0 补丁(-p1 忽略补丁中的顶级目录)3. 支持自定义 shell 命令 |
%prep%setup -q%patch0 -p1 # 应用补丁mkdir -p %{buildroot}%{_sysconfdir}nginx |
| %build | 编译构建阶段 | 用于执行软件编译命令(如 ./configure、make 等),通常结合 RPM 宏指定编译参数,确保编译结果符合安装路径要求 |
%build%configure --prefix=%{_prefix} --conf-path=%{_sysconfdir}/nginx/nginx.conf --with-http_ssl_modulemake %{?_smp_mflags} # 多线程编译,自动适配CPU核心数 |
| %install | 安装阶段 | 用于将编译后的文件安装到 %{buildroot}(临时根目录)中,对应最终系统的安装路径,常用 make install 或手动复制文件,必须确保文件路径与 %files 段一致 |
%installrm -rf %{buildroot} # 清理旧的临时目录make install DESTDIR=%{buildroot} # 安装到临时根目录# 复制自定义配置文件install -m 644 confnginx.conf %{buildroot}%{_sysconfdir}/nginx/install -m 755 scriptsnginx.service %{buildroot}%{_unitdir}/ |
| %clean | 构建后清理阶段 | 用于清理构建过程中生成的临时文件、目录(现代 RPM 打包可省略,系统会自动清理),通常删除%{buildroot} 和编译目录 |
%cleanrm -rf %{buildroot}rm -rf %{_builddir}%{name}-%{version} |
五、 文件管理段(定义 RPM 包包含的文件)
%files 段用于指定最终生成的 RPM 包中包含的所有文件/目录,以及文件的权限、所有者等属性,%install%{buildroot} 必须与 阶段安装到 的文件路径完全一致,否则打包会失败。
5.1 核心语法
%files [选项]
# 文件/目录路径(基于 %{buildroot},省略 %{buildroot} 前缀)
# 可使用 RPM 宏、通配符(*、? 等)
5.2 关键选项与说明
| 选项/语法 | 详细说明 | 示例 |
|---|---|---|
| 普通文件/目录 | 直接指定文件或目录路径,RPM 会自动继承 %install 阶段的权限和所有者 |
%{_sbindir}/nginx(单个文件)%{_sysconfdir}nginx/(整个目录)%{_datadir}nginx/html/*(通配符匹配) |
| %defattr(mode, user, group, dir_mode) | 定义文件默认属性(可选,现代系统可省略,默认继承系统属性):1. mode:文件默认权限(如 0644)2. user:文件默认所有者(如 root)3. group:文件默认所属组(如 root)4. dir_mode:目录默认权限(如 0755) |
%defattr(0644, root, root, 0755) |
| %config(noreplace) | 标记配置文件,安装时若系统中已存在该配置文件,不会覆盖原有文件(仅生成 .rpmnew 后缀的新文件),避免用户配置丢失;省略noreplace 则会直接覆盖 |
%config(noreplace) %{_sysconfdir}/nginx/nginx.conf |
| %doc | 标记文档文件(安装后存放于 %{_docdir}/%{name}-%{version} 目录),如 README、CHANGELOG 等,打包时会自动复制并归类 |
%doc README.md CHANGELOG LICENSE |
| %man | 标记手册页文件(自动识别并安装到 %{_mandir} 对应目录,无需指定完整路径) |
%man nginx.8 |
| %license | 标记许可证文件(单独归类存放,便于用户查看,优先级高于 %doc) |
%license LICENSE |
| %attr(mode, user, group) | 为单个文件/目录指定自定义属性(覆盖 %defattr 默认值) |
%attr(0700, nginx, nginx) %{_localstatedir}/run/nginx(权限700,所有者nginx) |
| %dir | 明确标记目录(若目录下无文件,需用 %dir 声明才会被包含到 RPM 包中) |
%dir %{_localstatedir}/log/nginx |
六、 安装/卸载脚本段(定义运行时执行逻辑)
这类脚本段在 RPM 包安装、升级、卸载的不同阶段执行,用于完成系统配置、服务启停、用户创建等附加操作,支持标准 shell 命令。
| 脚本段 | 执行时机 | 详细说明 | 示例 | ||||
|---|---|---|---|---|---|---|---|
| %pre | 预安装阶段(文件解压前) | 用于准备工作(如创建系统用户/组、创建必要目录、备份旧文件等),运行身份为 root | %pre# 创建nginx用户和组,若不存在getent group nginx >dev/null | groupadd -r nginxgetent passwd nginx >dev/null | useradd -r -g nginx -s /sbin/nologin -d %{_localstatedir}/lib/nginx nginx | ||
| %post | 后安装阶段(文件解压后) | 用于完成配置生效、服务启动、缓存更新等操作 | %post# 重新加载systemd配置并启动nginx服务systemctl daemon-reload >dev/null 2>&1 | :systemctl enable --now nginx >dev/null 2>&1 | : | ||
| %preun | 预卸载阶段(文件删除前) | 用于停止服务、备份配置文件等操作,$1=0 表示完全卸载,$1=1 表示升级/降级 |
%preunif [ $1 -eq 0 ]; then # 完全卸载时停止nginx服务 systemctl stop nginx >dev/null 2>&1 | :fi | |||
| %postun | 后卸载阶段(文件删除后) | 用于清理残留文件、重新加载配置、删除用户/组等操作 | %postunif [ $1 -ge 1 ]; then # 升级时重启nginx服务 systemctl restart nginx >/dev/null 2>&1 | :else # 完全卸载时清理残留并重载systemd systemctl daemon-reload >dev/null 2>&1 | :fi | ||
| %verify | 验证阶段(执行 rpm -V 时) |
用于自定义软件验证规则(可选,极少使用),检查文件完整性、配置有效性等 | %verifyif [ ! -f %{_sysconfdir}/nginx/nginx.conf ]; then echo "nginx config file missing" >&2 exit 1fi |
关键说明:脚本中建议添加>/dev/null 2>&1 || :,避免非关键错误导致安装/卸载失败。
七、 条件判断(if/else)用法(灵活适配多场景)
RPM spec 文件的 if/else 是 RPM 内置的宏条件判断语法,非普通 shell 脚本判断,核心用于实现多架构适配、多发行版兼容、功能按需编译等灵活配置,核心关键字为 %if、%else(可选)、%elif(可选)、%endif(必选,闭合条件块)。
7.1 核心语法格式
# 单分支判断
%if <条件表达式>
# 满足条件时执行的配置/脚本/文件定义
%endif
# 双分支判断
%if <条件表达式>
# 满足条件时的逻辑
%else
# 不满足条件时的逻辑
%endif
# 多分支判断
%if <条件表达式1>
# 满足条件1的逻辑
%elif <条件表达式2>
# 满足条件2的逻辑
%else
# 不满足所有前置条件的默认逻辑
%endif
关键说明:条件表达式前后无需加括号;条件块内可包含 spec 任意内容;%endif 必选;支持嵌套条件判断。
7.2 核心条件判断类型(常用场景)
7.2.1 基于系统架构判断(%{_arch} 宏)
通过内置宏 %{_arch} 获取当前构建系统的硬件架构(如 x86_64、aarch64、i386 等),实现不同架构的差异化配置。
常用表达式:
- 精确匹配:
%if "%{_arch}" == "x86_64" - 多值匹配:
%if "%{_arch}" == "x86_64" || "%{_arch}" == "aarch64" - 反向匹配:
%if "%{_arch}" != "i386"
# 不同架构指定不同的构建依赖或文件路径
%if "%{_arch}" == "x86_64"
BuildRequires: some-lib-x86_64-devel
%{_libdir}/x86_64-linux-gnu/xxx.so
%elif "%{_arch}" == "aarch64"
BuildRequires: some-lib-aarch64-devel
%{_libdir}/aarch64-linux-gnu/xxx.so
%else
BuildRequires: some-lib-devel
%{_libdir}/xxx.so
%endif
7.2.2 基于宏是否定义判断(%{?宏名} / %{!宏名})
用于判断某个宏是否已定义,实现“可选功能”的开关配置,灵活度最高。
常用表达式:
%if %{?宏名}:宏已定义则条件为真%if %{!宏名}:宏未定义则条件为真- 结合值判断:
%if "%{?宏名}" == "开启值"
# 自定义宏 --with-ssl,控制是否编译 SSL 功能
%define with_ssl 1
%if %{?with_ssl}
BuildRequires: openssl-devel >= 1.1.1
%build
./configure --with-ssl %{nil}
%else
%build
./configure %{nil}
%endif
# 判断系统是否为 systemd 系统
%if %{?systemd}
%{_unitdir}/nginx.service
Requires(post): systemd
%endif
7.2.3 基于发行版版本判断(%{rhel} / %{fedora} 宏)
通过内置宏判断系统发行版及版本,实现 CentOS/RHEL 不同版本(如 el7、el8、el9)或 Fedora 不同版本的兼容适配。
常用内置宏:
%{rhel}:CentOS/RHEL 主版本号(如 7、8、9)%{fedora}:Fedora 版本号(如 38、39)%{dist}:发行版标识(如 el7、fc39)
常用表达式:
- 版本比较:
%if %{rhel} >= 8 - 精确版本:
%if %{rhel} == 7
# 不同 CentOS/RHEL 版本的 Python 依赖差异
%if %{rhel} >= 8
BuildRequires: python3-devel >= 3.6
Requires: python3 >= 3.6
%else
BuildRequires: python2-devel
Requires: python2 >= 2.7
%endif
# 不同版本的系统服务配置差异
%if %{rhel} >= 7
%post
systemctl daemon-reload >/dev/null 2>&1 || :
systemctl enable --now nginx >/dev/null 2>&1 || :
%else
%post
chkconfig --add nginx
service nginx start >/dev/null 2>&1 || :
%endif
7.3 注意事项
语法严格性:
%if/%else/%elif/%endif关键字必须单独成行,否则解析失败;宏的引号:字符串类型的宏(如
%{_arch})判断时建议加双引号,避免宏值为空导致语法错误;嵌套限制:支持嵌套但层级不宜过深(建议≤3层),避免可读性差;
调试方法:通过
rpmbuild -bp --nodeps xxx.spec预处理 spec 文件,查看实际生效的配置。
八、 其他辅助选项与总结
8.1 其他辅助选项
| 选项 | 详细说明 | 示例 |
|---|---|---|
| Patch0 | 补丁文件路径(多个补丁依次定义为 Patch1、Patch2...),配合 %patch0 使用 |
Patch0: nginx-1.24.0-fix-config-path.patch |
| ExclusiveArch | 指定仅支持的硬件架构,多个架构用空格分隔 | ExclusiveArch: x86_64 aarch64 |
| AutoReqProv | 自动检测依赖和提供的功能(默认开启 yes,可关闭 no 手动指定) |
AutoReqProv: no |
| %define / %global | 自定义宏(%global 作用域更广,推荐优先使用) |
%global nginx_user nginx |
8.2 总结
RPM spec 文件的核心逻辑可归纳为“基础定义-依赖管理-构建执行-文件配置-灵活适配”五大模块,关键要点如下:
基础信息:
Name/Version/Release/Summary/License是必选,定义 RPM 包身份;依赖管理:
BuildRequires(构建依赖)、Requires(运行依赖)确保打包和运行完整性;构建流程:
%prep(解压补丁)、%build(编译)、%install(安装到临时目录)按顺序执行;文件管理:
%files需与%install路径完全一致,通过%config、%attr等指定文件属性;灵活适配:条件判断(
%if/%else)基于架构、发行版、宏定义实现多场景兼容,是复杂打包需求的核心工具。(注:文档部分内容由 AI 生成)