From Fedora Project Wiki

如何创建一个GNU Hello RPM包

这是一个编写 RPM 文件的简明教程, 展示了如何快速创建一个简单的源代码包和二进制软件包。这里假设您已对使用预编译的 RPM 软件包以及 FOSS (Free and Open Source Software:自由及开源软件) 的编译过程有所了解。

关于如何创建 RPM 文件的完整信息(包括更详细的技巧),请参考 How to create an RPM package。如果你打算为 Fedora 仓库创建一个 RPM 包,请按照 How to join the Fedora Package Collection Maintainers 中的流程,并遵照各种 Fedora 的指南。

本教程演示了 GNU“Hello World” 项目的打包过程。虽然用 C 语言程序打印 “Hello World” 到标准输出是小菜一碟,但 GNU 版本包含了与一个典型的 FOSS 软件项目相关的最常用的外围组件,包括配置/编译/安装环境、文档、国际化等等。GNU 版本包含了一个由源代码和 configure/make 脚本组成的 tar 文件,但并不包含打包信息。因此,这是一个很好的 RPM 包打包示例。

开发环境

为了创建 RPM 包,我们需要一套开发工具。开发环境仅需要配置一次即可,使用系统管理员 (root) 账户运行以下命令:

# dnf install fedora-packager @development-tools

如果您想在一个干净的 chroot 环境中测试编译过程,您需要将您的非特权账户配置为 'mock' 用户组的成员:

# usermod -a -G mock <用户名>

只有以上命令需要 root 权限。所有其他操作应使用非特权账户完成,甚至可为开发工作创建专用的隔离账户。现代基于 RPM 的系统中,包括 Fedora,都在一个非特权账户中编译和测试 RPM 包。执行以下命令

$ rpmdev-setuptree

将会创建 ~/rpmbuild 目录作为 RPM 编译目录。此目录包含若干子目录,用于保存项目源码、RPM 配置文件以及最终的源码和二进制包。

创建 "Hello World" RPM 包

我们打包所需的项目源码通常称为 'upstream' 源。我们将从项目网站上,将源码下载到 ~/rpmbuild/SOURCE 目录中。我们获得了一个 tarball 压缩文件, 这正是大多数 FOSS 项目首选发布形式。

$ cd ~/rpmbuild/SOURCES
$ wget http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz

RPM 包通过 .spec 文件进行配置。在 ~/rpmbuild/SPECS 目录创建模板文件 hello.spec

$ cd ~/rpmbuild/SPECS
$ rpmdev-newspec hello

Emacsvi 的最新版本有 .spec 文件编辑模式,它会在创建新文件时打开一个类似的模板。所以可使用以下命令来自动使用模板文件。

$ vi hello.spec

深入 .spec 文件

在我们 .spec 文件中的字段需要稍作修改。字段说明请参考 Fedora rules。在本例中,文件如下所示:

Name:     hello
Version:  2.10
Release:  1%{?dist}
Summary:  The "Hello World" program from GNU
Summary(zh_CN):  GNU "Hello World" 程序
License:  GPLv3+
URL:      http://ftp.gnu.org/gnu/hello    
Source0:  http://ftp.gnu.org/gnu/hello/%{name}-%{version}.tar.gz

%description
The "Hello World" program, done with all bells and whistles of a proper FOSS 
project, including configuration, build, internationalization, help files, etc.

%description -l zh_CN
"Hello World" 程序, 包含 FOSS 项目所需的所有部分, 包括配置, 构建, 国际化, 帮助文件等.

%changelog
* Sun Nov 16 2014 The Coon of Ty <Ty@coon.org> - 2.10-1
- Update to 2.10
* Thu Jul 07 2011 The Coon of Ty <Ty@coon.org> - 2.8-1
- Initial version of the package
  • Version 标签应与 'upstream' 源的版本号一致,而 Release 是在 Fedora 中使用的发布编号。
  • Summary 标签的第一个字母应大写,以避免 rpmlint 工具警告。
  • 审查软件的 License 状态是打包者的职责,这可以通过检查源码或 LICENSE 文件,或与作者沟通来完成。
  • Group 标签过去用于按照 /usr/share/doc/rpm-<version>/GROUPS 分类软件包。目前该标记已丢弃,默认不会添加。添加该标记也不会有任何影响。
  • %changelog 标签应包含每个 Release 所做的更改日志,尤其应包含上游的安全/漏洞补丁的说明。Changelog 日志可使用 rpm --changelog -q <packagename> 查询,通过查询可得知已安装的软件是否包含指定漏洞和安全补丁,感谢勤劳的 Fedora 打包者添加相关 CVE 编号。
  • %changelog 条目应包含版本字符串,以避免 rpmlint 工具警告。
  • 多行的部分,如 %changelog%description 由指令下一行开始,空行结束。
  • 一些不需要的行 (如 BuildRequiresRequires) 可使用 '#' 注释。
  • 多数情况下,模板中的许多行不需要修改。

构建 RPM 软件包

现在,我们可以尝试执行以下命令,以构建源码、二进制和包含调试信息的软件包:

$ rpmbuild -ba hello.spec

命令执行后,提示并列出未打包的文件,例如那些需要安装在系统中,但又未在 .spec 中声明的文件。我们需要在 %files 中声明它们。注意不要使用形如 /usr/bin/ 的硬编码, 应使用类似 %{_bindir}/hello 这样的宏来替代。手册页应在 %doc 中声明 : %doc %{_mandir}/man1/hello.1.*

这是一个反复的过程:编辑 .spec 文件后,重新运行 rpmbuild

由于我们的程序使用了翻译和国际化,因此会看到很多未声明的 i18 文件。 使用 推荐方法 来声明它们:

  • 查找 %install 中的语言文件: %find_lang %{name}
  • 添加编译依赖: BuildRequires: gettext
  • 声明找到的文件: %files -f %{name}.lang

如果程序使用 GNU info 文件,你需要确保安装和卸载软件包,不影响系统中的其他软件,按以下步骤操作:

  • 在 %install 中添加删除 'dir' 文件的命令: rm -f %{buildroot}/%{_infodir}/dir
  • 添加 Requires(post): infoRequires(preun): info
  • 添加以下安装脚本:
%post
/sbin/install-info %{_infodir}/%{name}.info %{_infodir}/dir || :

%preun
if [ $1 = 0 ] ; then
/sbin/install-info --delete %{_infodir}/%{name}.info %{_infodir}/dir || :
fi

这段代码源于 Packaging:ScriptletSnippets#Texinfo (zh_CN)。该页面包含许多常见打包任务的解决方案。建议根据该页面,定制你自己的方案。

完整的 hello.spec 文件

以下为初始版本的 hello.spec 文件:

Name:           hello
Version:        2.10
Release:        1%{?dist}
Summary:        The "Hello World" program from GNU
Summary(zh_CN): GNU "Hello World" 程序

License:        GPLv3+
URL:            http://ftp.gnu.org/gnu/%{name}
Source0:        http://ftp.gnu.org/gnu/%{name}/%{name}-%{version}.tar.gz

BuildRequires: gettext
Requires(post): info
Requires(preun): info

%description
The "Hello World" program, done with all bells and whistles of a proper FOSS
project, including configuration, build, internationalization, help files, etc.

%description -l zh_CN
"Hello World" 程序, 包含 FOSS 项目所需的所有部分, 包括配置, 构建, 国际化, 帮助文件等.

%prep
%autosetup

%build
%configure
make %{?_smp_mflags}

%install
%make_install
%find_lang %{name}
rm -f %{buildroot}/%{_infodir}/dir

%post
/sbin/install-info %{_infodir}/%{name}.info %{_infodir}/dir || :

%preun
if [ $1 = 0 ] ; then
/sbin/install-info --delete %{_infodir}/%{name}.info %{_infodir}/dir || :
fi

%files -f %{name}.lang
%doc AUTHORS ChangeLog NEWS README THANKS TODO
%license COPYING
%{_mandir}/man1/hello.1.*
%{_infodir}/hello.info.*
%{_bindir}/hello

%changelog
* Sun Nov 16 2014 The Coon of Ty <Ty@coon.org> - 2.10-1
- Update to 2.10
* Tue Sep 06 2011 The Coon of Ty <Ty@coon.org> - 2.8-1
- Initial version of the package

有了 spec 文件,你应该能够顺利完成构建过程,并创建源码和二进制 RPM 包。

接下来,使用 rpmlint 工具检查 spec 文件和所有 RPM 包是否符合 RPM 设计规范:

$ rpmlint hello.spec ../SRPMS/hello* ../RPMS/*/hello*

如果没有警告或错误,我们就成功了。否则,请使用 rpmlint -irpmlint -I <error_code> 命令查看更详细的 rpmlint 诊断说明。

使用 mock 构建 RPMs

使用 mock 在 Fedora 严格的构建环境中,测试构建软件包。默认 mock 将构建针对 Rawhide (Fedora 开发分支) 的软件包。

$ mock --verbose -r fedora-22-i386 --rebuild ../SRPMS/hello-2.10-1.fc22.src.rpm

mock 选项:

  • --rebuild:重建指定 SRPM
  • --buildsrpm:从 spec (--spec ...) 和源码 (--sources ...) 或从 SCM 构建 SRPM
  • --shell:在 chroot 内以交互方式运行指定命令。默认命令:/bin/sh
  • --chroot:在 chroot 内以非交互方式运行指定命令
  • --clean:彻底删除指定 chroot
  • --scrub=[all|chroot|cache|root-cache|c-cache|yum-cache]:彻底删除指定 chroot 或缓存目录或所有 chroot 和缓存
  • --init:初始化 chroot,不执行构建
  • --installdeps:为指定 SRPM 安装编译依赖
  • -i, --install:使用包管理安装软件包
  • --update:使用包管理更新已安装软件包
  • --remove:使用包管理删除软件包
  • --orphanskill:杀死指定 buildroot 的所有进程
  • --copyin:向指定 chroot 拷贝文件
  • --copyout:从指定 chroot 拷贝文件
  • --pm-cmd:执行包管理命令 (yum 或 dnf)
  • --yum-cmd:执行 yum 包管理命令
  • --dnf-cmd:执行 dnf 包管理命令
  • --snapshot:使用指定名称创建 LVM 快照
  • --remove-snapshot:删除指定 LVM 快照
  • --rollback-to:回滚到指定快照
  • --umount:卸载 buildroot,如果它已从独立设备挂载 (LVM)
  • --mount:挂载 buildroot,如果它已从独立设备挂载 (LVM)
  • -r CONFIG, --root=CONFIG:chroot 配置文件名或路径。路径以 .cfg 结尾,否则查找配置目录。默认:default
  • --offline:激活 'offline' 模式
  • -n, --no-clean:构建前不清理 chroot
  • --cleanup-after:构建后清理 chroot。与 --resultdir 同时使用。仅 'rebuild' 时激活
  • --no-cleanup-after:构建后不清理 chroot。如果启用自动清理,使用此选项关闭
  • --cache-alterations:更改 chroot (即--install) 后重建根缓存。使用 tmpfs 插件时有用
  • --nocheck:通过 --nocheck 选项使 rpmbuild 跳过 'make check' 测试
  • --arch=ARCH:调用 personality(),告诉内核模拟该系统架构
  • --target=RPMBUILD_ARCH:传递给 rpmbuild 作为编译目标架构
  • -D 'MACRO EXPR', --define='MACRO EXPR':定义 rpm 宏 (可多次指定)
  • --macro-file=MACROFILE:使用预定义 rpm 宏文件
  • --with=option:为构建启用配置选项 (可多次指定)
  • --without=option:为构建禁用配置选项 (可多次指定)
  • --resultdir=RESULTDIR:保存构建结果的目录
  • --rootdir=ROOTDIR:chroot 根目录
  • --uniqueext=UNIQUEEXT:设置唯一扩展名 (unique extension),追加至 buildroot 目录名
  • --configdir=CONFIGDIR:更改配置文件目录
  • --rpmbuild_timeout=RPMBUILD_TIMEOUT:设置 rpmbuild 任务超时时间
  • --unpriv:使用 --chroot 时,在运行命令前放弃特权
  • --cwd=DIR:使用 --chroot 时,运行命令前切换至指定目录 (相对于 chroot 环境)
  • --spec=SPEC:指定 spec 文件用于构建 SRPM (仅用于 --buildsrpm)
  • --sources=SOURCES:指定源 (文件/目录) 用于构建 SRPM (仅用于 --buildsrpm)
  • --symlink-dereference:跟随软链接 (仅用于 --buildsrpm)
  • --short-circuit=SHORT_CIRCUIT:传递 short-circuit 选项给 rpmbuild 跳过已完成的阶段。警告:不可用于生成软件包。暗含 --no-clean 选项。有效选项:build, install, binary
  • --rpmbuild-opts=RPMBUILD_OPTS:指定额外 rpmbuild 选项
  • --enablerepo=[repo]:使用 enablerepo 选项启用源
  • --disablerepo=[repo]:使用 disablerepo 选项禁用源
  • --old-chroot:使用旧 chroot 代替 systemd-nspawn
  • --new-chroot:使用新 chroot (systemd-nspawn)
  • --postinstall:在构建后,尝试在同一个 buildroot 安装软件包
  • -v, --verbose:详细信息
  • -q, --quiet:安静模式
  • --trace:启用内部 mock 跟踪输出
  • --enable-plugin=ENABLED_PLUGINS:启用插件。可用插件: ['tmpfs', 'root_cache', 'yum_cache', 'bind_mount', 'ccache', 'selinux', 'package_state', 'chroot_scan', 'lvm_root', 'compress_logs', 'sign', 'pm_request']
  • --disable-plugin=DISABLED_PLUGINS:禁用插件。可用插件: ['tmpfs', 'root_cache', 'yum_cache', 'bind_mount', 'ccache', 'selinux', 'package_state', 'chroot_scan', 'lvm_root', 'compress_logs', 'sign', 'pm_request']
  • --plugin-option=PLUGIN:KEY=VALUE:定义插件选项 (可多次指定)
  • -p, --print-root-path:打印 chroot 根路径
  • -l, --list-snapshots:列出 LVM 快照与 buildroot 的关联
  • --scm-enable:从 SCM 库构建
  • --scm-option=SCM_OPTS:定义 SCM 选项 (可多次指定)
  • --yum:使用 yum 包管理
  • --dnf:使用 dnf 包管理

参考文献

修改历史

Przemek Klosowski wrote this tutorial when he worked through Christoph Wickert's IRC session on building RPMs using Rahul Sundaram suggestion of GNU "Hello World" as a test case. After he wrote up his experience, he found out about the excellent and extensive How to create an RPM package page on this wiki, as well as the Christian Lyder Jacobsen's website. However, Christian isn't planning to update his site, and it seemed that a 5-minute 'fast food' alternative to the more extensive article might suit some people. More in-depth information on using and building RPM packages is available from other sources.