构建rpm包需要几要素

  • 构建工具(rpmbuild/rpm)
  • 构建环境
  • 规格描述文件xxx.spec

依赖构建工具

centos:

yum install rpm-build

macOS:

brew install rpm

Debian:

apt install rpm

打包环境

打包相关的配置保存在宏文件 (macrofiles) 中,默认使用 $HOME/rpmbuild 目录,用户配置文件保存在 $HOME/.rpmmacros;而制作包时的操作实际都在 topdir 指定的目录下

结构

如下图所示, 1级目录是必须结构的即{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} ,二级的是这里使用的例子

~/diskA/gopath/src/foo # tree -L 2  _output/rpm
root@vm43
_output/rpm
├── BUILD   # 解压后的tar.gz包,或者需要构建的源码、图标等文件
│   ├── deploy #测试例子
│   ├── example
│   ├── hack
├── BUILDROOT # 临时安装目录,会打包该目录下文件,此目录会根据版有一个和安装后文件结构一样的组织架构
├── RPMS #输出的rpm包(会按架构区分)
│   └── x86_64
├── SOURCES #安装源或者打包好的tar源码包
├── SPECS #构建描述文件
│   └── x3ds.spec
│   └──another.spec
└── SRPMS  #源码rpm包输出

这里的目录_output/rpm是由一个宏定义的%{_topdir}, 默认不指定的话是~/rpmbuild, 所谓宏,其实就是预先定义的一下变量, 有很多,他们都在spec中直接使用 可以通过rpm build的时候指定 rpmbuild -bb --define="_topdir [your_path_here]" your.spec

因此我们可以通过自己创建一个这样的目录结构在指定位置构建和输出

# create build folder
mkdir -pv ~/rpm-maker/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

上面BUILDROOT是某个包某个版本安装结构,可以理解为它就是最后安装rpm包后文件的放置位置是和这里的结构一致的.

比如这里输出一个构建完成的例子可能是这样的: struct 也就是说上图中的rpm包x3ds-v3.0.0.20200703-1.el7.x86_64会在安装的时候,按照这个结构放置相关的文件

规格描述文件

规格描述文件是后缀为spec的文件,用来描述rpm包 SPEC 文件包含如下字段:

SPEC DirectiveDefinitionExample
Name包的名称,应与 SPEC 文件名匹配curl
Version软件的上游版本号1.0.1
ReleaseRPM 软件版本号。初始值通常应为 1%{?dist},并在一个新版本构建时重置为 1
SummaryPM 包的简要说明1.0.1
URL该程序的更多信息的完整 URL(通常是打包的软件的上游项目网站)。https://here2say.com
License软件的许可证。1.0.1
Source0上游源代码的压缩归档的路径或 URL如果需要,可以添加更多的 SourceX 指令,每次递增数字,例如:Source1,Source2 Source3,依此类推x3ds.tar.gz
Patch0应用于源代码的第一个补丁的名称。如果需要,可以添加更多 PatchX 指令,增加每次编号如:Patch1,Patch2,Patch3 等。
BuildArch表示 RPM 包的构建的计算机架构。如果包不依赖于体系结构,即完全用于编写解释的编程语言,这应该是 BuildArch:noarchx86_64
BuildRequires编译软件包所需的依赖包列表,以逗号分隔gcc,go,bash
Requires安装软件包时所需的依赖包列表,以逗号分隔sh,python3
ExcludeArch如果某个软件无法在特定处理器架构下运行,在此进行指定。mips
Group软件分组System/Tools

在运维过程中,经常会查看到一个RPM包的Name、Version、Release。这个字段就是在 SPEC 文件中定义的。

例如,要查询 curl RPM 包信息

rpm -qi curl

可以看到和上面是对应的

除了描述部分,还有语法

RPM SPEC 文件中使用的语法。和构建阶段

SPEC DirectiveDefinition
%description完整描述了 RPM 中打包的软件,可以包含多行并分成段落。
%prep打包准备阶段执行一些命令
%build包含构建阶段执行的命令,构建完成后便开始后续安装。
%install包含安装阶段执行的命令。
%check包含测试阶段执行的命令。
%files需要被打包/安装的文件列表。
%changelogRPM 包变更日志。

贴一个完整的例子:

### 0.define section
%define example somevalue  # 自定义宏段方便后续使用, 引用格式%_example
%define _topdir /home/hoyho/rpm-maker  

### 1.The introduction section
Name:           myapp    # 包名称,通常会在指定源码压缩包时引用
Version:        1.0.0      #版本号
Release:        1%{?dist}    #发布号,每次制作rpm包时递增该数字
Summary:        myapp is a useless demo programm #no more than 50 char
License:        GPLv2+ and BSD  #GPL、BSD、MIT等
Group:          Applications/Tools #软件组,比如gui能分类的地方
URL:            http://here2say.com/           #官网或者介绍页面
Source0:        %{name}-myapp-%{version}.tar.gz #源码包名称,之前放SOURCE目录下的(可使用URL),也可用SourceN指定多个
BuildRequires:  gcc,make    # 制作过程中用到的软件包
Requires:       pcre,pcre-devel,openssl,chkconfig  #安装时所需软件包,可使用bash >= 1.1.1
Requires(pre):  test     #指定不同阶段的依赖  
BuildRoot:      %_topdir/BUILDROOT  #会打包该目录下文件,可查看安装后文件路径
Packager:       FooBar <[email protected]>  
Vendor:         kidding.com  





%description  #软件包的详细描述,随意写
myapp is blala....

#--- 2.The Prep section  ###准备阶段,主要是解压源码,并切换到源码目录下,创建目录结构等等初始化操作
%prep
%setup -q   #宏的作用是解压并切换到目录, 这里会把Source0: %{name}-myapp-%{version}.tar.gz解压放在BUILD目录下
#%patch0 -p1   # 如果需要打补丁,则依次写  

#--- 3.The Build Section  ###编译制作阶段,主要目的就是编译,当然现在而言有些都不必要,比如用Jenkins已经编译好了
%build  
make %{?_smp_mflags} #核则并行编译  

#--- 4.Install secti  ####安装阶段
%install
if [-d %{buildroot}];
  then  rm -rf %{buildroot}  #清空下安装目录,实际会自动清除
fi 
make install DESTDIR=%{buildroot} #安装到buildroot目录下
%{__install} -Dp -m0755 contrib/init.d %{buildroot}%{_initrddir}/foobar
%{__install} -d %{buildroot}%{_sysconfdir}/foobar.d/

#--- 4.1 scripts section  ##各个阶段的设置,比如是否需要备份原文件,初始化配置文件等等自己按需来
%pre   #装前执行的脚本
echo '-'

%post   #安装后执行的脚本
echo '--'

%preun   #卸载前执行的脚本
echo '----'

%postun  #卸载后执行的脚本
echo '-----'

%pretrans #事务开始时执行脚本
%posttrans  #在事务结束时执行脚本  

#--- 5. Clean section ####清理段,可以通过--clean删除BUILD不需要的文件
%clean
rm -rf %{buildroot}  

#--- 6. File section  ####打包时要包含的文件,注意同时需要在%install中安装
%files
%defattr (-,root,root,0755)  #设定默认权限
%config(noreplace) /etc/my.cnf  #表明是配置文件,noplace表示替换文件
%doc %{src_dir}/Docs/ChangeLog  #表明这个是文档
%attr(644, root, root) %{_mandir}/man8/myapp.8*  #分别是权限,属主,属组
%attr(755, root, root) %{_sbindir}/myapp.d

#--- 7. Chagelog section  ###记录SPEC的修改日志段
%changelog * Fri Dec 29 2012 foobar <[email protected]> - 1.0.0-1 - Initial version

值得一提的几个点:

  • BuildRoot: 在 %install 阶段(%build 阶段后)文件需要安装至此位置。默认情况下,根目录为 %{_topdir}/BUILDROOT/, 是的这里的面BUILDROOT就是前面所说的和安装rpm包结构一样的地方。 %buildroot 內的路径/usr/bin/tick5,其实等于最终 .rpm 安装它的路径。 所以说在写 %install 的時候,不妨把 %buildroot 看作最终安裝任何档案的 / 目录。

  • %install: 包含安装阶段执行的命令。命令将文件从 %{_builddir} 目录安装至 %{buildroot} 目录。可以使用 $RPM_BUILD_ROOT 代替 %{buildroot},两者都可以使用。

  • 术语: %{_builddir}(即"build目录"),与 %{buildroot}(即"build root目录")是不同的目录。在%{_builddir}中进行编译,并将需要打包的文件从%{_builddir}中复制到%{buildroot}中。 %install阶段的命令不会在用户安装RPM包时执行,此阶段只在打包时执行。 一般在%install阶段执行make install之类的命令。%make_install命令等同于 DESTDIR=%{buildroot} ,该命令会将文件安装到%{buildroot}目录中。如果程序不支持 DESTDIR ,可手动执行安装:需要在%{buildroot}下创建必要的目录,并从%{_builddir}复制文件至%{buildroot}目录。

  • %prep %setup -q -T -a 0 -a 7 -a 10 -c -n %{src_dir} 参数列表: -T 禁止自动解压源码文件 -D 解压前不删除目录 -a 切换目录前,解压指定Source文件,例如-a 0表示解压Source0 -b 切换目录后,解压指定Source文件,例如-a 0表示解压Source0 -n 解压后目录名称与RPM名称不同,则通过该参数指定切换目录 -c 解压缩之前先生成目录

RPM Macros

可以看到前面大量使用了各种宏定义 RPM (and SRPM)在构建过程非常实用 他们大多数暴露和打包有关的目录之类 当然不得不提,在打包过程.spec文件中最广泛使用的%{buildroot},该宏指向安装目标的根目录, 也用来设置打包时%install步骤中的 DESTDIR 其他很多都在 .spec 外部使用,比如覆盖原来的默认目录 具体参考下表

macrodefinitioncomment
%{buildroot}%{_buildrootdir}/%{name}-%{version}-%{release}.%{_arch}same as $BUILDROOT |
%{_topdir}%{getenv:HOME}/rpmbuild
%{_builddir}%{_topdir}/BUILD
%{_rpmdir}%{_topdir}/RPMS
%{_sourcedir}%{_topdir}/SOURCES
%{_specdir}%{_topdir}/SPECS
%{_srcrpmdir}%{_topdir}/SRPMS
%{_buildrootdir}%{_topdir}/BUILDROOT

路径相关的宏定义

macrodefinitioncomment
%{_sysconfdir}/etc
%{_prefix}/usrcan be defined to /app for flatpak builds
%{_exec_prefix}%{_prefix}default: /usr
%{_includedir}%{_prefix}/includedefault: /usr/include
%{_bindir}%{_exec_prefix}/bindefault: /usr/bin
%{_libdir}{_exec_prefix}/%{_lib}default: /usr/%{_lib}
%{_libexecdir}%{_exec_prefix}/libexecdefault: /usr/libexec
%{_sbindir}%{_exec_prefix}/sbindefault: /usr/sbin
%{_datadir}%{_datarootdir}default: /usr/share
%{_infodir}%{_datarootdir}/infodefault: /usr/share/info
%{_mandir}%{_datarootdir}/mandefault: /usr/share/man
%{_docdir}%{_datadir}/docdefault: /usr/share/doc
%{_rundir}/run
%{_localstatedir}/var
%{_sharedstatedir}/var/lib
%{_lib}lib64lib on 32bit platforms

一些比较少用或者比较旧的定义

macrodefinitioncomment
%{_datarootdir}%{_prefix}/sharedefault: /usr/share
%{_var}/var
%{_tmppath}%{_var}/tmpdefault: /var/tmp
%{_usr}/usr
%{_usrsrc}%{_usr}/srcdefault: /usr/src
%{_initddir}%{_sysconfdir}/rc.d/init.ddefault: /etc/rc.d/init.d
%{_initrddir}%{_initddir}old misspelling, provided for compatiblity

执行命令

最后看看如何生成 rpm 包,也就是使用 rpmbuild 命令,过程可以分为多个阶段。

rpmbuild --bb mysql.spec

使用参数: -bb 制作成二进制RPM包 -bs 制作源码的RPM包 -ba 源码和二进制两种形式的RPM包 -bl 检测BUILDROOT没有包含到rpm包中的文件

-bp 执行到pre -bc 执行到build段 -bi 执行install段

--quiet 默认会输出每次执行的shell命令,此时忽略

通过源码包重新编译

rpmbuild --rebuild package-0.0-0.src.rpm

编译时指定参数

rpmbuild -bb SPECS/yourpackage.spec --define "__version 0.9.4" --define "__release 1"

其他注意事项: 建议使用非root用户构建

ref:

https://www.ibm.com/developerworks/cn/linux/l-lo-rpm-build-package/index.html?ca=drs-

https://docs.fedoraproject.org/en-US/packaging-guidelines/RPMMacros/

https://www.jianshu.com/p/f1e36e4a152c

https://medium.com/linux-%E9%96%8B%E7%99%BC%E5%85%A5%E9%96%80/rpm-%E6%89%93%E5%8C%85-%E7%94%B1%E4%B8%80%E7%AB%85%E4%B8%8D%E9%80%9A%E5%88%B0%E5%8B%95%E6%89%8B%E6%BF%AB%E7%94%A8-%E4%BA%8C-df9eea70bd7b